【C++杂货铺】拷贝构造函数

这篇具有很好参考价值的文章主要介绍了【C++杂货铺】拷贝构造函数。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

📖定义
拷贝构造函数是构造函数的一个重载,它的本质还是构造函数,那就意味着,只有在创建对象的时候,编译器才会自动调用它,那他和普通的构造函数有什么区别呢?

拷贝构造函数,是创建对象的时候,用一个已存在的对象,去初始化待创建的对象。简单来说,就是在我们创建对象的时候,希望创建出来的对象,和一个已存在的对象一模一样,此时就应该用拷贝构造函数,而不是普通的构造函数。拷贝构造函数有一点类似于克隆技术。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//会去调用拷贝构造函数

int a = 10;
int b = a;//不会调用拷贝构造

上面代码,首先定义了一个日期类对象d1,接着想创建第二个日期类对象d2,并且希望d2d1一模一样,也就是用d1去克隆出d2d2相当于是d1的一份拷贝。所以在创建d2对象的时候,参数列表直接传递了d1

小Tips:拷贝构造函数是针对自定义类型的,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数。内置类型不涉及拷贝构造函数,如上,用a去创建b,是由编译器直接把a所表示的空间中的内容直接拷贝到b所表示的空间,并不涉及拷贝构造函数。

📖拷贝构造函数的错误写法
有了上面的分析,可能很多朋友会觉得,那我直接在类里面再写一个构造函数,把它的形参设置成日期类对象,不就行了嘛,于是便得到了下面的代码:

Data(Data d)//错误的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

是不是觉得很简单?创建d2对象的时候,实参把d1传过来,然后用d接收,最后再把d的所有值赋值给this指针(当前this指针就指向d2),这一切堪称完美,但是我想告诉你,这种写法是大错特错的。

📖为什么是错的
问题出现在传参,就是实参d1传递给形参d的时候,上面代码中的形参d,既不是指针也不是引用,说明是值传递,值传递就意味着,形参d是实参d1的一份拷贝,注意:是拷贝,就是说,形参d要和实参d1一模一样,怎么才能让dd1一摸一样?调用拷贝构造函数呀。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,这次调用拷贝构造,又会有一个形参d,这个形参d又需要调用拷贝构造才能创建,相信到这里,小伙伴们已经看出问题所在了———无穷递归,形参在接收的时候,会无穷无尽的去调用拷贝构造函数,就像套娃一样。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数
为了避免出现这种无穷递归,编译器会自行检查,如果拷贝构造函数的形参是值传递,编译时会直接报错。

【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

📖必须是引用
为了打破上面的魔咒,拷贝构造函数的形参只能有一个,并且必须是类类型对象的引用。下面才是正确的拷贝构造函数:

Data(Data& d)//正确的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//

此时创建d2的时候,传递d1调用拷贝构造函数,形参d是一个日期类的引用,因为引用时区别名,意味着dd1的一个别名,此时就不会再去无穷无尽的调用拷贝构造啦。

📖建议加const
因为存在用一个const对象去初始化创建一个新对象这种场景,所以建议在拷贝构造函数的形参前面加上const,此时普通的对象能用,const对象也能用。

Data(const Data& d)//正确的拷贝构造
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
const Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);

📖编译器生成的拷贝构造干了什么?
上一节提到,拷贝构造是一种默认成员函数,我们不写编译器会自动生成。编译器生成的默认拷贝构造函数,对内置类型按照字节方式直接拷贝(也叫值拷贝浅拷贝),对自定义类型是调用其拷贝构造函数完成拷贝

class Time//定义时间类
{
public:
	Time()//普通构造函数
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)//拷贝构造函数
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private://成员变量
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数
📖什么是浅拷贝
上面提到,编译器生成的拷贝构造函数,会对内置类型完成浅拷贝,浅拷贝就是以字节的方式,把一个字节里的内容直接拷贝到另一个字节中。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

