C++基础精讲篇第7讲:类中拷贝构造函数特性详解

C++基础精讲篇第6讲:类中构造函数和析构函数特性详解_King_lm_Guard的博客-CSDN博客https://blog.csdn.net/King_lm_Guard/article/details/126043552        本讲内容基于上一讲中分析的C++类中6个默认的成员函数,在上一讲中详细分析了构造函数和析构函数,这一讲博主接着分析拷贝构造函数相关特性。

目录

1、拷贝构造函数基本概念

2、特性分析

3、调用场景特性分析

4、总结


1、拷贝构造函数基本概念

通俗讲解:就是利用已经创建好的对象d1来创建新的对象d2。对象d2与对象d1特性一样。

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

2、特性分析

拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。所以可利用引用终止这种递归状态。下面通过代码具体给大家展开分析:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
class Date
{
public:
	//构造函数
	Date(int year = 2022, int month = 7, int day = 29)
	{
		cout << "构造函数:>" << endl;
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数,正确演示
	//Date(const Date& d)
	//{
	//	cout << "拷贝构造函数:>" << endl;
	//	this->_year = d._year;
	//	this->_month = d._month;
	//	this->_day = d._day;
	//}

	//拷贝构造函数,递归演示
	Date(const Date d)
	{
		cout << "拷贝构造函数:>" << endl;
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

错误演示:无穷递归

         调用拷贝构造函数需要先传参,而在这里演示的传值传参就是一个拷贝构造,所以会发生无穷递归。

正确演示:利用引用传参

补充:如果要实现上面分析拷贝,除了使用引用,原则上可以使用指针实现拷贝,如下代码所示,但我们会发现采用指针,此时在函数Date中的形参类型是Date*,这不符合拷贝构造函数的特性,这是需要读者们注意的。

	//指针演示拷贝
	Date( const Date* d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

3、若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。也就是说对内置类型就是考虑浅拷贝或值拷贝。

        如上图案例所示:用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数,但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数。

补充:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接值拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。


4、针对日期类这样的类,就没必要自己显示实现拷贝构造函数了,如3中的例子所示,但如果是用户自定义类型,则需要用户显示实现,如果不手动显示实现,还是按照编译器默认的浅拷贝,则会出现程序崩溃。下面举例说明:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
	//构造函数
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	//入栈
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	//析构函数
	~Stack()
	{
		if (_array) 
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

程序运行结果:

         上面的测试程序是创建数据结构中的栈采用C++写的,当我们按照编译器默认生成的拷贝构造函数(在这里也就是值拷贝或者浅拷贝),会发现程序崩了,这是为什么呢?难道不能采用将一个字节一个字节拷贝的方式吗?下面跟着我一起详细分析其原因:

 1、s1对象调用构造函数初始化时,默认申请了10个元素的空间,然后利用入栈函数,在里面放置了4个元素1 2 3 4 ;

2、s2对象使用s1拷贝构造,而在Stack类中没有显示定义拷贝构造函数,则编译器会给在Stack类生成一份默认的拷贝构造函数,默认拷贝构造函数是按照值拷贝的,也就是说将s1中内容原封不同的拷贝到s2中,因此s1和s2指向了同一块内存空间;

3、当程序退出时,s1和s2要销毁,根据上一讲分析的在栈区开辟的空间满足后构造先析构的特点,所以s2先销毁,s2销毁时调用析构函数,也就是将0x00cc6580的空间释放了,但此时s1并不知道该空间已经被销毁,所以当到s1销毁时,会将0x00cc6580空间再释放一次,当一块空间被多次释放,肯定会造成程序崩溃。

4、针对这种空间被多次释放的情况,需要用户手动实现深拷贝,但由于深拷贝内容复杂,所以博主计划在后面的章节中再为大家带来详细讲解。


注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

3、调用场景特性分析

先阅读下面的程序:

class Date
{
public:
	//构造函数
	Date(int year , int month , int day )
	{
		cout << "构造函数:>" <<this<< endl;
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数
	Date(const Date& d)
	{
		cout << "拷贝构造函数:>" << this<<endl;
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	//析构函数
	~Date()
	{
		cout << "析构函数:>" << this << endl;
	}

private:
	int _year;
	int _month;
	int _day;

};
//修改前,传值传参
Date Test(Date d)
{
	Date temp(d);
	return temp;
}
//修改后,传引用且引用做返回值
//Date& Test(Date& d)
//{
//	static Date temp(d);
//	return temp;
//}

int main()
{
	Date d1(2022,7,29);
	Test(d1);
	return 0;
}

拷贝构造函数典型调用场景:

1、使用已存在对象创建新对象;
2、函数参数类型为类类型对象;
3、函数返回值类型为类类型对象;

拷贝构造函数典型调用场景

 传值传参+传值做返回值:

传引用传参+引用做返回值:(最好在Date temp(d)前面加上static  原因在C++中引用介绍有详细讲解。)

 总结:从上面演示的程序代码中可以看出,使用传值传参,对空间的消耗很大,而且,需要多个拷贝,效率低下,因为为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

4、总结

        今天这一讲主要讲解了拷贝构造函数的用法及特性,但要注意,拷贝构造还有深拷贝部分需要深入学习,具体会在后面的学习过程中为大家带来详细讲解。欢迎点赞、关注、支持!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值