目的:为了初始化新对象,同构造函数
特点:
- 用户未定义,编译器会自动生成默认拷贝构造;
- 拷贝构造函数参数只有一个,参数类型必须是:本类类型对象的引用,(防止引用更改原对象的成员变量)用const修饰:
const Date& d
- 由编译器自动调用;
- 编译器自动生成的拷贝构造函数,将原对象所有显式的内容进行拷贝,放在新的对象空间中(新对象与原对象共用同一块内存空间);
重点:使用拷贝构造函数的三种应用场景
- 用已存在的类类型对象创建新对象
- 函数参数为类类型
- 函数返回值为类类型
- 初始化对象的方式:用已经存在的对象创建新对象,并初始化为相同值
- 要重点区分拷贝构造对象时,使用=,是调用运算符重载函数还是拷贝构造
- 就看被赋值的对象是否之前已经存在了,不存在就要按拷贝构造函数走
拷贝构造函数根据类中是否涉及到资源管理分成两种(与析构函数类似):
内置类型成员,进行值拷贝(又叫浅拷贝)
自定义类型成员,调用自定义类型类中的拷贝构造
- 值拷贝:指将一个变量的值赋给另一个变量时,
将源变量的值完全复制一份给目标变量,而不是两个变量共享同一个内存地址。
这意味着如果源变量的值发生改变,目标变量的值不会受到影响- 例子:
如果源变量pa是指针类型,其值存储的是动态开辟空间的首地址
值拷贝就是将pa存储的值拷贝给pa’,所以两个指针指向同一块内存空间,析构的时候就会报错- 当类中包含数组成员时,拷贝构造函数会按照值拷贝的方式将整个数组拷贝过来,而不是仅拷贝数组首元素的地址
因为数组是一个连续的内存块,数组名本身是指向数组首元素的指针,因此在进行值拷贝时,需要将整个内存块中的数据都复制到新对象的相应位置。
普通拷贝构造函数的函数体:
- 拷贝构造的参数是类类型对象的引用,那么形参d就是实参d1的引用;
- 将d对象的年月日的值赋给当前对象(当前对象就是调用拷贝构造的那个对象,就是d2)
参数使用const的原因:
- 防止在函数体内部通过修改原对象的引用,修改了原对象
参数是引用的原因:会陷入无限递归
- d1拷贝构造d2,先传参,再调用拷贝构造
传参方式是传值,所以需要在传参过程中生成一份实参的拷贝设为d1’,
要用d1去初始化d1’,d1’去初始化d2- d1’原先不存在是传参期间用d1拷贝构造出来的;所以就需要调又用拷贝构造,这次调用目的是为了创建d1’;
- 创建d1’就又需要调用拷贝构造函数,从而陷入无限递归
可以调用默认拷贝构造函数的原因
- d1对象中有三个成员变量(年月日),在往d2中拷贝时,逐字节拷贝这些数据,数据是值,不涉及资源管理
对于下列情况则用户必须提供不同于编译器自己生成的拷贝构造函数:
代码运行会在free处报错
s2在创建的时候,拷贝构造函数将s1所有属性通通复制给s2,包括s1变量空间地址与s2都相同,所以新旧对象在底层拥有同一份资源,两个对象销毁时,会将该份资源释放两次引起代码崩溃
调用拷贝构造函数的是为了拷贝原对象的数值,而不是对象中存储数据的地址;但是默认的拷贝构造函数却全部将原对象内容拷贝。
不能调用默认拷贝构造函数的原因:
- 按字节拷贝,将d1中的数据原封不动的搬移,这种就是浅拷贝;
- String对象中有一个成员变量,在往s2中拷贝时,逐字节拷贝这些数据,数据是char* 类型的指针(该指针指向动态开辟的空间);
- 所以该对象只占4个字节,拷贝的时候会逐字节拷贝该指针变量保存的地址;对该空间重复释放就会出错
以上是用已存在的类类型对象创建新对象的应用场景
void test (Date d);
{}
int main()
{
Date d1(2015, 1, 1);
test(d1);
return 0;
}
参数为类类型对象,就类似于C语言的传值:在传参过程中生成一份实参的拷贝;而要生成就需要调用拷贝构造函数
Date test (const Date& d);
{
return d;
}
int main()
{
Date d1(2015, 1, 1);
Date d=test(d1);
return 0;
}
- 参数是引用类型,所以在传参过程中就不会生成实参的临时拷贝;
- 函数返回方式是以值的方式返回,在返回时需要创建临时对象;
函数返回的时候不能直接返回对象d,因为d是函数中的一个局部对象(在函数参数处创建);
函数结束后,该局部对象是要被销毁,因此必须要构造一个新的临时对象用来返回,而新的临时对象本来没有现在要通过d构造出来,那就是通过已经存在的d构造一个新的对象;
所以需要调用拷贝构造函数
返回值的类型: