C++拷贝构造函数和浅拷及深拷贝详解

目录

 

拷贝构造函数

1,使用一个已经创建完毕的对象来初始化一个对象

2,值传递的方式给函数参数传值

3,值方式返回局部对象

深拷贝和浅拷贝

什么是浅拷贝和深拷贝

浅拷贝带来的问题

深拷贝


拷贝构造函数

拷贝构造函数也称复制构造函数,是一种特殊的构造函数。形参必须是引用,但不限制 为const,一般普遍的会加上const限制。

此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常 量方式调用。

两种分类方式

按参数分为:有参构造函数和无参构造函数

按类型分为:普通构造函数和拷贝构造函数

带参数的这里暂且不提,这里主要讲一下拷贝构造函数

C++中拷贝构造函数调用时机通常有三种情况

1,使用一个已经创建完毕的对象来初始化一个对象

2,值传递的方式给函数参数传值

3,以值方式返回局部对象

1,使用一个已经创建完毕的对象来初始化一个对象

#include <iostream>
using namespace std;

class A
{
private:
	int x;
public:
	A(int a)
	{
		cout << "A有参构造函数调用!" << endl;
		x = a;
	}
	A(const A& a)
	{
		cout << "A拷贝构造函数" << endl;
		x = a.x;
	}
	void show()
	{
		cout << "x的值为:" << x << endl;
	}
};
int main()
{
	A a1(20);
	A a2(a1);
	a2.show();
	return 0;
}

输出结果

A有参构造函数调用!

A拷贝构造函数

x的值为:20

可以发现,通过拷贝构造函数将a1的x值赋值给了a2的x

2,值传递的方式给函数参数传值

#include <iostream>
using namespace std;

class A
{
private:
	int x;
public:
	A(int a)
	{
		cout << "A有参构造函数调用!" << endl;
		x = a;
	}
	A(const A& a)
	{
		cout << "A拷贝构造函数" << endl;
		x = a.x;
	}
	void show()
	{
		cout << "x的值为:" << x << endl;
	}
};
void test(A w)
{

}
int main()
{
	A a1(20);
	test(a1);
	return 0;
}

输出结果

A有参构造函数调用!

A拷贝构造函数

创建a1对象时会调用有参构造函数,这是正常的,但是在调用test函数时将a1 对象传进去的时候又调用了拷贝构造函数

拷贝构造函数是怎么做的呢,其实是实参在传给形参的时候会调用拷贝构造函数, 形参w会根据实参a1拷贝出一个新的对象,也就是A w=a

其实原理和函数的原理一样,就是形参和实参的区别。一般函数传参都是值传递, 也就是在传入实参后在函数体内定义了一个和实参类型相同的变量,并且将它赋值为传入的实参

还有就是引用传递和指针传递,引用传递在函数体内部未新定义变量去接收这个值, 没有开辟新空间,只是创造了一个别名,本质上形参就是传入的实参,在函数有改 变就会对原有的对象产生改变

指针传递和值传递一样,不过函数体内定义的是一个指针变量,将它赋值为传入的 地址,通过指针访问和修改数据

这里就是类似函数的值传递

3,值方式返回局部对象

#include <iostream>
using namespace std;

class A
{
private:
	int x;
public:
	A(int a)
	{
		cout << "A有参构造函数调用!" << endl;
		x = a;
	}
	A(const A& a)
	{
		cout << "A拷贝构造函数" << endl;
		x = a.x;
	}
	void show()
	{
		cout << "x的值为:" << x << endl;
	}
};
A test()
{
	A a(10);
	return a;
}
int main()
{
	A a1 = test();
	return 0;
}

输出结果

A有参构造函数调用!

A拷贝构造函数

从输出结果不难看过,在test函数声明一个A对象自然会调用有参构造函数,在 main接收test返回的对象又调用了拷贝构造函数,即在return的时候返回的并不是真正的a对象,而是根据a对象拷贝一个新的对象返回,即a对象和a1对象不 相同,所以会调用拷贝构造函数。

这个也是值传递,不过是根据局部对象拷贝的新的对象返回

注意:如果没有自定义拷贝构造函数,又使用了对象的拷贝,则编译器会自动生成 一个默认构造函数

