C++中的深拷贝与浅拷贝
——给同学们讲一下“复制魔法”
同学们,今天我们学习一个非常重要的C++知识:
当我们复制一个对象时,到底是复制“外表”,还是复制“里面的东西”?
这就是:
浅拷贝(Shallow Copy)
深拷贝(Deep Copy)
它们主要和:
指针
动态内存(new)
类对象复制
有关。
一、先理解什么叫“拷贝”?
生活中:
小明有一本《C++魔法书》。
小红说:
“我也想要一本一样的。”
于是小明复制了一本。
这就是:
复制 = 拷贝C++里面:
Student a; Student b = a;意思:
把对象a复制给对象b。
例如:
int a = 10; int b = a;结果:
a: 10 b: 10两个数字互不影响。
如果:
b = 20;那么:
a: 10 b: 20没有问题。
因为:
int里面保存的是数据本身。
二、问题出现:指针!
看一个例子:
int a = 100; int *p = &a;内存:
a ┌─────┐ │100 │ └─────┘ ↑ | p指针p保存的是:
地址
不是100。
现在:
int *q = p;发生什么?
100 ┌─────┐ │ │ └─────┘ ↑ ↑ | | p qp和q:
指向同一个地方。
这就是:
浅拷贝
三、什么是浅拷贝?
一句话:
只复制地址,不复制真正的数据。
比如:
class Student { public: int age; int *score; };创建:
Student s1; s1.age = 10; s1.score = new int(100);内存:
s1 age ┌────┐ │10 │ └────┘ score | ↓ ┌─────┐ │100 │ └─────┘现在:
Student s2 = s1;默认拷贝:
C++会这样:
s2.age = s1.age s2.score = s1.score结果:
s1.score | ↓ ┌─────┐ │100 │ └─────┘ ↑ | s2.score两个指针:
指向同一个100。
四、浅拷贝有什么问题?
看代码:
#include<iostream> using namespace std; class Student { public: int *score; Student() { score = new int(100); } ~Student() { delete score; } }; int main() { Student a; Student b = a; }发生什么?
创建a:
a.score | ↓ 100复制b:
a.score | ↓ 100 ↑ | b.score程序结束:
先销毁b:
delete score;100被释放。
然后销毁a:
再次:
delete score;可是:
这个内存已经没有了!
这叫:
重复释放
可能导致:
程序崩溃
内存错误
五、什么是深拷贝?
深拷贝:
一句话:
不仅复制地址,而是重新复制一份数据。
还是刚才例子:
a:
a.score | ↓ 100复制:
Student b=a;深拷贝以后:
a.score | ↓ ┌─────┐ │100 │ └─────┘ b.score | ↓ ┌─────┐ │100 │ └─────┘注意:
两个100:
是两块不同的内存。
六、如何实现深拷贝?
需要自己写:
拷贝构造函数
格式:
类名(const 类名 &对象) { }例如:
#include<iostream> using namespace std; class Student { public: int *score; Student() { score = new int(100); } //拷贝构造函数 Student(const Student &s) { score = new int(*s.score); } ~Student() { delete score; } }; int main() { Student a; Student b=a; }关键:
score = new int(*s.score);拆开:
第一步:
取出原来的值:
*s.score得到:
100第二步:
重新申请空间:
new int(100)得到新的:
100所以:
两个对象:
互不影响。
七、浅拷贝 vs 深拷贝
| 比较 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制什么 | 地址 | 数据 |
| 速度 | 快 | 慢 |
| 是否申请新空间 | 否 | 是 |
| 两个对象是否共享数据 | 是 | 否 |
| 容易出错吗 | 容易 | 安全 |
| 需要自己写吗 | 默认提供 | 通常自己写 |
八、生活中的比喻
浅拷贝:
小明有一个遥控汽车。
小红复制:
拿了一张纸:
写:
遥控汽车的位置:小明家
这不是复制汽车。
只是复制地址。
深拷贝:
小红:
重新买了一辆一模一样的汽车。
现在:
小明玩自己的。
小红玩自己的。
互不影响。
九、什么时候需要深拷贝?
主要情况:
类里面有指针
例如:
class Array { int *data; };或者:
动态数组:
int *arr=new int[100];或者:
字符串:
char *name;都需要考虑深拷贝。
十、C++中的“三件套”
如果一个类管理动态内存:
通常需要:
1. 构造函数
申请空间:
new2. 拷贝构造函数
负责:
深拷贝
3. 析构函数
释放空间:
delete这三个叫:
Rule of Three(三法则)
十一、给同学们的记忆口诀
普通变量,直接复制。 指针变量,要看地址。 复制地址,是浅拷贝。 复制数据,是深拷贝。 new出来的东西, 一定要小心释放。十二、信奥竞赛中的理解
在GESP、CSP-J阶段:
通常:
int
double
char
string
vector
已经帮我们处理好了。
例如:
vector<int> a; vector<int> b=a;这里:
不是简单复制地址。
vector内部已经实现了深拷贝。
所以:
b[0]=100;不会影响:
a[0]但是学习:
链表
二叉树
动态数组
智能指针
时:
深浅拷贝就是必须掌握的核心知识。
一句话总结:
浅拷贝复制“钥匙”(地址),深拷贝复制“房子”(数据)。
指针越多,越要小心浅拷贝。