如有兴趣了解更多请关注我的个人博客https://07xiaohei.com/
(一)引言:
对于普通类型,复制是极其简单的:
int a=1;
int b=a;//复制
int *p1=&a;
int *p2=p1;//复制
但对于内部含有多个数据成员的类对象,其结构复杂,复制也相对复杂。
这时就需要调用复制构造函数来完成拷贝过程。
(二)概念:
复制构造函数又名拷贝构造函数,是一种特殊的构造函数,它通过编译器调用完成基于同一类的其他对象的构造及初始化。
其形参是唯一的,且只能是本类的引用,一般情况下会在前面加const引用来保证被复制的类对象在复制过程中不被修改。
- 如果形参是值传递,将会发生无穷递归,即被复制的类对象需要先复制出一个临时对象传入复制构造函数,然后临时对象又需要调用复制构造函数产生新的临时对象…
- 如果形参是指针传递,能够实现要求,但是会导致歧义。
在类中如果没有显式给出复制构造函数时,则编译器自动给出一个默认复制构造函数。如果有自己定义的构造函数(包括复制构造函数),则按函数重载的规律,调用合适的构造函数。
(三)调用时机:
-
对象以值传递的方式传入函数参数。
#include <iostream> using namespace std; class Time { private: int hour; int minute; int sec; public: Time() { hour = minute = sec = 0; } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {} Time(const Time& t) { hour = t.hour; minute = t.minute; sec = t.sec; cout << "复制构造函数" << endl; }//复制构造函数 void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; void test(Time t) { return; } int main() { Time t1(4, 5, 6); test(t1);//发生复制 t1.show_time(); return 0; }
调用test()时,会进行以下步骤:
- Time对象t1传入test形参时,产生临时对象temp。
- 调用复制构造函数将t1的值赋值给temp。即有Time temp(t1)发生。
- 在test函数本身执行完毕后,将temp析构掉
- temp本质上是test的局部变量,生命周期和test相同。
-
对象以值传递的方式从函数返回。
#include <iostream> using namespace std; class Time { private: int hour; int minute; int sec; public: Time() { hour = minute = sec = 0; } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {} Time(const Time& t) { hour = t.hour; minute = t.minute; sec = t.sec; cout << "复制构造函数" << endl; }//复制构造函数 void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; Time test(int i1,int i2,int i3) { Time t(i1, i2, i3); return t;//发生复制 } int main() { Time t1(4, 5, 6); t1 = test(10, 9, 8); t1.show_time(); return 0; }
调用test至return时,会进行以下步骤:
- 在return处产生临时对象temp。
- 调用复制构造函数将test内创建的局部变量t的值赋值给temp。即有Time temp(t)发生。
- 在test函数本身执行到最后,将t析构掉
- 在test()执行完毕后(此时赋给t1已经结束了),析构temp(因其为临时对象)。
-
对象需要通过另外一个对象进行初始化。
#include <iostream> using namespace std; class Time { private: int hour; int minute; int sec; public: Time() { hour = minute = sec = 0; } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {} Time(const Time& t) { hour = t.hour; minute = t.minute; sec = t.sec; cout << "复制构造函数" << endl; }//复制构造函数 void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; int main() { Time t1(4, 5, 6); Time t2 = t1;//发生复制 t1.show_time(); return 0; }
-
当成员变量为类类型时。
#include <iostream> using namespace std; class year { private: int years; public: year() { years = 0; } year(const year& y) { cout << "year复制构造函数" << endl; }//year的复制构造函数 }; class Time { private: int hour; int minute; int sec; year y; public: Time() { hour = minute = sec = 0; } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {} void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; int main() { Time t1(4, 5, 6); Time t2 = t1;//发生复制,且调用的是成员类的复制构造函数。 t1.show_time(); return 0; }
因为Time的复制构造函数不存在,且其成员类year中存在复制构造函数,所以,此时编译器合成一个Time类的复制构造函数,但是这个合成的复制构造函数的目的是向其中插入能够去调用类year的复制构造函数的代码。
如需要两个被同时调用,需要自己构造Time的复制构造函数,并且对成员类进行显式赋值。
代码如下:
#include <iostream> using namespace std; class year { private: int years; public: year() { years = 0; } year(const year& y) {cout << "year复制构造函数" << endl;}//year复制构造函数 }; class Time { private: int hour; int minute; int sec; year y; public: Time() { hour = minute = sec = 0; } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) {} Time(const Time& t):y(t.y) { hour = t.hour; minute = t.minute; sec = t.sec; cout << "复制构造函数" << endl; }//复制构造函数 void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; int main() { Time t1(4, 5, 6); Time t2 = t1;//发生复制,且调用了各自的复制构造函数 t1.show_time(); return 0; }
(四)浅拷贝和深拷贝:
-
默认拷贝构造函数:
当我们自己未定义任何复制构造函数时,编译器会为我们自动生成一个默认拷贝构造函数,以保证需要复制对象时不会产生问题。
但是此函数是使用被复制对象的值对复制对象的数据成员进行赋值,不会复制静态数据成员(实际上不会进行任何操作),需要手动添加。
-
浅拷贝:
浅拷贝是指对象复制只对对象中的数据成员进行简单赋值(默认构造函数即为浅拷贝)。
浅拷贝的对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 。如果其中一个对象改变了这个地址,就会影响到另一个对象。
浅拷贝中,一旦存在动态成员,将会产生大问题:
#include <iostream> using namespace std; class Time { private: int hour; int minute; int sec; int* p; public: Time() { hour = minute = sec = 0; p = new int(10); } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(5); } ~Time() { delete p; } void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; int main() { Time t1(4, 5, 6); Time t2(t1); t2.show_time(); return 0; }
运行此代码将会发生p指针指向的动态数组发生了多次释放。
分析此过程:
因为浅拷贝是将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用。所以,两个不同对象的动态数组指针p地址值其实是相同的,它们指向的是同一个空间。
在销毁对象时,两个对象的析构函数对同一空间进行了两次释放,进而产生了指针悬挂错误。
我们所需要的是两个动态指针p指向两个不同空间,但是两个空间具有相同的值,而不是两个动态指针p指向同一空间。
此时需要进行深拷贝。
-
深拷贝:
深拷贝是指将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
深拷贝创建一个新的对象和数组,重新动态分配空间,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,拷贝的是“值”而不是“引用”。
代码如下:
#include <iostream> using namespace std; class Time { private: int hour; int minute; int sec; int* p; public: Time() { hour = minute = sec = 0; p = new int(10); } Time(int i1, int i2, int i3) :hour(i1), minute(i2), sec(i3) { p = new int(10); } Time(const Time& t) { hour = t.hour; minute = t.minute; sec = t.sec; p=new int(*t.p); cout << "复制构造函数" << endl; }//复制构造函数 ~Time() { if (p != NULL) { cout << "析构"<<&p << endl; delete p; p = NULL; } } void set_time() { cin >> hour >> minute >> sec; return; } void show_time() { cout << hour << ":" << minute << ":" << sec << endl; } }; int main() { Time t1(4, 5, 6); Time t2(t1);//发生复制 t2.show_time(); return 0; }
可以看到两次析构函数析构前的动态指针p指向地址不同,说明两个动态指针p各自指向不同的内存空间,且指向的内容相同,此时即有深拷贝。
-
防止浅拷贝发生:
声明一个私有复制构造函数,可以不定义,此时如果按值传递或或函数返回该类对象,不会发生浅拷贝错误,可以避免问题。