浅拷贝和深拷贝
拷贝即赋值,把一个对象拷贝给另一个对象,即是用一个对象的值赋值给另一个对象。对于普通的值拷贝,其实并没有深拷贝和浅拷贝的区别(或者可以把这类的拷贝都认为是深拷贝):
int a, b;
a = 21;
b = a;
对于深拷贝和浅拷贝,最直接的理解就是看有没有 对需要被赋值的对象开辟属于自己的内存空间 。为什么这么说呢?因为浅拷贝只是增加了一个指向原有的内存的指针,并将这个指针赋值给新对象,这种情况下,当原对象的指针对所指向内存空间的值做修改时,新对象中指针所指向的值也会被修改。而深拷贝则会为新对象开辟一个属于自己的内存空间,并会把原对象中的值通通复制一份存入这个新开辟的内存空间中,并将指向这个新的内存空间的指针返回给新对象。
对于拥有指针成员变量的类,类对象之间的拷贝不能使用浅拷贝的方式。先来看一下以下代码:
class A//未定义拷贝构造函数的类,编译器会隐式生成一个按位拷贝的拷贝构造函数
{
public:
int *ptr;
A(){ ptr = new int(0); }
~A()
{
delete ptr;
ptr = nullptr;
}
};
class B//自定义一个深拷贝构造函数
{
public:
int *ptr;
B(){ ptr = new int(0); }
B(B& b){
ptr = new int(*b.ptr);//为新对象申请一个新的内存空间并用对象b的值对其初始化
}
~B()
{
delete ptr;
ptr = nullptr;
}
};
int main()
{
A a;
A aa(a);
cout<< *a.ptr <<'\n'; //输出0
cout<< *aa.ptr <<'\n'; //输出0
*a.ptr = 21;
cout<< *a.ptr <<'\n'; //输出21
cout<< *aa.ptr <<'\n'; //输出21
B b;
B bb(b);
cout<< *b.ptr <<'\n'; //输出0
cout<< *bb.ptr <<'\n'; //输出0
*b.ptr = 21;
cout<< *b.ptr <<'\n'; //输出21
cout<< *bb.ptr <<'\n'; //输出0
return 0;
}//aa析构异常
在类A中,并没有声明拷贝构造函数,这样编译器会在编译的时候自动生成一个缺省的拷贝构造函数,这个函数的作用就是执行类似memcpy的按位拷贝,这是一种浅拷贝的方式,这种方式就导致了a.ptr和aa.ptr指向了同一块内存,通过a.ptr修改了内存中的值之后,对象aa也可以感受到这次修改。在main作用域结束之后,对象aa先调用了析构函数,将aa.ptr指向的内存空间释放,此时a.ptr就会成为一个野指针,之后对象a再调用析构函数是,就会执行一个 对野指针进行内存释放的操作,这是非常危险的! 而在类B中,由于声明了一个深拷贝构造函数,因此每个对象的指针成员都有一块只属于自己的内存空间,因此就不会引发内存泄漏和内存崩溃等相关问题的出现。