C++ 构造函数-拷贝构造和拷贝赋值
一、拷贝构造函数
当用一个对象来初始化另一个对象时,第一个对象会生成一个拷贝值来初始化另一个,如果没有实现 拷贝构造函数,编译器默认会生成一个。
编译器生成的拷贝构造是浅拷贝。
1 浅拷贝构造
浅拷贝(Shallow Copy)是C++中对象复制的一种方式,其核心在于仅复制对象成员的表面值,包括指针的地址,而不会为指针指向的堆内存创建新副本。这一行为是编译器默认生成的拷贝构造函数和赋值运算符的实现方式。
1. 核心特点
- 指针共享:若对象包含指针成员,浅拷贝后新旧对象的指针指向同一块堆内存(例如:
obj1.data_ptr = obj2.data_ptr
)。 - 默认行为:编译器自动生成浅拷贝逻辑,无需手动实现(零编码成本)。
- 效率优势:仅复制指针或基本类型数据,速度快,无额外内存开销。
2. 典型问题
浅拷贝在涉及动态内存管理的场景中会引发严重问题:
- 双重释放(Double Free):两个对象析构时重复释放同一块堆内存,导致程序崩溃(例如析构函数中执行
delete[] data
)。 - 数据意外修改:通过一个对象修改指针指向的数据,另一个对象的值同步变化,导致逻辑错误。
3. 示例代码与现象
#include <iostream>
using namespace std;
class ShallowClass {
public:
int* data;
ShallowClass(int val) {
data = new int(val);
}
// 使用编译器生成的默认拷贝构造函数(浅拷贝)
~ShallowClass() { delete data; }
};
int main() {
ShallowClass a(10);
ShallowClass b = a; // 触发浅拷贝
if(a.data == b.data )
cout << "a 和 b 的地址是同一个" << endl;
*a.data = 20; // 修改会影响两个对象
if(*b.data == *a.data)
cout<<"*b 也被改掉了, ";
cout << "b的值:" << *b.data << endl;
cout << "a的值:" << *a.data << endl;
}
运行结果:
a 和 b 的地址是同一个
*b 也被改掉了, b的值:20
a的值:20
*b.data
变为20,且程序因双重释放崩溃。
原理分析
- 指针直接复制:浅拷贝仅复制指针值,导致两个对象的
data
成员指向同一内存- 双重释放问题:析构时两个对象会先后释放同一块内存,触发未定义行为
4. 适用场景
浅拷贝并非完全不可用,其在以下场景中安全:
- POD类型(Plain Old Data):不含指针、文件句柄等资源的简单结构(例如
int
、float
等基本类型组成的类)。 - 临时对象传递:当对象生命周期短暂且无需独立管理资源时(如函数参数传递)。
2. 深拷贝构造
1. 深拷贝的特点
- 独立内存分配:拷贝时为新对象分配独立内存并复制数据内容
- 安全生命周期管理:每个对象拥有独立的堆内存,析构时互不影响
2. 深拷贝的语法分析
-
语法:
ClassName(const ClassName& other)
-
核心机制:
- 通过深拷贝避免指针资源重复释放
- 触发场景:传值参数、返回对象、显式拷贝构造调用
-
特殊注意:
错误示例(引发无限递归):阻断编译class Demo { public: Demo(Demo d) {} // 参数必须为引用类型 };
*正确示例:*用已经存在的a来初始化b。
// 常量左值引用(标准写法) class Demo { public: Demo(const Demo& d) {} }; Demo a(3); Demo b(a);//用a初始化b
3. 深拷贝实现与修正示例
#include <iostream>
using namespace std;
class DeepClass {
public:
int* data;
int size;
DeepClass(int val) : size(sizeof(val)) {
data = new int(val);
}
// 自定义深拷贝构造函数
DeepClass(const DeepClass& other) : size(other.size) {
data = new int(*other.data); // 新建内存并复制值
}
~DeepClass() { delete data; }
};
int main() {
DeepClass a(10);
DeepClass b = a; // 触发拷贝构造
if(a.data == b.data )
cout << "a 和 b 的地址是同一个" << endl;
*a.data = 20; // 修改会影响两个对象
if(*b.data == *a.data)
cout<<"*b 也被改掉了, ";
cout << "b的值:" << *b.data << endl;
cout << "a的值:" << *a.data << endl;
}
运行结果:
b的值:10
a的值:20
二、拷贝赋值
1. 拷贝赋值运算符
- 行为:将已有对象的值覆盖到已存在的对象,属于运算符重载。
- 示例:
b = a;
(前提是b
已存在) - 内存操作:需先释放目标对象原有资源,再分配新内存并复制内容,需处理自赋值问题。
2. 函数语法
-
拷贝赋值运算符:
ClassName& operator=(const ClassName& other)
-
实现方式:需先释放旧资源,再分配新资源并复制,同时检查自赋值。
// 深拷贝赋值运算符 DeepClass& operator=(const DeepClass& other) { if (this != &other) { delete data; size = other.size; data = new int(*other.data); } return *this; }
3.代码实现
#include <iostream>
#include <algorithm>
using namespace std;
class DeepClass {
public:
int* data;
int size;
DeepClass(int val) : size(sizeof(val)) {
data = new int(val);
}
// 自定义深拷贝构造函数
DeepClass(const DeepClass& other) : size(other.size) {
data = new int(*other.data); // 新建内存并复制值
}
// 深拷贝赋值运算符
DeepClass& operator=(const DeepClass& other) {
if (this != &other) {
delete data;
size = other.size;
data = new int(*other.data);
}
return *this;
}
~DeepClass() { delete data; }
};
int main() {
DeepClass a(10);
// DeepClass b = a; // 触发拷贝构造
DeepClass b(3);
b = a; // 触发拷贝赋值,如果没有实现赋值运算符重载则触发的是浅拷贝(下面a会影响b)
if(a.data == b.data )
cout << "a 和 b 的地址是同一个" << endl;
*a.data = 20; // 修改会影响两个对象
if(*b.data == *a.data)
cout<<"*b 也被改掉了, ";
cout << "b的值:" << *b.data << endl;
cout << "a的值:" << *a.data << endl;
}
运行结果:
b的值:10
a的值:20
三、拷贝构造和拷贝赋值
1. 拷贝构造:用一个已有的对象去构造一个副本对象
A a1 (...);
A a2 (a1); // 拷贝构造:a2就是a1的副本,a2和a1“一模一样”
A a2 = a1; // 拷贝构造
A a3 = A (...); // 拷贝构造
2. 拷贝赋值:用一个已有的对象去赋值给另一个已有的对象
A a1 (...);
A a2 (...);//a1和a2都是已有的对象
a2 = a1; // 拷贝赋值:a2和a1“一模一样”
3. 缺省的拷贝行为
对于简单类型的成员变量,做字节复制,对于类类型的成员变量,触发相应类型的拷贝动作。多数情况下缺省的拷贝行为已足够适用。
4. 拷贝构造的时
1) 构造对象的副本
A a2 (a1); // 直接触发构造函数
A a2 = a1; // 初始化时触发拷贝构造
A a2 = A (...);
2) 以对象为参数调用函数
void foo (A a) {...}
A a;
foo (a); // 值传递参数时触发拷贝构造
3) 从函数中返回对象
A foo (void) {
A a;
...
return a;
}
A a = foo (); //返回对象时触发拷贝构造
4) 以对象的形式捕获异常
try {
throw A;
}
//catch (A e) { //触发拷贝构造
catch (A& e) {
...
}
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student (const char* name) : m_name (strcpy (new char[strlen (name ? name : "") + 1], name ? name : "")) {
// m_name = new char[strlen (name) + 1];
// strcpy (m_name, name);
}
// 拷贝构造函数
Student (const Student& that) : m_name (strcpy (new char[strlen (that.m_name ? that.m_name : "") + 1], that.m_name ? that.m_name : "")) {}
// 拷贝赋值运算符
Student& operator= (const Student& that) {
// 防止自我赋值
if (&that != this) {
// 分配新资源
char* name =
new char[strlen (that.m_name) + 1];
// 释放旧资源
delete[] m_name;
// 拷贝新内容
m_name = strcpy (name, that.m_name);
}
// 返回自引用
return *this;
}
// 析构函数
~Student (void) {
if (m_name) {
delete[] m_name;
m_name = NULL;
}
}
void who (void) {
cout << m_name << endl;
}
char* m_name;
};
int main (void) {
char name[256] = "张飞";
Student s1 (name);
s1.who (); // 张飞
strcpy (name, "赵云");
Student s2 (name);//拷贝构造
s2.who (); // 赵云
s1.who (); // ?
Student s3 (s1);//拷贝构造
s3.who ();
strcpy (s1.m_name, "刘备");
s1.who (); // 刘备
s3.who (); // 张飞
s3 = s1; // s3.operator= (s1)//拷贝赋值
s3.who (); // 刘备
strcpy (s1.m_name, "张飞");
s1.who (); // 张飞
s3.who (); // 刘备
int a = 10, b = 20, c = 30;
(a = b) = c;
cout << a << endl;//30
return 0;
}