一、概述
拷贝构造函数特征:第一个参数是自身类类型的引用,且任何额外参数都有默认值
class Foo { public: Foo(); // 默认构造函数 Foo(const Foo &); // 拷贝构造函数 //... };
注意:
如果没有为一个类定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数,默认的拷贝构造函会依次将非static成员拷贝到正在创建的对象中(对于基本类型的成员变量,按字节复制;对于类类型成员变量,调用其相应类型的拷贝构造函数);
但是,当你的类含有指针类型的私有数据成员时,默认拷贝构造函数是危险的,因为它使两个对象的指针都指向了同一块内存区域,这时便是浅拷贝了。
二、什么时候会触发拷贝构造函数
1.使用同类型的对象 去创建另外一个对象;
string nines = string(100,'9');
2.把对象传入函数的非引用参数时;
void Function(string s) //调用这个函数时,实参传到这个形参s时,会触发拷贝构造 { // ... }
3.把对象作为函数非引用返回值时。
string s = "hello" string Function() { // ... return s; // 触发拷贝构造 }
三、浅拷贝
浅拷贝小故事:
张三有 100 元现金和一个房产证,房产证上写着房子的地址“魔都1巷007号”;
某天,张三就想把自己的东西复制一份给李四;
于是就复制了 100 元和一个房产证给李四,房产证上的地址还是写着“魔都1巷007号”。
后来,张三把房子卖了,李四某天也去卖房子的时候就奔溃了。
浅拷贝
就是在拷贝对象时,只是按字节地拷贝了类成员的值,对于指针或引用类型,没有拷贝其指向地具体数据过去;
就像上面故事中,只拷贝了100元和房产证,而两份房产证都是指向同一份数据(“魔都1巷007号”)。
用代码的形式表示:
#include <cstdio> #include <cstring> #define HOUSE_LEN 64 class Person { int money; // 钱 char *house; // 房子 public: Person(int _money, char *_house) { money = _money; house = _house; } void Show() { printf("I have %d Yuan,and house %s\n",money, house); } void SellHouse()// 卖房子 { delete[] house; } }; int main() { char *pHouse = new char[HOUSE_LEN]; strncpy(pHouse, "魔都1巷007号", HOUSE_LEN); Person ZhangSan(100, pHouse); printf("I am ZhangSan\n"); ZhangSan.Show(); Person LiSi = ZhangSan; printf("I am LiSi\n"); LiSi.Show(); ZhangSan.SellHouse(); printf("ZhangSan SellHouse\n"); //LiSi.SellHouse(); // 执行这一句会使程序报错,double free //printf("LiSi SellHouse\n"); return 0; }
浅拷贝的隐患
多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏。
四、深拷贝
深拷贝小故事:
张三有 100 元现金和一个房产证,房产证上写着房子的地址“魔都1巷007号”;
某天,张三就想把自己的东西复制一份给李四;
于是就复制了 100 元和一个房产证给李四,并且新建了一座房子“魔都1巷007号-2”,把李四的房产证上地址改为“魔都1巷007号-2”。
深拷贝
就是在拷贝对象时,对于指针或引用类型的成员,需要申请新的内存并把源对象成员指向的数据一起拷贝到新的内存。
就像上面故事中,拷贝了100元和房产证,并且新建了“魔都1巷007号-2”,把李四的房产证地址也改为“魔都1巷007号-2”。
用代码的形式表示:
#include <cstdio> #include <cstring> #define HOUSE_LEN 64 class Person { int money; // 钱 char *house; // 房子 public: Person(int _money, char *_house) { money = _money; house = _house; } Person(const Person &person) { money = person.money; house = new char[HOUSE_LEN]; snprintf(house,HOUSE_LEN, "%s-2",person.house); } void Show() { printf("I have %d Yuan,and house %s\n",money, house); } void SellHouse()// 卖房子 { delete[] house; } }; int main() { char *pHouse = new char[HOUSE_LEN]; strncpy(pHouse, "魔都1巷007号", HOUSE_LEN); Person ZhangSan(100, pHouse); printf("I am ZhangSan\n"); ZhangSan.Show(); Person LiSi = ZhangSan; printf("I am LiSi\n"); LiSi.Show(); ZhangSan.SellHouse(); printf("ZhangSan SellHouse\n"); LiSi.SellHouse(); // 因为有了拷贝构造函数做了深拷贝,这里不会报错 printf("LiSi SellHouse\n"); return 0; }
五、总结
在设计一个类的过程中,当这个类有指针或引用类型的成员时,且可能会触发调用拷贝构造函数,就需要自己定义一个拷贝构造函数,避免编译器的默认的拷贝构造函数引起浅拷贝而导致程序面临危险。
参考资料
《C++Primer》
如果对你有帮助的话,记得点赞、收藏,如果有什么遗漏的或者什么体会,请在评论告诉我,好东西记得分享 ^ _ ^