📖拷贝构造函数可以不写嘛?
通过上面的分析可以得出:编译器自己生成的构造函数对内置类型和自定义类型都做了处理。那是不是意味着我们就可以不写拷贝构造函数了呢?答案是否定的,对于日期类,我们确实可以不写,用编译器自己生成的,但是对于一些需要深拷贝的对象,构造函数是非写不可的。栈就是一个典型的需要我们自己写构造函数的例子

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;
}

上面定义了一个栈类Stack,我们没有写它的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,栈中的成员变量都是内置类型,默认的拷贝构造函数会对这三个成员变量都完成值拷贝(浅拷贝)。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数
此时浅拷贝的问题在于:对象s1和对象s2中的_array存的是同一块空间的地址,他俩指向了同一块空间,当程序退出,往s1s2中的任意一个对象push值,另一个也会跟着改变。s1s2要销毁,s2先销毁,s2销毁时调用析构函数,已经将0X11223344这块空间释放了,但是s1并不知道,到s1销毁的时候,会将0X11223344这块空间再释放一次,一块内存空间多次释放,最终就会导致程序崩溃。

📖深拷贝
通过上面的分析可以看出,简单的浅拷贝不能满足栈的需求,因此,对于栈,我们需要自己写一个拷贝构造函数,来实现深拷贝,深拷贝就是去堆上重新申请一块空间,把s1_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间。
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数

//自己写的拷贝构造函数,实现深拷贝
Stack(const Stack& st)
{
	DataType* tmp = (DataType*)malloc(sizeof(DataType) * st._capacity);
	if (nullptr == tmp)
	{
		perror("malloc申请空间失败");
		return;
	}
	memcpy(tmp, st._array, sizeof(DataType) * st._size);
	_array = tmp;
	_size = st._size;
	_capacity = st._capacity;
}

📖总结:
类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请时,拷贝构造函数是一定要写的,否则就是浅拷贝,最终析构的时候,就会释放多次,造成程序崩溃。

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

  • 使用已存在对象创建新对象。
  • 函数参数类型为类类型对象。
  • 函数返回值为类类型对象。
class Data
{
public:
	Data(int year = 1, int month = 1, int day = 1)
	{
		cout << "调用构造函数:" << this << endl;
		cout << endl;
		_year = year;
		_month = month;
		_day = day;
	}

	Data(const Data& d)
	{
		cout << "调用拷贝构造:" << this << endl;
		cout << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	~Data()
	{
		cout << "~Data()" << this << endl;
		cout << endl;
	}
private:
	int _year;
	int _month;
	int _day;

	//可以不用写析构,因为全是自定义类型,并且没有动态申请的空间,这三个成员变量会随着对象生命周期的结束而自动销毁
};
Data Text(Data x)
{
	Data tmp;
	return tmp;
}

int main()
{
	Data d1(2023, 4, 29);
	Text(d1);
	return 0;
}

【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数
📖总结:
自定义类型在传参的时候,形参最好用引用来接收,这样可以避免调用拷贝构造函数,尤其是深拷贝的时候,会大大的提高效率,函数返回时,如果返回的对象在函数栈帧销毁后还在,最好也用引用返回。


🎁结语:
 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
【C++杂货铺】拷贝构造函数,C++杂货铺,c++,开发语言,拷贝构造函数文章来源地址https://www.toymoban.com/news/detail-599611.html

到了这里,关于【C++杂货铺】拷贝构造函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++杂货铺】模板

    📖 实现一个通用的交换函数 想要实现一个通用的交换函数不难,借助函数重载就可以。函数重载小伙伴们还记得嘛👀,忘了的小伙伴可以走传送门回去复习一下。如上面代码所示,我们借助函数重载实现了三份 Swap 函数,分别用来交换两个整型变量、两个双精度浮点型变量

    2024年02月09日
    浏览(32)
  • 【C++杂货铺】引用

