C++ ——— 类的 6 个默认成员函数之 拷贝构造函数

目录

前言

浅拷贝问题

日期类的赋值拷贝

栈类的赋值拷贝

解决栈类的二次释放问题

拷贝构造函数的概念

拷贝构造函数的特征

日期类的拷贝构造函数

栈类的拷贝构造函数

编译器默认生成的拷贝构造函数


前言

在前两章学习了 析构函数 和 构造函数

C++ ——— 类的 6 个默认成员函数之 析构函数-CSDN博客

C++ ——— 类的 6 个默认成员函数之 构造函数-CSDN博客

接下来学习拷贝构造函数


浅拷贝问题

日期类的赋值拷贝

void func(Data d)
{
	d.Print();
}

int main()
{
	Data d1(2024, 12, 12);
	func(d1);

	return 0;
}

关于日期类的代码请见构造函数的讲解

实例化了一个日期类 d1,并将 d1 值传递给 func 函数,func 函数中的同类型 d 接收,并打印数据

代码验证:

可以发现,打印没有任何问题,程序也是正常结束

栈类的赋值拷贝

void func(Stack s)
{

}

int main()
{
	Stack s1;
	func(s1);

	return 0;
}

关于栈类的代码请见构造函数的讲解

同样实例化了一个栈类 s1,并且将 s1 值传递给 func 函数,func 函数中的同类型 s 接收

代码验证:

出现程序崩溃,问题出在栈的析构函数中

// 析构函数
~Stack()
{
	cout << "~Stack()" << endl;

	free(_a);
	_a = nullptr;
	_top = _capacity = 0;
}

我们知道程序运行结束时,编译器会自动调用类的析构函数,且是以栈的后进先出的特点调用的

所以会先调用 func 中 s 的析构函数,且 s1 是通过值传递给 s 的,那么 s 中的 _a 是直接赋值了 s1 中的 _a,先调用 func 中 s 的析构函数后,就把 _a 这块空间给释放掉了

再是 s 调用析构函数,但此时 s 中的 _a 的空间已经被释放了,_a 已经是野指针了,并且没有置空,所以 free 再次释放后就照成了二次释放问题,所以程序会崩溃

解决栈类的二次释放问题

1. 将值传递改为引用传递

void func(Stack& s)
{
}

int main()
{
	Stack s1;
	func(s1);

	return 0;
}

s 作为 s1 的别名,对 s 的修改就会直接修改 s1 ,所以先对 s 析构时,s1 中的 _a 也同样置空了,就不会出现二次释放的问题

代码验证:

可以发现用引用能解决二次释放的问题,但是有些情况解决不了

比如用了引用,那么对 s 的改变就会改变 s1 ,但是不期望改变 s1

所以以上问题用一个函数来解决,就是拷贝构造函数


拷贝构造函数的概念

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般是用 const 修饰)

在用已存在的类类型对象创建新对象时,由编译器自动调用


拷贝构造函数的特征

1. 拷贝构造函数时构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个,且必须时类的类型对象的引用,使用值传递的方式编译器会直接报错,因为会引发无穷递归调用

日期类的拷贝构造函数

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

不论是传参还是赋值,都会先来调用拷贝构造函数,这是 C++ 强制要求的

所以拷贝构造函数不能写成值传递的形式,否则就会出现无穷递归的问题

因为不使用引用传参的话,在传参的时候又会调用拷贝构造,就会无穷递归调用下去

使用引用传参,就能避免这个问题,因为引用的底层逻辑还是指针,而指针只是地址,所以就不会出现无穷递归的问题

栈类的拷贝构造函数

Stack(const Stack& s)
{
	// 深拷贝

	//开辟同样大的空间
	_a = (int*)malloc(sizeof(int) * s._capacity); 

	// 判断释放开辟成功
	if (_a == nullptr)
	{
		perror("realloc fail");
		return;
	}

	// 将内容赋值到开辟好的空间里
	memcpy(_a, s._a, sizeof(int) * s._top);
	_top = s._top;
	_capacity = s._capacity;
}

避免二次释放的问题,所以开辟 s 中 _a 一样大的空间,并赋值内容,这就是深拷贝,各自指向各自的空间,且对 s 的改变不影响 s1


编译器默认生成的拷贝构造函数

若没有显示定义,编译器会生成默认的拷贝构造函数

默认的拷贝构造函数对象按内存存储的字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

那么默认生成的拷贝对于栈这个类就会有问题,所以还是手动写上拷贝构造较好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值