C++:类的6大默认成员函数(拷贝构造函数篇)

本文详细介绍了C++中拷贝构造函数的概念,包括它的作用、默认行为(浅拷贝/值拷贝)、const修饰的用途以及在涉及资源申请时的重要性。特别强调了没有显式定义拷贝构造函数可能导致的资源管理问题。
摘要由CSDN通过智能技术生成


前言:Hello,大家好,咱这篇博客继续默认成员函数,今天的笔记分享为拷贝构造函数~

1、拷贝构造函数的概念

在创建对象时,我们能否创建一个与已存在对象一某一样的新对象呢?
答案是可以的,让我们一起来看看吧。

首先我们先定义一个Date类:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;

};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(d1);

	return 0;
}

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

Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

上述拷贝构造函数我们也可以写成(加上this指针):

const用途

Date(Date& d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

当然了,一般情况下,我们还会加上const修饰:

Date(const Date& d)

正常情况下,加不加const无所谓,加上的目的主要是防止我们有时候写代码迷糊了,将构造函数写成如下形式:

d._year= this->_year;
d._month=this->_month ;
d._day=this->_day ;

这种情况相当于"逆拷贝",我们通过拷贝d2来改变d1,本末倒置。

在这里插入图片描述
const的出现就是为了避免此类"昏头行为",加上之后,当我们写反了编译器就会报错,我们就可以马上纠正了。

2、拷贝构造函数的特性

拷贝构造函数也是特殊的成员函数,其特性为:
a.拷贝构造函数是构造函数的一个重载形式(拷贝构造函数是构造函数的一种);

b.拷贝构造函数的参数只有一个且必须是类型为类的对象的引用,使用传值方式编译器直接报错(因为会引发无穷递归调用);
对于传值传参和传引用传参,我们通常作以下解释:

解释1.C++传值传参时需要调用拷贝构造函数:
在这里插入图片描述
解释2.但是传引用传参时,就不需要调用拷贝构造函数了,此时的rd就是d1的别名。
在这里插入图片描述

延伸3.对于以下这个拷贝构造函数,如果我们将它改为传值调用,会发生什么情况呢?

Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

调用拷贝构造函数要先传参,而根据解释1,传值传参时需要调用拷贝构造函数,而且传值传参时会形成一个新的拷贝构造函数,不断调用,不断生成,最终会引发无穷递归
也正因为如此,我们拷贝构造函数的参数必须是类型为类的对象的引用
在这里插入图片描述

浅拷贝/值拷贝

c. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	

private:
	int _year;
	int _month;
	int _day;

};

int main()
{
	Date d1(2024, 1, 28);
	Date d2(d1);
	d1.Print();
	d2.Print();
	return 0;
}

在这里插入图片描述
这里我没写拷贝构造函数,但这里可以看出自动拷贝,也可以说明默认生成的拷贝构造函数对内置类型同样处理了(不同于构造、析构函数)。
d.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

当然像日期类这样的类是没必要的。但下面的类呢?让我们一起来看看吧。

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

在这里插入图片描述
s2对象是s1使用拷贝构造,而此时Stack没有显示定义拷贝构造函数,所以s1中的内容会原封不动地拷贝到s2,此时就会出现一个问题:s1,s2指向了一个内存空间,当程序退出时,s1,s2都会调用析构函数来销毁,这就导致了一块内存空间会被释放两次,必将造成程序崩溃。

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值