拷贝构造
本章思维导图:
注:本章思维导图对应的xmind
文件和.png
文件都已同步上传到”资源“
如果我们想要用一个已经存在的对象实例化一个与之完全相同的对象,怎么做呢?
C++提供了一个简单的方法——拷贝构造
拷贝构造是C++类里面默认成员函数之一
1.1 拷贝构造的声明
- 拷贝函数是构造函数的重载
- 拷贝函数没有返回值
- 只有一个形参,该形参必须是类类型的引用,且最好由const修饰
例如,对于Date
类的拷贝构造,应该声明为:
Date(const Date& d);
如果形参不是引用类型,那么就会报错:
“Date”: 非法的复制构造函数: 第一个参数不应是“Date”
我们来进行分析:
所以我们才说:形参必须是类类型的引用
1.2 拷贝构造的定义
要知道如何正确的定义拷贝构造,就需要先对浅拷贝和深拷贝有简单的了解:
1.2.1 浅拷贝
- 浅拷贝又称值拷贝
- 浅拷贝只是对成员变量值的简单复制,而不是复制指向的动态分配的资源(如堆内存)
- 原对象和拷贝对象将共享相同的资源。
例如,对于Date
类的拷贝构造:
Date(int year = 1, int month = 1, int day = 1) { //只是对成员变量值的简单复制 _year = year; _month = month; _day = day; }
而如果我们将Stack
类的拷贝构造写成浅拷贝:
Stack(const Stack& st) { _a = st._a; _capacity = st._capacity; _top = st._top; }
那么代码 _a = st._a
就会带来极大的危险:
- 这是对两个指针进行简单的值拷贝,即让这两个指针的值相同,即指向相同的内容。如图:
- 那么,如果在这种情况下**,调用析构函数就会出现错误**:
~Stack() { free(_a); _capacity = _top = 0; } int main() { Stack st1; Stack st2(st1); return 0; }
- 这是因为,
_a
和st._a
这两个指针都指向了相同的区域,当对象st1
调用析构函数后,其指向的空间就被销毁了,成了野指针,当st2
再次调用析构函数时,就会造成对野指针的销毁,而free
是不能对野指针进行操作的,因此会报错。
因此,当涉及到指针类型的拷贝时,仅靠浅拷贝是不够的,这就我们的需要深拷贝了。
1.2.2 深拷贝
- 深拷贝又称址拷贝
- 相较于浅拷贝只是对成员变量值的简单赋值,深拷贝会复制对象的成员变量以及指向的资源,包括指针指向的数据
- 这确保了原对象和拷贝对象拥有彼此独立但内容相同的资源副本。
例如,Stack
类的拷贝构造的正确写法为:
Stack(const Stack& st) { //址拷贝 _a = (int*)malloc(sizeof(int) * st._capacity); //开辟相同大小的空间 memcpy(_a, st._a, st._capacity * sizeof(int)); //使新空间的内容和原来的一样 _capacity = st._capacity; _top = st._top; }
可以通过调试来检验正确性:
可以看到st1._a
和st2._a
虽然内容相同,但地址不同,说明结果正确。
因此,当定义拷贝构造时,就必须考虑一个问题:
- 拷贝时涉及资源空间的拷贝(对指针指向空间的拷贝),就一定要用深拷贝(址拷贝)
- 如果只是简单的复制操作,那就进行浅拷贝(值拷贝)
1.3 默认拷贝构造
因为拷贝构造是C++类里面的默认成员函数,因此,如果用户不自定义拷贝构造,系统就会自动生成调用
那么,系统自动生成的是浅拷贝还是深拷贝呢?
让我们用Stack
类一试便知:
可以看到,st1._a
和st2._a
不仅内容相同,且地址相同。显而易见:
系统默认生成的拷贝构造是浅拷贝
- 对于内置类型,只进行赋值操作
- 对于自定义类型,调用它的拷贝构造
1.4 拷贝构造的必要性
必须自定义拷贝构造的情况:
- 如
Stack
类- 当涉及到对指针指向空间资源的拷贝时,必须自定义拷贝构造,形成深拷贝。使新对象指针指向的空间和原空间独立且相同
- 如果不用深拷贝,就会造成
free
野指针的情况,使程序崩溃
可以不自定义拷贝构造的情况:
- 如
Date
类- 如果只是对成员变量值的简单复制,那么用浅拷贝即可。可以不用自定义拷贝构造,直接用系统自动生成的即可。
- 如果类的成员变量都是自定义类型,那也可以直接用自动生成的拷贝构造
下一章,我们将讲C++类里面运算符重载等相关知识。
如果觉得本篇对你有所帮助或者对以后的内容感兴趣,不妨关注专栏👉C++教程