深浅拷贝是面试面试经典问题
C++中的构造函数有三类,默认构造函数,有参构造函数,拷贝构造函数。
首先考虑一种情况,对一个已经定义的对象进行拷贝,编译器会调用构造函数中的拷贝构造函数。
浅拷贝: 如果用户没有定义自己的拷贝构造函数,则会调用默认的拷贝构造函数。默认拷贝构造函数中会对对象中的变量进行简单的赋值拷贝操作,就是一个等号赋值操作,这种操作就是浅拷贝。
深拷贝: 而深拷贝就是在堆区重新申请一块空间,来进行拷贝赋值操作。
在下面的程序中,我们先定义一个dog1对象,并将dog1拷贝给dog2
#include <iostream>
using namespace std;
class dog{
public:
int d_Age;
dog(){
cout <<"dog的默认构造函数" << endl;
}
dog(int age){
d_Age = age;
cout <<"dog的有参构造函数" << endl;
}
~dog(){
cout <<"dog的析构函数" << endl;
}
};
int main(){
dog dog1(5); //调用有参构造函数
cout << "dog1的年龄为:" << dog1.d_Age << endl;
dog dog2(dog1); //调用默认拷贝构造函数
cout << "dog2的年龄为:" << dog2.d_Age << endl;
}
由于这个时候我们没有自定义拷贝构造函数,所以编译器调用了默认的拷贝构造函数,也就是完成了浅拷贝,将dog1的年龄拷贝给了dog2。函数执行结果:
dog的有参构造函数
dog1的年龄为:5
dog2的年龄为:5
dog的析构函数
dog的析构函数
此时我们增加dog大小的变量d_Size,我们将变量定义在堆区,堆区的变量需要我们手动释放,所以在析构函数中我们需要将堆区开辟的数据释放。运行下方程序
#include <iostream>
using namespace std;
class dog{
public:
int d_Age;
int * d_Size;
dog(){
cout <<"dog的默认构造函数" << endl;
}
dog(int age,int sizze){
d_Age = age;
d_Size = new int(sizze); //返回的是一个指针
cout <<"dog的有参构造函数" << endl;
}
~dog(){
if(d_Size != NULL){ //释放堆区数据
delete d_Size;
d_Size = NULL;
}
cout <<"dog的析构函数" << endl;
}
};
int main(){
dog dog1(5,40); //调用有参构造函数
cout << "dog1的年龄为:" << dog1.d_Age <<"大小为:" << *dog1.d_Size << endl;
dog dog2(dog1); //调用默认拷贝构造函数
cout << "dog2的年龄为:" << dog2.d_Age <<"大小为:" << *dog2.d_Size << endl;
}
这时候程序崩了。。。
为什么会这样子呢?我们刚才创建了两个对象,dog1和dog1的复制品dog2,如果利用编译器提供的拷贝构造函数,会做浅拷贝操作。也就是把dog1的的d_Age和dog1的d_Size复制给dog2,这里d_Size是我们在堆区用new创建的一个变量,所以保存的是一个地址。浅拷贝会直接将dog1中的d_Age(保存的是5)和d_Size(保存的是变量的地址)中的值复制给dog2,所以dog2中的d_Age和d_Size保存的也就是5和变量的地址。当调用析构函数的时候,dog1和dog2都会执行析构函数,由于dog1和dog2是保存在栈中,先进后出,所以dog2先调用析构函数,先将d_Size中的地址对应的堆区中的内存释放,然后dog1再执行析构函数中释放内存操作,由于同一块地址的内存已经被释放过了,所以程序就崩了。如下图
浅拷贝带来的问题就是堆区的内存重复释放。 浅拷贝的问题,需要利用深拷贝进行解决。由于编译器提供的浅拷贝不能满足要求,我们要自己实现一个拷贝构造函数,来解决浅拷贝带来的问题。
#include <iostream>
using namespace std;
class dog{
public:
int d_Age;
int * d_Size;
dog(){
cout <<"dog的默认构造函数" << endl;
}
dog(int age,int dsize){
d_Age = age;
d_Size = new int(dsize); //返回的是一个指针
cout <<"dog的有参构造函数" << endl;
}
dog(const dog &p){
cout << "dog的拷贝构造函数"<< endl;
d_Age = p.d_Age;
d_Size = new int(*p.d_Size);
}
~dog(){
if(d_Size != NULL){ //释放堆区数据
delete d_Size;
d_Size = NULL;
}
cout <<"dog的析构函数" << endl;
}
};
int main(){
dog dog1(5,40); //调用有参构造函数
cout << "dog1的年龄为:" << dog1.d_Age <<"大小为:" << *dog1.d_Size << endl;
dog dog2(dog1); //调用拷贝构造函数
cout << "dog2的年龄为:" << dog2.d_Age <<"大小为:" << *dog2.d_Size << endl;
}
这次程序没有崩掉,完美运行~ 由于我们在拷贝构造函数中重新开辟了堆区的内存,则变量结构如下
dog的有参构造函数
dog1的年龄为:5大小为:40
dog的拷贝构造函数
dog2的年龄为:5大小为:40
dog的析构函数
dog的析构函数
总结:浅拷贝就是编译器实现的直接等号复制操作,深拷贝重新申请一块空间,进行拷贝操作,所以如果对象中有属性在堆区中开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。