类的默认成员函数3 --- 拷贝构造

拷贝构造

前面我们已经了解到了构造函数和析构函数, 这里介绍拷贝构造

1. 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。那在创建对象时,可否创建一个与一个对象一某一样的新对象呢?

  1. 构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型的对象创建新对象时由编译器自动调用

  2. 拷贝构造 (属于构造函数的一种重载形式)
    创建一个和原有已经存在的对象完全一样的新对象,所以参数是一个对象。

2. 特征

  1. 拷贝构造函数也是特殊的成员函数,其特征如下:

    1. 当构造函数的参数是一个对象时就是拷贝构造
    2. 没有返回值, 函数名也和类名相同
    3. 参数类型为引用,不能是值
    4. 属于构造函数的一种重载形式
    5. 如果不显示定义,编译器会自动生成一个拷贝构造, 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,即浅拷贝
  2. Data(Data date){} 这种传值形式,首先编译器不会通过, 而且会造成无限递归而导致程序崩掉,因为在调拷贝构造时会有传值的过程, 而传值其实就是一个拷贝的过程,所以所当我们去调用拷贝构造函数体时,首先会进行值拷贝,也就是在传参时还需要再调一次拷贝构造,
    Data(Data& date){} 参数类型必须为引用, 不能是值(且不会通过, 引发无穷递归)
    Data(const Data& date){} 加了const 有个好处, 就是不管是匿名对象还是普通对象他都可以接收, 且由于匿名对象具有常性, 也只能用const接收, 他的初始化 要用对象加.来实现, _year = d._year;

3.拷贝构造的使用

class Date{
public:
 	 Date(int year = 1, int month = 1, int day = 1){
		 _year = year;
		 _month = month;
		 _day = day;
	 }
	 Date(const Date& date){// 实参传进来 内容是不变的, 用const修饰,
	 // 既可接收普通对象也可接收匿名对象
	 // 加const的好处是也可以接收匿名对象,匿名对象是临时的,具有常性,只能用const接收
	 
		 _year = date._year;
		 _month = date._month;
		 _day = date._day;
	 }
private:
	 int _year;
	 int _month;
	 int _day;
};

int main(){
	 Date d1;
	 Date d2(d1);// 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
	 return 0; 
}

传值的拷贝构造:

// 若为传值的拷贝构造
Date(const Date date){ // 由于成员函数的第一个参数地this指针,
// 则相当拷贝构造函数是Date(const Date* const this, Date date)

}

Date copy(d);  // 而传参时,其实就是Date(&copy, d), this传递就是copy的地址,把d传给date
// 由于是值拷贝,就要调一次拷贝构造,

这是整个拷贝传值的过程:
在这里插入图片描述
因为把 d 传给 date 是传值,也叫值传递, 而值传递不是本身(不是引用),必须要进行值拷贝来传递, 且值拷贝需要调拷贝函数,而调了拷贝构造函数时, 由于调拷贝构造函数需要传参, 所以不得不又得发生值拷贝,然后又继续调拷贝构造, 才导致无穷递归调用。 所以拷贝构造不能传值, 得传本身 --> 即引用。

void test(){
	Date d(2020, 5, 5);
	Date copy(d);  //拷贝构造
	Date copy2 = d;  //也会调拷贝构造, 对于copy2本身不存在 通过d去创造一个新的对象 
	// 也为拷贝构造  d = copy = copy2
}
  1. 如果不显示定义, 编译器会自动生成一个拷贝构造(完成的是字节拷贝(也叫内容浅拷贝), 对象模型多大 就拷多大),d = copy = copy2, 但是需要拷贝资源(深拷贝) 时, 则需要自己定义拷贝构造
    下面我们给一个顺序表:
class seqList{
public:
	seqList(int n = 10){
		_array = (int*)malloc(sizeof(int)* n); // 开空间
		_size = 0;
		_capacity = n;
	}
	// 析构函数
	~seqList(){
		if (_array)
			free(_array);
		_size = 0;
		_capacity = 0;
		_array = nullptr;
		cout << "~seqList()" << endl;
	}
private: // 这为顺序表的对象模型. 但是资源并不在这里
	int* _array; // 顺序表中会定义指针  4个字节
	int _size;// 大小                 4个字节
	int _capacity;// 容量             4个字节  
	// 总共12个字节, 默认拷贝构造函数实现的就是12个字节的拷贝
}
void test(){
	seqList lst; // 构造函数
	seqList copy(lst);  // 调拷贝构造
}

不显式定义拷贝构造的前提下, 实现的是字节拷贝, 此时经调试发现 lst 和 copy 两个对象的_array指针指向同一个地址, 说明两个对象共享同一片资源, 在函数退出时需要调 2 次析构函数, 导致释放 2 次空间, 程序崩掉.

  • 事实证明: 浅拷贝并没有把资源拷过去, 只拷贝了成员变量的内容, 而资源并不在顺序表的对象模型中; 指针 _array 实际指向的才是资源 ; 浅拷贝只拷贝了对象模型的内容(成员变量的值), 并不拷贝资源.

小题:
以下代码共调用多少次拷贝构造函数?

Widget f(Widget u){  
  Widget v(u);
  Widget w=v;
  return w;
}

int main(){
  Widget x;
  Widget y=f(f(x));
}

调用拷贝构造函数的时机,从代码上看会有三种情形,1.对象初始化对象 2.函数参数以对象传递 3.函数的返回值以对 象返回,因此乍眼一看,超过1次
整个程序分别在,x初始化u、u初始化v、v初始化w、w返回时,注意w返回时初始u不在调用拷贝构造函数,第二次调用 f() 函数时,相当于u的结合会少调用一次,其他不变,所以总体次数为4+3 = 7次

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值