[本节内容]
1.拷贝
2.浅拷贝的认识
3.深拷贝的认识
4.浅拷贝与深拷贝的比较
1.拷贝
拷贝就是我们平时说的复制,就如同“克隆”,用一个已有的对象快速地复制出多个完全相同的对象。
一般我们在以下三种情况下会用到对象的复制:
1.拷贝构造函数:用一个已有的对象去创建同类的新对象并对它初始化
2.对象作为函数的参数进行调用,这时调用此函数时使用的是值传递,也会产生对象的复制
3.函数的返回值是类的对象,在函数调用结束时,需要将函数中的对象复制一个临时对象并传给改函数的调用处
2.浅拷贝
浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的值拷贝(改变指针使它指向要拷贝的值),之前讲到的默认拷贝构造函数执行的就是浅拷贝。大多数情况下只需要“浅拷贝”就能完成所需工作。
但是若对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码:
class A
{
public:
A() // 构造函数,p主动要求分配空间
{
p = new int(10);
}
~A() // 析构函数,释放动态分配的空间
{
cout << "~A()" << endl;
delete p;
}
private:
int w;
int h;
int *p; // 指针成员
};
int main()
{
A a1;
A a2(a1); // 调用默认拷贝构造函数,创建新对象
return 0;
}
运行结果:
由运行结果可知,程序在这里崩溃了,原因在于类A的析构函数调用了两次。我们来简单分析一下:
这里,由于创建a1对象时调用的构造函数中有一个动态分配的语句,所以它在堆上有自己独立的一块空间。但是创建a2对象时调用的是默认拷贝构造函数中并没有动态开辟空间的语句,而是直接进行值拷贝,将自己对象的指针指向a1之前开辟的空间,此时两个对象就共用了堆内的同一块空间。
他们在使用时没有什么错误,但是在使用后的销毁过程中,a1先调用自己的析构函数释放掉了自己开辟的空间,当a2再调用析构函数时也要释放空间但却找不到这块空间,因此程序发生崩溃现象。
在这里,我们期望的是a1,a2所指向的两个空间有相同的值,它们能执行自己的析构函数,而不是简单的让a1,a2有相同的值,所以我们需要深拷贝来防止默认拷贝构造函数的发生。
3.深拷贝
在“深拷贝”的情况下,对于对象中动态成员,就不是仅仅简单地赋值了,而是要重新动态分配空间后再进行值的拷贝。如上面的例子按照如下的方式进行深拷贝处理——自己写一个拷贝构造函数:
class A
{
public:
A() // 构造函数,p指向堆中分配的一空间
{
p = new int(10);
}
~A() // 析构函数,释放动态分配的空间
{
cout << "~A()" << endl;
delete p;
}
A(const A &a)
{
w=a.w;
h = a.h;
p = new int(10);
*p = *(a.p);
}
private:
int w;
int h;
int *p; // 指针成员
};
int main()
{
A a1;
A a2(a1); // 复制对象
system("pause");
return 0;
}
此时对象完成拷贝复制的情况大概如下;
由图可知,此时a1,a2各有一块空间,且空间内的内容相同,这时无论使用还是销毁,都不会再出现程序因内存崩溃的问题。这就是所谓的深拷贝。所以一般来说深拷贝比浅拷贝的代价更大一些。