4.2构造函数与析构函数

146 阅读3分钟

构造函数与析构函数:对象的生命管理

一、构造函数家族

1.1 默认构造函数

class Robot {
public:
    // 默认构造函数(无参数)
    Robot() { 
        cout << "机器人已激活" << endl; 
    }
};
​
Robot r1;  // 自动调用默认构造

1.2 拷贝构造函数

class Book {
public:
    string title;
  
    // 拷贝构造(参数为同类对象的const引用)
    Book(const Book& other) {
        title = other.title + "_副本";
    }
};
​
Book origin{"C++ Primer"};
Book backup(origin);  // 调用拷贝构造
cout << backup.title; // 输出:C++ Primer_副本

1.3 委托构造(C++11)

class Circle {
    double radius;
public:
    Circle() : Circle(1.0) {} // 委托给带参构造
    Circle(double r) : radius(r) {}
};

🔍 类型对比表

构造函数类型触发场景典型特征
默认构造ClassName obj;无参数
拷贝构造对象初始化拷贝ClassName(const ClassName&)
委托构造构造器复用初始化列表调用其他构造器

二、初始化列表与explicit

2.1 初始化列表

class Temperature {
    const int scale;  // 必须通过初始化列表赋值
    double value;
public:
    Temperature(bool isCelsius) 
        : scale(isCelsius ? 0 : 1), value(0) { // 初始化const成员
        // 构造函数体
    }
};

🚨 常见错误: 尝试在构造函数体内初始化const成员会导致编译错误

2.2 explicit关键字

class Timer {
    int seconds;
public:
    explicit Timer(int s) : seconds(s) {}
};

void checkTimer(Timer t) {}

int main() {
    Timer t1(60);      // 正确:显式构造
    Timer t2 = 120;     // 错误:禁止隐式转换
    checkTimer(180);    // 错误:需要显式转换
}

🔑 设计建议: 对单参数构造函数总是使用explicit,除非明确需要隐式转换

三、深拷贝与浅拷贝

3.1 浅拷贝风险演示

class ShallowArray {
    int* data;
public:
    ShallowArray(int size) { 
        data = new int[size]; 
    }
    ~ShallowArray() { 
        delete[] data; 
    }
};

int main() {
    ShallowArray arr1(5);
    ShallowArray arr2 = arr1; // 默认拷贝构造(浅拷贝)
} // 运行时崩溃:双重释放内存

3.2 实现深拷贝

class DeepArray {
    int* data;
    int size;
public:
    // 自定义拷贝构造
    DeepArray(const DeepArray& other) 
        : size(other.size) {
        data = new int[size];
        memcpy(data, other.data, size*sizeof(int));
    }
  
    // 赋值运算符重载
    DeepArray& operator=(const DeepArray& other) {
        if(this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            memcpy(data, other.data, size*sizeof(int));
        }
        return *this;
    }
};

💡 内存示意图

浅拷贝:
对象A[内存块#100]
对象B[内存块#100] ❌

深拷贝:
对象A[内存块#100]
对象B[内存块#200]

四、析构函数实践

class FileHandler {
    FILE* file;
public:
    explicit FileHandler(const char* filename) {
        file = fopen(filename, "r");
    }
  
    ~FileHandler() {
        if(file) fclose(file);
        cout << "文件资源已释放" << endl;
    }
};

// 使用示例
void processFile() {
    FileHandler fh("data.txt");
    // 自动调用析构函数
}

五、综合应用案例

class SmartBuffer {
    char* buffer;
    size_t capacity;
public:
    explicit SmartBuffer(size_t size) 
        : capacity(size), buffer(new char[size]) {}
  
    // 拷贝构造(深拷贝)
    SmartBuffer(const SmartBuffer& other) 
        : capacity(other.capacity), buffer(new char[other.capacity]) {
        memcpy(buffer, other.buffer, capacity);
    }
  
    // 赋值运算符
    SmartBuffer& operator=(const SmartBuffer& rhs) {
        if(this != &rhs) {
            delete[] buffer;
            capacity = rhs.capacity;
            buffer = new char[capacity];
            memcpy(buffer, rhs.buffer, capacity);
        }
        return *this;
    }
  
    ~SmartBuffer() {
        delete[] buffer;
    }
};

六、总结与提升

课后思考

  1. 为什么拷贝构造函数的参数必须是引用类型?
  2. 什么情况下必须自定义析构函数?

进阶练习

  1. 实现一个字符串类,包含完整的构造/拷贝构造/析构体系
  2. 设计包含对象成员的类,验证成员初始化顺序

关键原则

  • 遵循Rule of Three:如果定义了拷贝构造/赋值运算符/析构函数中的一个,通常需要定义全部三个
  • 优先使用初始化列表进行成员初始化
  • 对资源管理类始终实现深拷贝

通过理解构造函数与析构函数的运作机制,开发者可以精确控制对象的生命周期,避免内存泄漏和资源竞争问题。建议使用Valgrind等工具检测内存管理问题,并在实际项目中实践RAII(资源获取即初始化)原则。