在C++编程语言中,类和对象是最核心的面向对象编程概念。类可以理解为一种用户自定义的数据类型,它不仅仅包含数据成员,还包含操作这些数据的成员函数。而对象则是类的具体实例,就像建筑设计图纸(类)和实际建造出来的房子(对象)之间的关系。
初学者常犯的一个误区是混淆类声明和对象定义。类声明只是定义了这种类型的结构,并不会占用内存空间。只有当创建类的实例(即对象)时,才会真正分配内存。例如:
cpp复制class Student { // 类声明
string name;
int age;
};
Student s1; // 对象定义,此时才分配内存
提示:在C++中,类定义通常放在头文件(.h)中,而成员函数实现则放在源文件(.cpp)中,这是良好的工程实践。
构造函数是类中非常特殊的成员函数,它在对象创建时自动调用。如果没有显式定义构造函数,编译器会生成一个默认的无参构造函数。但一旦定义了任何构造函数,编译器就不再提供默认构造函数。
cpp复制class Person {
public:
Person() { cout << "默认构造函数" << endl; }
Person(string n) : name(n) { cout << "带参构造函数" << endl; }
private:
string name;
};
析构函数则是在对象生命周期结束时自动调用的函数,用于释放资源。它没有返回值,也不接受参数:
cpp复制~Person() {
cout << "析构函数被调用" << endl;
}
注意:如果类中有动态分配的内存,必须自定义析构函数来释放这些资源,否则会造成内存泄漏。
拷贝构造函数用于通过已有对象创建新对象,其典型声明形式为:
cpp复制Person(const Person& other);
当发生以下情况时会调用拷贝构造函数:
浅拷贝与深拷贝是拷贝构造函数中的关键概念。默认的拷贝构造函数执行的是浅拷贝,即简单复制成员变量的值。如果类中有指针成员,通常需要自定义拷贝构造函数实现深拷贝:
cpp复制Person(const Person& other) {
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
}
赋值运算符重载(operator=)允许我们自定义对象之间的赋值行为。它与拷贝构造函数的区别在于:拷贝构造函数用于初始化新对象,而赋值运算符用于已存在对象之间的赋值。
cpp复制Person& operator=(const Person& other) {
if (this != &other) { // 防止自赋值
delete[] name; // 释放原有资源
name = new char[strlen(other.name) + 1];
strcpy(name, other.name);
}
return *this;
}
取地址运算符重载包括两个版本:
cpp复制Person* operator&() { return this; } // 普通对象
const Person* operator&() const { return this; } // const对象
虽然这两个运算符很少需要重载,但在某些特殊场景下(如智能指针实现)会很有用。
const成员函数是指在函数声明后加const关键字的成员函数,表示该函数不会修改对象的状态:
cpp复制string getName() const { return name; }
const对象只能调用const成员函数,这是C++保证对象不被修改的重要机制。
C++编译器在以下情况下会自动生成默认成员函数:
默认生成的函数有以下特点:
这些默认行为在类包含指针成员时通常是不安全的,会导致内存泄漏或重复释放等问题。
C++11引入了移动语义,为此新增了两个特殊成员函数:
cpp复制Person(Person&& other); // 移动构造函数
Person& operator=(Person&& other); // 移动赋值运算符
移动操作通过"窃取"资源而非复制来提高效率,特别适合处理临时对象或即将销毁的对象。
在C++中,有三个基本操作密切相关:析构函数、拷贝构造函数和拷贝赋值运算符。如果定义了其中一个,通常也需要定义其他两个(三法则)。C++11后扩展到五个(加上移动构造函数和移动赋值运算符)。
cpp复制class MyString {
public:
MyString(const char* str = nullptr); // 构造函数
~MyString(); // 析构函数
MyString(const MyString& other); // 拷贝构造函数
MyString& operator=(const MyString& other); // 赋值运算符
MyString(MyString&& other) noexcept; // 移动构造函数
MyString& operator=(MyString&& other) noexcept; // 移动赋值
private:
char* data;
};
// 实现略...
在这个案例中,我们完整实现了六大特殊成员函数,确保类的资源管理安全可靠。
当派生类对象赋值给基类对象时,会发生对象切片,派生类特有的部分会被"切掉"。解决方案是使用指针或引用,或者禁止拷贝操作。
在赋值运算符实现中,必须处理自赋值情况:
cpp复制MyString& operator=(const MyString& other) {
if (this != &other) { // 自赋值检查
// 实现代码...
}
return *this;
}
在资源管理类中,需要确保即使在异常发生时也不会泄漏资源。一种常见技术是"拷贝并交换"惯用法:
cpp复制MyString& operator=(MyString other) { // 注意:参数是按值传递
swap(*this, other);
return *this;
}
cpp复制class UniqueFile {
public:
UniqueFile(const UniqueFile&) = delete; // 禁止拷贝
UniqueFile(UniqueFile&&) noexcept; // 允许移动
// ...
};
C++11/14/17对特殊成员函数的生成规则有重要改变:
理解这些变化对于编写现代C++代码至关重要。