构造函数与析构函数:对象的生命管理
一、构造函数家族
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;
}
};
六、总结与提升
课后思考:
- 为什么拷贝构造函数的参数必须是引用类型?
- 什么情况下必须自定义析构函数?
进阶练习:
- 实现一个字符串类,包含完整的构造/拷贝构造/析构体系
- 设计包含对象成员的类,验证成员初始化顺序
关键原则:
- 遵循Rule of Three:如果定义了拷贝构造/赋值运算符/析构函数中的一个,通常需要定义全部三个
- 优先使用初始化列表进行成员初始化
- 对资源管理类始终实现深拷贝
通过理解构造函数与析构函数的运作机制,开发者可以精确控制对象的生命周期,避免内存泄漏和资源竞争问题。建议使用Valgrind等工具检测内存管理问题,并在实际项目中实践RAII(资源获取即初始化)原则。