Skip to content

构造函数和析构函数 ​

类的构造函数可以在创建新对象时初始化新对象,确保成员变量包含有效的值,确保对象在诞生时都是合法的、完整的。

构造函数总是与包含它的类同名,并且没有返回值(也没有返回类型)。

默认生成的成员函数 ​

对于一个空类 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 关键字
};