跳转至

03: 构造函数与析构函数 (Constructors & Destructors)

构造函数负责对象的初始化,析构函数负责对象的清理。


构造函数概览

构造函数是一种在对象创建时自动调用的特殊成员函数,用于初始化对象的数据成员。

核心特点

  1. 函数名与类名完全一致
  2. 无返回值(连 void 都不能写)
  3. 可以重载(多个不同参数的构造函数)
  4. 自动调用,无需手动调用
  5. 不能被继承

构造函数的类型

类型 语法 调用时机
默认构造 ClassName() 创建无参对象
带参构造 ClassName(type param) 创建时传入参数
拷贝构造 ClassName(const ClassName& other) 用已有对象创建新对象

默认构造函数 (Default Constructor)

不带参数的构造函数。如果程序员没有定义任何构造函数,编译器会自动生成一个空的默认构造。

C++
1
2
3
4
5
class Simple {
private:
    int _value;
    // 编译器自动生成 public 的 构造函数 Simple() {},此时 _value 不会被初始化
};

Warning

默认构造的陷阱

编译器自动生成的默认构造函数不会初始化内置类型(如 int、double)成员,它们的值是随机的。建议始终手动初始化。

C++
class Safe {
private:
    int _value = 0;            // 类内初始化(这样默认构造函数会初始化 _value 为 0)
public:
    Safe() = default;          // 显式要求编译器生成
};

class Explicit {
    int _value;
public:
    Explicit() : _value(0) {}  // 手动定义,确保初始化
};

默认构造的"消失"规则

一旦定义了任何一个带参构造函数,编译器就不再生成默认构造函数。

C++
1
2
3
4
5
6
7
8
9
class Rectangle {
private:
    double _width, _height;
public:
    Rectangle(double w, double h) : _width(w), _height(h) {}
};

// Rectangle r;     // 编译错误!编译器不生成默认构造函数,现有的构造函数是带参的
Rectangle r(3, 4);  // 编译通过!

Tip

同时提供两种构造

可以显式要求编译器生成默认构造函数,或者手动定义一个无参构造函数:

C++
Rectangle() = default;
// 或 Rectangle() : _width(0), _height(0) {}


带参构造函数与初始化列表

初始化列表 (Initializer List)

在构造函数的参数列表后使用冒号引出,用于直接初始化成员变量:

C++
1
2
3
4
ClassName(type param1, type param2)
    : member1(param1), member2(param2) {
    // 函数体
}

这段代码的执行顺序为: 1. 调用成员变量的构造函数,并传入参数 param1、param2 2. 按顺序依次对 member1、member2 进行初始化(分别初始化为 param1 和 param2) 3. 执行构造函数的函数体

初始化列表 vs 函数体赋值:

方面 初始化列表 函数体赋值
效率 直接构造 先默认构造再赋值,会多一步
const 成员 可以 不可以
引用成员 可以 不可以
对象成员(无默认构造) 可以 不可以

Important

必须使用初始化列表的场景

  1. 初始化 const 成员
  2. 初始化引用类型成员
  3. 初始化没有默认构造函数的对象成员

前两个场景是因为 const 和引用必须在创建时初始化,无法重新赋值;第三个场景是因为在执行函数体前,编译器会尝试调用对象成员的默认构造函数来对其进行初始化,如果没有默认构造函数就会编译错误!

C++
class Student {
private:
    string _name;
    int _id;
    double _score;
public:
    Student(string name, int id, double score)
        : _name(name), _id(id), _score(score) {}
};
class MustUseList {
private:
    const int _id;       // const 成员
    int& _ref;           // 引用成员
    Student _s;          // 对象成员(无默认构造)
public:
    MustUseList(int id, int& ref, string name)
        : _id(id), _ref(ref), _s(name, id, 0.0) {}
};

构造函数重载

一个类可以有多个构造函数,通过参数的数量或类型区分:

默认参数 (Default Arguments)

C++
class Time {
private:
    int _hour, _minute, _second;
public:
    Time() : _hour(0), _minute(0), _second(0) {}

    Time(int h) : _hour(h), _minute(0), _second(0) {
        if (_hour < 0 || _hour >= 24) _hour = 0;
    }

    Time(int h, int m, int s)
        : _hour(h), _minute(m), _second(s) {
        if (_hour < 0 || _hour >= 24) _hour = 0;
        if (_minute < 0 || _minute >= 60) _minute = 0;
        if (_second < 0 || _second >= 60) _second = 0;
    }
};

Time t1;             // 调用默认构造
Time t2 = 10;        // 调用单参数构造(隐式转换)
Time t3(10);         // 调用单参数构造
Time t4(10, 30, 45); // 调用三参数构造

对于 t2,编译器会将 10 隐式转换为 Time(10),然后调用单参数构造函数。

explicit 关键字

explicit 阻止编译器通过单参数构造函数进行隐式类型转换。

C++
class Time {
private:
    int _hour, _minute, _second;
public:
    Time() : _hour(0), _minute(0), _second(0) {}

    explicit Time(int h) : _hour(h), _minute(0), _second(0) {
        if (_hour < 0 || _hour >= 24) _hour = 0;
    }

    Time(int h, int m, int s)
        : _hour(h), _minute(m), _second(s) {
        if (_hour < 0 || _hour >= 24) _hour = 0;
        if (_minute < 0 || _minute >= 60) _minute = 0;
        if (_second < 0 || _second >= 60) _second = 0;
    }
};

