拷贝构造函数和赋值运算符重载之间有什么区别?
参考回答
拷贝构造函数 和 赋值运算符重载 是C++中用于对象拷贝的重要特性,但它们在语法、调用时机和实现目的上存在明显区别。
区别对比
| 特性 | 拷贝构造函数 | 赋值运算符重载 |
|---|---|---|
| 作用 | 用于创建新对象,并用现有对象初始化它。 | 用于将一个已存在的对象的值赋给另一个已存在的对象。 |
| 调用时机 | 在创建新对象时触发,例如通过拷贝初始化。 | 在对象已经存在时触发赋值操作。 |
| 默认实现 | 编译器会生成默认的拷贝构造函数,进行成员逐一拷贝。 | 编译器会生成默认的赋值运算符,进行成员逐一赋值。 |
| 返回值 | 没有返回值,因为它用于对象的初始化。 | 通常返回对自身的引用(*this)。 |
| 是否重新分配资源 | 通常需要分配新资源(如动态内存)。 | 需要检查和释放目标对象已有的资源,然后再赋值。 |
| 代码示例 | A(const A& obj); |
A& operator=(const A& obj); |
详细讲解与示例
1. 拷贝构造函数
定义
拷贝构造函数是用来通过一个现有对象来初始化一个新对象的特殊构造函数。其形式如下:
class A {
public:
A(const A& obj); // 拷贝构造函数
};
调用时机
- 对象直接初始化:
A obj1; A obj2 = obj1; // 调用拷贝构造函数 - 函数按值传参或返回值:
void func(A obj); // obj 通过拷贝构造函数被初始化 A func(); // 返回值用拷贝构造函数构造
示例
#include <iostream>
using namespace std;
class A {
int* data;
public:
A(int value) {
data = new int(value);
cout << "Constructor called" << endl;
}
// 拷贝构造函数
A(const A& obj) {
data = new int(*obj.data);
cout << "Copy Constructor called" << endl;
}
void show() {
cout << "Value: " << *data << endl;
}
~A() {
delete data;
cout << "Destructor called" << endl;
}
};
int main() {
A obj1(10); // 调用普通构造函数
A obj2 = obj1; // 调用拷贝构造函数
obj2.show(); // 输出 Value: 10
return 0;
}
输出:
Constructor called
Copy Constructor called
Value: 10
Destructor called
Destructor called
2. 赋值运算符重载
定义
赋值运算符重载允许自定义对象的赋值行为。其形式如下:
class A {
public:
A& operator=(const A& obj); // 赋值运算符重载
};
调用时机
- 对象已经存在,通过赋值操作修改其值:
A obj1, obj2; obj1 = obj2; // 调用赋值运算符重载
示例
#include <iostream>
using namespace std;
class A {
int* data;
public:
A(int value) {
data = new int(value);
cout << "Constructor called" << endl;
}
// 赋值运算符重载
A& operator=(const A& obj) {
if (this == &obj) return *this; // 检查自赋值
delete data; // 释放旧资源
data = new int(*obj.data); // 分配新资源
cout << "Assignment Operator called" << endl;
return *this;
}
void show() {
cout << "Value: " << *data << endl;
}
~A() {
delete data;
cout << "Destructor called" << endl;
}
};
int main() {
A obj1(10); // 调用普通构造函数
A obj2(20); // 调用普通构造函数
obj1 = obj2; // 调用赋值运算符重载
obj1.show(); // 输出 Value: 20
return 0;
}
输出:
Constructor called
Constructor called
Assignment Operator called
Value: 20
Destructor called
Destructor called
3. 拷贝构造函数与赋值运算符的关键区别
| 特性 | 拷贝构造函数 | 赋值运算符重载 |
|---|---|---|
| 对象状态 | 创建新对象并初始化。 | 修改已存在对象的值。 |
| 资源管理 | 分配新资源,不需要释放旧资源。 | 需要释放已有资源并分配新资源。 |
| 返回值 | 无返回值。 | 返回对当前对象的引用(*this)。 |
| 调用方式 | 在对象创建时自动调用(如拷贝初始化)。 | 在赋值语句中调用(=)。 |
4. 常见错误
(a) 忘记检查自赋值
在赋值运算符重载中,如果未检查自赋值,可能导致数据被错误释放。
示例:
A& operator=(const A& obj) {
if (this == &obj) return *this; // 避免自赋值
delete data; // 正常释放资源
data = new int(*obj.data); // 分配新资源
return *this;
}
(b) 忽略深拷贝
默认的拷贝构造函数和赋值运算符执行浅拷贝。如果对象包含动态分配的内存,浅拷贝可能导致资源共享问题,进而引发程序崩溃。
解决方案:
– 手动实现深拷贝。
总结
| 特性 | 拷贝构造函数 | 赋值运算符重载 |
|---|---|---|
| 作用 | 用于创建新对象,并用现有对象初始化它。 | 用于将一个已存在的对象的值赋给另一个已存在的对象。 |
| 调用时机 | 在创建新对象时触发,例如通过拷贝初始化。 | 在对象已经存在时触发赋值操作。 |
| 返回值 | 没有返回值,因为它用于对象的初始化。 | 通常返回对自身的引用(*this)。 |
| 是否重新分配资源 | 通常需要分配新资源(如动态内存)。 | 需要检查和释放目标对象已有的资源,然后再赋值。 |
拷贝构造函数和赋值运算符重载的区别在于调用时机和功能定位,两者需要在深拷贝场景中配套正确实现,以避免潜在的问题。