区别
浅拷贝:简单的赋值拷贝
深拷贝:在堆区申请新的空间进行拷贝
示例
为方便理解,引入一段错误代码
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person 的默认构造函数调用" << endl;
}
Person(int age,int height)
{
pAge = age;
pHeight = new int(height);//堆区
cout << "Person 的有参构造函数调用" << endl;
}
~Person()
{
//析构代码主要用于释放堆区数据
if (pHeight != NULL)
{
delete pHeight;
pHeight = NULL;
}
cout << "Person 的析构函数调用" << endl;
}
int pAge;
int *pHeight;
};
void test()
{
Person p1(18,160);
cout << "p1 的年龄为:" << p1.pAge << endl;
cout << "p1 的身高为:" << *p1.pHeight << endl;
//如果 p2 利用编译器提供的拷贝构造函数,会做浅拷贝操作 (逐字节拷贝)
Person p2(p1);//默认的拷贝构造,为浅拷贝
cout << "p2 的年龄为:" << p2.pAge << endl;
cout << "p2 的身高为:" << *p2.pHeight << endl;
}
int main()
{
test();
system("pause");
}
上述代码可正常通过编译,但实际运行后会提示以下错误
输出结果为
可以看到虽然原对象没有拷贝构造函数,但 p2 依然可以正确输出结果,但执行 p2 的析构函数时程序报错,因此只有 p1 的析构函数调用输出
原理
如果原对象没有拷贝构造函数,编译器会自动生成浅拷贝操作,源码为:
Person(const Person &p)
{
//默认拷贝构造函数
pAge = p.pAge;
pHeight = p.pHeight;
}
不难理解,编译器做的就是把源对象内的成员值简单复制了过来
这会导致 p2 内的堆区成员与 p1 同源,也即是指向同一块堆区地址 (pAge 为局部变量,储存在栈区,由编译器自动分配释放)
如果在 test 函数中输出一下 p1.pHeight 和 p2.pHeight 就会发现值是相同的
当 test 函数结束时,编译器会做两件事:
- 销毁 p1,调用析构函数将堆区 p1.pHeight 释放
- 销毁 p2,调用析构函数将堆区 p2.pHeight 释放
因为 p2 中 pHeight 与 p1 指向同一块堆区地址,当 p1 将此地址释放后,p2 重复释放,导致程序程序报错
解决办法
为避免重复释放导致程序报错,就要使用深拷贝操作,可以手动定义 Person 的拷贝构造函数:
Person(const Person &p)
{
cout << "Person 的拷贝构造函数调用" << endl;
pAge = p.pAge;
pHeight = int new (*p.pHeight);//为 pHeight 开辟另一块堆区地址
}
此时 p2 将会为 pHeight 重新申请堆区空间,只是值与 p1 相同
如果输出 p1.pHeight 与 p2.pHeight 就会发现两者地址不同
图示
为方便理解,另附两张图
浅拷贝:
深拷贝: