浅拷贝与深拷贝最容易区别的地方在于堆空间的拷贝
浅拷贝:也是默认拷贝构造函数,在没有定义拷贝构造函数时编译器会自动生成一个默认的拷贝构造函数。
#include <iostream>
using namespace std;
class A
{
int num;
public:
A(int n) :num(n) {}
~A() {}
//浅拷贝
A(const A& a)
{
num = a.num;
}
void GetNum()
{
cout << "num = " << num << endl;
}
};
int main()
{
A a1(2);
a1.GetNum();
A a2(a1);//等价于 A a2 = a1;
a2.GetNum();
return 0;
}
上例是一个很简单的浅拷贝,只有一个数据成员的赋值操作,编译器默认拷贝实现的也就是如上的效果,输出如下:
num = 2;
num = 2;
深拷贝:增加堆空间的new操作
如果仍使用浅拷贝构造函数会如何呢?如下所示:
#include <iostream>
using namespace std;
class A
{
int num;
int *p;//int型指针
public:
A(int n) :num(n)
{
p = new int(n);//创建堆空间
}
~A()
{
if(p != NULL)
{
delete p;
}
}
//浅拷贝
A(const A& a)
{
num = a.num;
p = a.p;//直接赋值
}
void GetNum()
{
cout << "num = " << num << ", p = " << p << ", *p = " << *p << endl;
}
};
int main()
{
A a1(2);
a1.GetNum();
A a2(a1);//等价于 A a2 = a1;
a2.GetNum();
return 0;
}
结果是能正常编译,但无法运行。
这是因为 a2 在拷贝 a1 的时候,指针(int *)p的地址也直接复制过来了;那么在程序结束,两个类调用两次析构函数 delete p; 但是其地址都是使用的同一个,就会重复释放造成错误。
这里可能会疑惑析构函数里的判断语句,第一次 delete p 之后 p 会不会等于NULL, 答案是不等于;原因是两个指针 p 都指向了一块地址,所以就算delete释放空间的值后,地址本身还被另一个p记录着,仍然会再次delete;
钻一下牛角尖:
如果去掉析构函数里的delete语句,结果确实可以正常运行。
但是,这种行为的性质是造成内存泄漏,是不安全的,不可取的。
用户创建堆空间的时候要记得手动释放。
那么针对堆内存的问题,如何实现深拷贝
只需要替换一下浅拷贝的代码:
//深拷贝
A(const A& a)
{
num = a.num;
p = new int(*a.p);
}
输出如下:
num = 2, p = 00CF55D8, *p = 2
num = 2, p = 00CF5608, *p = 2