    前言:  相信大家在学习C语言的时候,最头疼的就是指针,经常会碰到一级指针、二级指针,这些指针使用起来,稍有不慎就会等导致程序崩溃,为了让广大程序员少掉点头发,C++中提出了 引用 这一概念。当然,在C++的代码中,仍然可以兼容C语言的指针。  在语法上 引用

    2024年02月16日
    浏览(29)
  • 【JAVA杂货铺】一文带你走进面向对象编程的构造方法 | Java| 面向对象编程 | (中)

    🌈个人主页:  Aileen_0v0 🔥系列专栏: Java学习系列专栏 💫个人格言: \\\"没有罗马,那就自己创造罗马~\\\"   目录 回顾  构造方法  this 面试题 构造方法的类型  下节预告 代码块  之前我们学习了什么是类  什么是对象  什么是面向对象编程 定义类   构造方法 :它的方法名

    2024年02月05日
    浏览(38)
  • 【C++杂货铺】内存管理

    从用途和存储的角度来看,在C/C++程序中有 局部数据、静态数据、全局数据、常量数据、动态申请的数据 五种主要的数据,各种数据的特点如下: 局部数据 :随用随创建,存储在栈区,作用域只在局部,生命周期在局部,出了作用域就销毁。 静态数据 :存储在数据段,作

    2024年02月16日
    浏览(26)
  • 【C++杂货铺】内管管理

    目录 🌈前言🌈 📁 C/C++中内存分布 📁 new 和 delete的使用 📁 new 和 delete的优点 📁 new 和 delete的原理  📂 operator new 和 operator delete函数  📂 内置类型  📂 自定义类型 📁 内存泄漏 📁 总结         欢迎收看本期【C++杂货铺】,本期内容讲解C++内存管理。包含了C++中内存

    2024年04月14日
    浏览(32)
  • 【C++杂货铺】详解string

    目录  🌈前言🌈 📁 为什么学习string 📁 认识string(了解) 📁 string的常用接口  📂 构造函数  📂 string类对象的容量操作  📂 string类对象的访问以及遍历操作​编辑  📂 string类对象的修改操作 📁 模拟实现string 📁 总结         欢迎观看本期【C++杂货铺】,本期内容

    2024年03月20日
    浏览(30)
  • 【JAVA杂货铺】一文带你走进面向对象编程|构造方法调用 | 代码块分类| 期末复习系列 | (中3)

    🌈个人主页:  Aileen_0v0 🔥系列专栏: Java学习系列专栏 💫个人格言:\\\" 没有罗马,那就自己创造罗马~\\\" 上次,我们学习了关于Java面向对象编程的 构造方法 ,以及 this 在构造方法/实例化对象中的使用,若有遗忘点击👉🔗 本节我们`来学习,代码块,tostring以及继承  那还等什么

    2024年02月04日
    浏览(37)
  • 【C++杂货铺】详解list容器

    目录 🌈前言🌈 📁 介绍 📁 使用  📂 构造  📂 迭代器iterator  📂 capacity  📂 modifiers  📂 迭代器失效 📁 模拟实现  📂 迭代器的实现 📂 代码展示 📁 和vector的区别 📁 总结         欢迎收看本期【C++杂货铺】,本期内容将讲解STL中关于list的内容,会分为一下几个方

    2024年04月14日
    浏览(35)
  • 【C++杂货铺】运算符重载

    本文将以日期类为基础,去探寻运算符重载的特性与使用方法,下面先给出日期类的基础定义: 备注 :拷贝构造函数和析构函数,均可以不写,因为当前日期类的三个成员变量都是内置类型,没有动态申请空间,使用浅拷贝就可以。 📖 如何比较两个日期的大小? 现如今,

    2024年02月16日
    浏览(53)
  • 【C++杂货铺】C++介绍、命名空间、输入输出

     C语言是 结构化 和 模块化 的语言,适合处理 较小规模 的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了 OOP (object oriented programming: 面向对象 )思想,支持面向对象的程序设计语言应

    2024年02月16日
    浏览(30)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包