构造函数 vs 拷贝构造函数
在 C++ 中,构造函数 和 拷贝构造函数 都用于初始化对象,但它们的作用不同:
- 构造函数(Constructor):用于创建对象并进行初始化。
- 拷贝构造函数(Copy Constructor):用于 用一个对象初始化另一个同类型对象。
1. 构造函数(Constructor)
作用:
- 在创建对象时自动调用。
- 主要用于给对象的成员变量赋初值。
语法:
class A {
public:
int num1;
int num2;
// 普通构造函数
A(int a = 0, int b = 0) : num1(a), num2(b) {
std::cout << "Constructor called!" << std::endl;
}
};
示例:
A a(10, 20); // 构造函数被调用,a.num1 = 10, a.num2 = 20
A b; // 默认构造,b.num1 = 0, b.num2 = 0
2. 拷贝构造函数(Copy Constructor)
作用:
- 用 已有的对象 初始化 新对象。
- 如果不显式定义,编译器会自动生成一个默认的 浅拷贝 拷贝构造函数。
语法:
A(const A& obj) {
num1 = obj.num1;
num2 = obj.num2;
}
示例:
A a(10, 20);
A b = a; // 触发拷贝构造函数
3. 构造函数 vs 拷贝构造函数 的区别
类型 | 作用 | 触发场景 | 代码示例 |
---|---|---|---|
构造函数 | 初始化对象 | A obj; 或 A obj(10, 20); | A(int a, int b) { ... } |
拷贝构造函数 | 复制已有对象 | A obj2 = obj1; 或 A obj2(obj1); | A(const A& a) { ... } |
4. 构造函数 vs 拷贝构造函数 代码示例
#include <iostream>
using namespace std;
class A {
public:
int num1;
int num2;
// 普通构造函数
A(int a = 0, int b = 0) : num1(a), num2(b) {
cout << "Constructor called!" << endl;
}
// 拷贝构造函数
A(const A& a) {
num1 = a.num1;
num2 = a.num2;
cout << "Copy Constructor called!" << endl;
}
};
int main() {
A obj1(10, 20); // 调用构造函数
A obj2 = obj1; // 调用拷贝构造函数
cout << "obj2: num1 = " << obj2.num1 << ", num2 = " << obj2.num2 << endl;
return 0;
}
输出
Constructor called!
Copy Constructor called!
obj2: num1 = 10, num2 = 20
5. 拷贝构造函数的默认行为:浅拷贝
如果不显式定义拷贝构造函数,C++ 编译器会自动生成 默认的拷贝构造函数,它执行 浅拷贝(默认拷贝成员变量的值)。
class A {
public:
int* ptr;
A(int value) {
ptr = new int(value); // 动态分配内存
}
~A() {
delete ptr; // 释放内存
}
};
int main() {
A obj1(10);
A obj2 = obj1; // 默认拷贝构造函数,浅拷贝
*(obj2.ptr) = 20; // 修改 obj2 的值
cout << *(obj1.ptr) << endl; // obj1 的值也变了!
}
问题:
obj1
和obj2
共享相同的指针,修改obj2
会影响obj1
。obj1
和obj2
在析构时会 两次删除同一块内存,导致 程序崩溃(double free 错误)。
解决方案:深拷贝
如果类包含 动态分配的内存(new
),需要 显式实现拷贝构造函数,执行 深拷贝。
class A {
public:
int* ptr;
A(int value) {
ptr = new int(value);
}
// 深拷贝
A(const A& obj) {
ptr = new int(*(obj.ptr)); // 分配新的内存,复制内容
}
~A() {
delete ptr; // 释放内存
}
};
6. 为什么拷贝构造函数参数必须是 const A&
?
A(A obj) { ... } // ❌ 错误!
如果参数是 值传递,意味着在 调用拷贝构造函数时会再拷贝一次,造成 无限递归,最终导致 栈溢出。
正确写法
A(const A& obj) { ... } // ✅ 传引用,避免递归调用
7. 总结
特性 | 构造函数 | 拷贝构造函数 |
---|---|---|
触发条件 | 创建新对象 | 用一个已有对象初始化新对象 |
默认行为 | 初始化成员变量 | 浅拷贝成员变量 |
传递方式 | 参数自由 | const A& 避免递归 |
拷贝构造函数的关键点
✅ 如果类包含 指针成员变量,必须 定义深拷贝 以避免内存泄漏。
✅ 参数必须是 const A&
,否则会引起 无限递归调用。
✅ 如果类不需要拷贝,可以使用 delete
禁止拷贝:
A(const A&) = delete;