C++中的两种拷贝

深拷贝与浅拷贝

通过构造函数对对象数据成员进行拷贝时,一般涉及两个层面:一个是将源对象数据成员赋给目标对象数据成员,这种操作不涉及动态存储空间层面的拷贝由系统为每一个类提供的默认拷贝函数完成,称为浅拷贝;当一个类包含指向动态存储空间指针类型的数据成员,并且通过构造函数动态申请了存储空间,那么必须定义一个拷贝函数,否则在析构时会出现错误,这种定义拷贝函数的过程称为深拷贝

下面我们来看看两个例子

# include <iostream>
using namespace std;
class Date
{
private:
	int year,month,day;
public:
	Date(int y=2020,int m=6,int d=23);
	Date(const Date &date);		//拷贝构造函数声明
	Date::~Date()
	{
		cout<<"Destructing..."<<endl;
	}
	void Show();
};
Date::Date(int y,int m,int d)
{
	year=y;
	month=m;
	day=d;
	cout<<"Constructing..."<<endl;
}
Date::Date(const Date &date1)
{
	year=date1.year;
	month=date1.month;
	day=date1.day;
	cout<<"Copy-Constructing..."<<endl;
}
void Date::Show()
{
	cout<<year<<"."<<month<<"."<<day<<endl;
}
Date Fun(Date date2)		//类里面的对象作为形参
{
	Date date3(date2);		//直接拷贝,相当于date3直接使用date2的参数
	return date3;
}
int main(void)
{
	Date obj1(2002,10,13);
	Date obj3;
	Date obj2(obj1);
	Date obj4=obj2;		//调用拷贝构造函数,相当于Date obj4(obj2)
	obj3=obj2;
	obj3=Fun(obj2);
	obj3.Show();
	system("pause");
	return 0;
}

在不涉及动态存储空间的拷贝时,构造函数所执行的操作只是将源对象数据成员赋给目标对象数据成员
可以正常执行constructing和destructing操作,结果如图:
无warning无error
再看看这一个

# include <iostream>
using namespace std;
class String
{
private:
	char *S;
public:
	//构造函数声明
	String(char *p=0);
	~String();
	void Show();
};
//构造函数定义
String::String(char *p)
{
	if(p)		//如果p指向为非空,则为其开辟一个新的存储空间,大小为strlen(p)+1,赋给S,若p为空,则S为空
	{
		S=new char[strlen(p)+1];
		strcpy(S,p);
	}
	else S=0;
}
String::~String()
{
	if(S)
		delete []S;		//析构函数释放new的存储空间
}
void String::Show()
{
	cout<<"S:"<<S<<endl;
}
int main(void)
{
	String s1("Hello World");		//调用构造函数
	String s2(s1);		//调用系统默认拷贝函数执行浅拷贝
	s1.Show();
	s2.Show();
	system("pause");
	return 0;
}

编译过程不会出现error或warning但是无法正常执行析构delete-new:delete []S操作,执行后中断执行
在这里插入图片描述
因为在执行

String s1("Hello World");		//调用构造函数

此时构造函数动态分配存储空间,并将返回地址赋给对象的成员S,然后将"Hello World"的内容复制到这块空间。
由于String没有定义一个拷贝函数,因此当语句

String s2(s1);	

定义对象s2时将调用系统默认的拷贝函数,负责将对象s1的数据成员S中存放的地址值赋给对象s2的数据成员S此时内存空间如图所示:

对象s1 S
Hello World
对象s2 S

因此对象s1复制给s2的仅仅是数据成员S的值,而并没有将S所指向的动态存储空间进行复制,这种复制称为浅拷贝
而浅拷贝的副作用是调用

s1.Show();
s2.Show();

时不会看出什么问题,因为两个对象所指向的存储区域是相同的,都是正确访问:调用成功与否看访问是否正确合法,但是当对象生命周期结束时需要撤销对象时,首先由s2调用析构函数(先构造后析构,后构造先析构),将S所指向字符串的动态存储空间释放:

对象s1 S

所以在对象s1调用析构函数之前,S已经成为悬挂指针,因此在调用析构函数时,无法正常执行

{
	if(S)
		delete []S;		//析构函数释放new的存储空间
}

从而导致出错,中断执行
此时通过定义一个深拷贝函数可以解决浅拷贝带来的指针悬挂问题深拷贝不是简单的复制指针本身,而是复制指针所指向动态存储空间中所有的内容,因此,两个对象的数据成员指针S就指向不同的地址,指向不同的动态存储空间的首地址,而两个动态存储空间中的内容完全一样
在第二个例子的类声明中增加一个拷贝函数的声明和定义:

{
	String(const String &r);		//定义一个深拷贝函数
}
{
	//深拷贝定义
String::String(const String &r)
{
	if(r.S)	//如果r.S指向为非空,则为其开辟一个新的存储空间,大小为strlen(r.S)+1,赋给S,若r.S为空,则S为空
	{
		S=new char[strlen(r.S)+1];
		strcpy(S,r.S);
	}
	else S=0;
}

此时再去执行语句

String s2(s1);	

使用对象s1去创建对象s2,调用深拷贝函数,为当前新对象数据成员S另外申请了一块内存,然后将已知对象数据成员S值复制到当前对象的S所指向的内存空间:

对象s1 S
Hello World
对象s2 S
Hello World

对象s1,s2指向不同的存储区域,此时是深拷贝,就不会出现指针悬挂的问题,可以正常执行析构操作,程序得以正常运行,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值