构造函数和析构函数
类的构造函数可以在创建新对象时初始化新对象,确保成员变量包含有效的值,确保对象在诞生时都是合法的、完整的。
构造函数总是与包含它的类同名,并且没有返回值(也没有返回类型)。
默认生成的成员函数
对于一个空类 MyClass{},C++ 编译器会为其自动生成六个默认的成员函数。
cpp
class MyClass {
public:
// 1. 默认构造函数
MyClass() {}
// 2. 默认拷贝构造函数
MyClass(const MyClass& rhs) {}
// 3. 默认拷贝赋值运算符
MyClass& operator=(const MyClass& rhs) {}
// 4. 移动构造函数
MyClass(MyClass&&) {}
// 5. 移动赋值运算符
MyClass& operator=(MyClass&&) {}
// 6. 默认析构函数
~MyClass() {}
};默认构造函数
无参数的构造函数,用于创建时不传初始值。
自定义默认构造函数
可以显式定义一个无参构造函数,或者一个所有参数都带默认值的构造函数,它们都算是默认构造函数。
cpp
// 1. 自定义无参默认构造函数
class MyClass {
public:
MyClass(); // 自定义的默认构造函数
};
MyClass mc; // 调用默认构造函数
// 2. 自定义所有参数带默认值的默认构造函数
class MyClass() {
public:
MyClass() = delete;
MyClass(int x = 0, int y = 0) {} // 带默认参数的构造函数,也是默认构造函数
}
MyClass mc1; // 不能与不带参的默认构造函数区分,因此需要删除不接收参数的构造函数
MyClass mc2 {2, 3};- 用户定义构造函数后,编译器不再提供默认的默认构造函数。
默认的默认构造函数
如果没有给类定义构造函数,编译器会提供默认的默认构造函数。
cpp
class MyClass {
public:
int a; // 未初始化
int b {0}; // 已初始化
std::string s; // 调用 std::string 的默认构造函数
};
MyClass mc1; // 调用默认构造函数
MyClass mc2(); // 调用默认构造函数- 编译器生成的默认构造函数没有参数,其唯一的作用是创建对象。
- 所有成员变量使用默认值,没有指定初始值的
a使用垃圾值。
default 关键字
C++ 11 允许用 = default 显式指定编译器生成默认构造函数(即使其它构造函数已定义)。
cpp
class MyClass {
public:
MyClass(int x) {}
MyClass() = default; // 显式生成默认构造函数
};
MyClass mc1; // 调用默认构造函数=default在值初始化时会对成员进行零初始化。- 现代 C++ 推荐需要显式声明默认构造函数时,优先使用
default关键字。
有参构造函数
用于按参数初始化对象:
cpp
class MyClass {
public:
MyClass(double length, double width, double height) {
m_length = length;
m_width = width;
m_height = height;
}
private:
double m_length;
double m_width;
double m_height;
};
MyClass mc1 {1, 2, 3}; // 调用有参构造函数
MyClass mc1(1, 2, 3); // 调用有参构造函数- 如果定义了有参构造函数,编译器就不会再提供默认构造函数(可显示定义),但会提供拷贝构造函数。
初始化列表
成员初始化列表 允许在构造对象时直接对成员变量进行初始化,而不是在构造函数体内赋值 。
cpp
class MyClass {
public:
MyClass(double length, double width, double height)
: m_length(length), m_width(width), m_height(height) {} // 初始化列表
private:
double m_length;
double m_width;
double m_height;
};- 成员变量的初始化顺序由类定义中的声明成员变量的顺序决定,而非按照成员初始化列表中出现的顺序进行初始化。
- 为避免混淆初始化列表最好按照类定义中成员变量的声明顺序
显式构造函数
关键字 explicit 防止隐式类型转换。
cpp
class MyClass {
public:
explicit MyClass(int x) { this->x = x; }
};
MyClass a = 20; // ❌ explicit 禁止隐式转换
MyClass b(20); // ✅ 显式构造
MyClass c{20}; // ✅ 显式构造- 应该将所有单参数构造函数声明为
explicit。
委托构造函数
一个构造函数调用另一个构造函数:
cpp
class MyClass {
public:
// 委托构造函数:用一个值同时初始化 x 和 y
MyClass(int x) : MyClass (x, x) {}
// 目标构造函数
MyClass(int x, int y) : x(x), y(y) {}
};
MyClass a(5); // x=5, y=5,委托给 MyClass(5, 5)
MyClass b(3, 7); // x=3, y=7- 可用于避免重复初始化代码。
拷贝构造函数
编译器会提供一个默认的拷贝构造函数,它通过复制已有对象(把实参对象的成员变量值复制给新对象)来创建对象。
但当类的一个或多个成员变量是指针时,就会产生不良后果(仅复制指针不会复制指针指向的内容)——新对象与原对象包含指向相同内容的成员。
cpp
class MyClass {
public:
int x, y;
MyClass(int x, int y) : x(x), y(y) {}
// 拷贝构造函数
MyClass(const MyClass& other) : x(other.x), y(other.y) {}
};
MyClass a(3, 7); // 普通构造
MyClass b(a); // 拷贝构造,b.x=3, b.y=7
MyClass c = a; // 同上,也是拷贝构造- 如果定义了拷贝构造函数,编译器就不会再提供默认的拷贝构造函数。
拷贝赋值运算符
当一个对象已经存在,并且需要将另一个对象的值赋给它时,会调用拷贝赋值运算符。
cpp
class MyClass {
public:
int x;
MyClass(int x) : x(x) {}
// 拷贝构造函数
MyClass(const MyClass& other) : x(other.x) {}
// 拷贝赋值运算符
MyClass& operator=(const MyClass& other) {
this->x = other.x;
return *this;
}
};
MyClass a(10);
MyClass b = a; // 拷贝构造,b 是新对象
MyClass c(5);
c = a; // 拷贝赋值运算符,c 已存在- 如果定义了拷贝赋值运算符,编译器就不会再提供默认的拷贝赋值运算符。
禁用构造函数
= delete 禁止特定的构造函数调用,例如禁止拷贝构造函数:
cpp
class MyClass {
public:
MyClass(const MyClass& other) = delete; // 禁止拷贝构造
};移动构造函数
移动构造函数允许资源所有权从一个对象转移到另一个对象,而不是复制资源。
cpp
class MyClass {
public:
// 移动构造函数
MyClass(MyClass&& other) {
data = other.data;
other.data = nullptr; // 防止析构释放原资源
}
private:
char* data;
};
MyClass a {"Hello"};
MyClass b = std::move(a); // 调用移动构造std::move(a)将a转换为右值,触发移动构造b获得a的资源指针,a.data被置为nullptr
移动赋值运算符
移动赋值运算符允许将一个对象的资源所有权转移给另一个对象,而不是复制资源。
cpp
class MyClass {
public:
// 移动赋值运算符
MyClass& operator=(MyClass&& other) {
if (this != &other) {
delete[] data; // 释放原资源
data = other.data;
other.data = nullptr; // 防止析构释放原资源
}
return *this;
}
private:
char* data;
};
MyClass a{"Hello"};
MyClass b;
b = std::move(a); // 移动赋值
b.print(); // 输出 Hello
a.print(); // 输出:null,资源已被转移析构函数
如果对类对象应用 delete 运算符,或处在创建类对象的块末尾,就会释放类对象。
释放类对象时,会执行析构函数以完成必要的清理工作。
类只有一个析构函数,如果没有自己定义,编译器会提供一个默认的、什么都不做的析构函数。
cpp
class MyClass {
public:
~MyClass(); // 析构函数,无参数、无返回值、不可重载
~MyClass() = default; // 如果析构函数的函数体为空,最好使用 default 关键字
};