// Time t2 = 10;    编译错误,因为 explicit 阻止了隐式转换,这行会被视为尝试将 int 转换为 Time
Time t3(10);     // 调用单参数构造函数,编译通过

Tip

什么时候用 explicit?

建议所有单参数构造函数都加上 explicit,除非你明确需要隐式转换。


委托构造函数 (Delegating Constructor, C++11)

一个构造函数可以调用同一个类的另一个构造函数,避免代码重复:

C++
class Time {
private:
    int _hour, _minute, _second;
public:
    Time() : Time(0, 0, 0) {}         // 委托给三参数构造
    Time(int h) : Time(h, 0, 0) {}    // 委托给三参数构造
    Time(int h, int m, int s)
        : _hour(h), _minute(m), _second(s) {
        // 核心初始化逻辑
    }
};

拷贝构造函数 (Copy Constructor)

使用同一个类的已有对象来初始化新对象。

C++
ClassName(const ClassName& source);

使用场景

场景 说明
值传递 void func(Student s)
值返回 Student getStudent()
声明时拷贝 Student s2 = s1;
列表初始化 Student s2{s1};
C++
class Student {
    string _name;
    int _id;
public:
    Student(string name, int id) : _name(name), _id(id) {}
    Student(const Student& other)
        : _name(other._name), _id(other._id) {
        cout << "拷贝构造被调用" << endl;
    }
};

Student s1("Alice", 1001);
Student s2 = s1;  // 调用拷贝构造函数

程序会输出 拷贝构造被调用,s2 成员的值会和 s1 一样,因为 s2 是通过 s1 拷贝构造出来的。


浅拷贝 vs 深拷贝

一个很容易踩的坑

Warning

浅拷贝的问题

当类包含指针成员且指向动态分配的内存时,编译器自动生成的拷贝构造只拷贝指针值(浅拷贝),导致两个对象指向同一块内存,析构时产生"双重释放"。

C++
// 浅拷贝问题
class Shallow {
    int* _data;
public:
    Shallow(int val) : _data(new int(val)) {}
    ~Shallow() { delete _data; }
    // 没有定义拷贝构造!
};

void problem() {
    Shallow a(42);
    Shallow b = a;   // a._data 和 b._data 指向同一地址
}                    // 析构时 double free!
C++
1
2
3
4
5
6
7
8
9
// 深拷贝
class Deep {
    int* _data;
public:
    Deep(int val) : _data(new int(val)) {}
    Deep(const Deep& other)
        : _data(new int(*other._data)) {}  // 通过 new 分配新内存并拷贝内容
    ~Deep() { delete _data; }
};
特性 浅拷贝 深拷贝
生成方式 编译器默认生成 程序员手动定义
指针复制 只复制地址值 分配新内存再复制内容
内存独立性 不独立 完全独立
适用场景 无指针成员 包含动态分配内存

析构函数 (Destructor)

析构函数在对象生命周期结束时自动调用,负责清理资源。

核心规则

  1. 名称:~ClassName()
  2. 无返回值、无参数
  3. 不能重载
  4. 自动调用,不能显式调用
  5. 如果未定义,编译器自动生成空析构
C++
1
2
3
4
5
6
7
class Resource {
private:
    int* _data;
public:
    Resource() : _data(new int[100]) {}
    ~Resource() { delete[] _data; }
};

构造与析构的顺序

C++
class Demo {
public:
    Demo(int id) : _id(id) { cout << "构造 " << _id << endl; }
    ~Demo() { cout << "析构 " << _id << endl; }
private:
    int _id;
};

Demo e(5);             // 构造 5
int main() {
    Demo a(1);         // 构造 1
    Demo b(2);         // 构造 2
    {
        Demo c(3);     // 构造 3
    }                  // 析构 3
    Demo d(4);         // 构造 4
    return 0;
}

输出如下:

Bash
构造 5
构造 1
构造 2
构造 3
析构 3
构造 4
析构 4
析构 2
析构 1
析构 5

全局对象的生命周期跨越整个程序:最先构造,最后析构。同一作用域内的局部对象先构造的后解析(栈式结构)


三大法则 (Rule of Three)

如果类需要自定义析构函数,通常也需要自定义拷贝构造函数和拷贝赋值运算符。

三件套 作用
析构函数 释放动态资源
拷贝构造函数 深拷贝初始化
拷贝赋值运算符 深拷贝赋值
C++
class String {
private:
    char* _data;
    size_t _size;
public:
    String(const char* str = "") {
        _size = strlen(str);
        _data = new char[_size + 1];
        strcpy(_data, str);
    }

    ~String() { delete[] _data; }                       // 1. 析构

    String(const String& other) : _size(other._size) {  // 2. 拷贝构造
        _data = new char[_size + 1];
        strcpy(_data, other._data);
    }

    String& operator=(const String& other) {            // 3. 拷贝赋值
        if (this == &other) return *this;  // 自赋值检查
        delete[] _data;                    // 释放旧资源
        _size = other._size;
        _data = new char[_size + 1];       // 分配新资源
        strcpy(_data, other._data);
        return *this;
    }
};

Tip

Rule of Five (C++11)

现代 C++ 扩展为"五大法则"——还需加上移动构造函数和移动赋值运算符。