深拷贝和浅拷贝

什么是浅拷贝和深拷贝

浅拷贝:简单的赋值拷贝操作。比如上面的操作就是浅拷贝

深拷贝:在堆区重新申请空间,进行拷贝操作

浅拷贝带来的问题

#include <iostream>
using namespace std;

class A
{
private:
	int x;
	int* y;
public:
	A(int a,int b)
	{
		cout << "A有参构造函数调 用!" << endl;
		x = a;
		y = new int(b);//在堆区申请int类型变量,用y接收
	}
	~A()
	{
		//析构代码,将堆区开辟的数据做释放操作
		if (y != NULL)
		{
			delete y;
			y = NULL;
		}
		cout << "A类析构函数调用" << endl;
	}
};
int main()
{
	A a1(2, 3);
	A a2(a1);
	return 0;
}

这个代码运行会报错,所以这里输出结果就没有了

简单讲一下这个代码,就是A类定义了一个int类型的x和一个int指针类型的y, 然后声明一个有参构造函数为x和y赋值,在析构函数那里为y释放空间,然后创 建了一个a1对象,接着利用编译器自带的拷贝构造函数将a1拷贝给a2。这里是 没有问题的,那么为什么会报错呢

 这里假设有参构造函数给y申请的地址是0x0011

将a1拷贝给a2时,将a1的y的地址拷贝给了a2的y,所以a1的y和a2的y指向的是堆区的同一片地址

main函数中按照调用顺序,首先会调用a2的析构函数,然后调用a1的析构函数。 在调用a2的析构函数后,a2的y指向的堆区空间就被释放了,在调用a1的析构 函数释放y的空间时就会报错,因为y的空间已经被释放过了,所以这就是浅拷贝 带来的问题:堆区的内存重复释放

浅拷贝的问题就要利用深拷贝来解决了

深拷贝

编译器提供的拷贝函数会出现上面所说的浅拷贝带来的问题,所以要自己实现拷贝 构造函数来解决浅拷贝带来的问题

对上面的代码进行一下修改

#include <iostream>
using namespace std;

class A
{
private:
	int x;
	int* y;
public:
	A(int a,int b)
	{
		cout << "A有参构造函数调用!" << endl;
		x = a;
		y = new int(b);//在堆区申请int类型变量,用y接收
	}
	A(const A& a)
	{
		cout << "A拷贝构造函数调用" << endl;
		x = a.x;
		//y = a.y;编译器默认构造函数实现就是这行代码
		//深拷贝操作
		y = new int(*a.y);
	}
	~A()
	{
		//析构代码,将堆区开辟的数据做释放操作
		if (y != NULL)
		{
			delete y;
			y = NULL;
		}
		cout << "A类析构函数调用" << endl;
	}
};
int main()
{
	A a1(2, 3);
	A a2(a1);
	return 0;
}

输出结果

A有参构造函数调用!

A拷贝构造函数调用

A类析构函数调用

A类析构函数调用

深拷贝的操作就行重新在堆区申请一片内存,将需要拷贝的值赋值过去,这样在析 构函数释放申请的空间时就不会出现堆区内存重复释放的问题。

这个代码和上面的代码唯一的区别在于拷贝构造函数也申请了一片堆区空间给y 赋值,上面的代码是直接将a1的y赋值给了a2,他们两个y指向的地址空间是一 样的。这里在拷贝构造函数申请一片空间将a1的y值赋值给a2,这样a1的y 和 a2的y指向的地址就不一样了,所以在释放y时就不会出现重复释放的问题了

这里假设构造函数申请的空间地址是0x0022

上面的操作就是深拷贝,也就是给要拷贝的对象也申请一片内存空间,不让两个对 象同时指向同一片内存空间

注意:这里我用了析构函数,一般我们不用析构函数也行,但是这里为了讲解浅拷贝带来 的堆区内存重复重放释放问题时用到了析构函数,在析构函数里面执行对堆区内存 的释放。

也就是说如果我们堆区有内存是要到析构函数那里释放的。这就是析构函数的用途

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题, 也就是我们要自己提供一个深拷贝防止浅拷贝带来的问题。

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

真的没事鸭

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

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

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

打赏作者

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

抵扣说明:

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

余额充值