c++ ---拷贝构造函数

拷贝函数的作用

为了同一个类实例化对象之间的赋值不出错`

举例验证

b.m_data = a.m_data
1.如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:
是b.m_data原有的内存没被释放,造成内存泄露;
是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;
是在对象被析构时,m_data被释放了两次。即:当含有指针时,如果调用默认的拷贝拷贝构造函数(赋值运算符),这个时候进行的浅拷贝(赋值),或者是影子拷贝,会使2个指针指向同一个内存区域,析构的时候就会出现同一个内存资源被释放2次的错误。对象存在资源但复制过程并未复制资源的情况视为浅拷贝。

  // 拷贝构造函数

  String::String(const String &other)
  {
  // 允许操作other 的私有成员m_data
  int length = strlen(other.m_data);
  m_data = new char[length+1];
  strcpy(m_data, other.m_data);
  }

  // 赋值函数

  String & String::operate =(const String &other)
  {
  // (1) 检查自赋值
  if(this == &other)
  return *this;
  // (2) 释放原有的内存资源
  delete [] m_data;
  // (3)分配新的内存资源,并复制内容
  int length = strlen(other.m_data);
  m_data = new char[length+1];
  strcpy(m_data, other.m_data);
  // (4)返回本对象的引用
  return *this;
  }

类String 拷贝构造函数与普通构造函数的区别是:在函数入口处无需与NULL 进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。
类String 的赋值函数比构造函数复杂得多.
分四步实现:
  (1)第一步,检查自赋值。你可能会认为多此一举,难道有人会愚蠢到写出 a = a 这样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现,例如

  // 内容自赋值
  b = a;
  …
  c = b;
  …
  a = c;
  // 地址自赋值
  b = &a;
  …
  a = *b;

也许有人会说:“即使出现自赋值,我也可以不理睬,大不了化点时间让对象复制自己而已,反正不会出错!”
  他真的说错了。看看第二步的delete,自杀后还能复制自己吗?所以,如果发现自赋值,应该马上终止函数。注意不要将检查自赋值的if 语句

  if(this == &other)

错写成为

  if( *this == other)

(2)第二步,用delete 释放原有的内存资源。如果现在不释放,以后就没机会了,将造成内存泄露
  (3)第三步,分配新的内存资源,并复制字符串。注意函数strlen 返回的是有效字符串长度,不包含结束符‘/0’。函数strcpy 则连‘/0’一起复制
  (4)第四步,返回本对象的引用,目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。那么能否写成return other 呢?效果不是一样吗?
    不可以!因为我们不知道参数other 的生命期。有可能other 是个临时对象,在赋值结束后它马上消失,那么return other 返回的将是垃圾。
    拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。 operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
还要注意的是拷贝构造函数是构造函数,不返回值
而赋值函数需要返回一个对象自身的引用,以便赋值之后的操作 链式操作
你肯定知道这个:

int a, b;
b = 7;
Func( a = b ); // 把i赋值后传给函数Func( int )
//同理:
CMyClass obj1, obj2;
obj1.Initialize();
Func2( obj1 = obj2 ); //如果没有返回引用,是不能把值传给Func2的
 
//注:
CMyClass & CMyClass:: operator = ( CMyClass & other )
{
	if( this == &other )
		return *this;
	// 赋值操作...
	return *this
}

调用拷贝构造函数的生成对象的方式是,用一个以有的对象的样子去构造一个新的各属性值一样的对象。如有类定义如下:
在C++语言里,

String s2(s1); 
String s3 = s1; 

只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。

#include <iostream>
#include <stdio.h>
using namespace std;

class A
{
private :
	int param1;
	char param2;
public :
	A()
	{
		cout<<"default construct function"<<endl;
	}
	A(int p1 , char p2)
	{
		this->param1 = p1;
		this->param2 = p2;
		cout<<"general construct function"<<endl;
	}
	
	A(A &other)
	{
		this->param1 = other.param1;
		this->param2 = other.param2;
		cout<<"copy construct function"<<endl;
	}
	
	void print(void)
	{
		cout<<"param1 = "<<param1<<endl<<"param2 = "<<param2<<endl;
	}
	
	A& operator=(A &other)
	{
		this->param1 = other.param1;
		this->param2 = other.param2;
		cout<<"overload operate = function"<<endl;
		return *this;
	}

};

int main(int argc, char * argv[])
{
	A obj1(1,'A');//定义一个对像obj1确定属性由参数传入,调用普通构造函数。
	
	obj1.print();//打印对象obj1的私有属性。
	
	A obj2(obj1);//用对象obj1去生成一个属性一样的对象obj2,调用拷贝构造函数。
	
	obj2.print();
	
	A obj3;//调用默认构造函数。
	obj3 = obj1;//调用赋值运算符重载函数。
	obj3.print();
	
	system("PAUSE");
	return 0;
}

总结:拷贝构造函数发生在下面三种情况下:
在C++中,下面三种对象需要调用拷贝构造函数:

1) 一个对象以值传递的方式传入函数体;
2) 一个对象以值传递的方式从函数返回;
3) 一个对象需要通过另外一个对象进行初始化;

深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝。

#include <iostream>
using namespace std;
#include <cstring>


class Namelist
{
	char *name;
public:
	Namelist (char *p)//带参数的构造函数
	{
		name= new char[strlen(p)+1];
		if (name!=0) 
			strcpy(name,p);
	}
	Namelist(){ };//缺省构造函数
	Namelist(Namelist&);//拷贝构造函数
	Namelist& operator=(char *p );
	Namelist& operator=( Namelist& ); 
	void display(){cout<<name<<endl;}
	~ Namelist ()
	{
		delete [] name ;
	}
};
Namelist::Namelist(Namelist& a)//定义拷贝构造函数
{
	name= new char[strlen(a.name)+1];
	if (name!=0) 
		strcpy(name,a.name);
}
Namelist& Namelist::operator=( char *p)//
//第一个重载赋值运算符,完成用常量给对象赋值
{
	name= new char[strlen(p)+1];
	if (name!=0) 
		strcpy(name,p);
	return *this;
} 
Namelist& Namelist::operator=( Namelist& a)
//第二个重载赋值运算符,完成类对象之间的赋值
{
	if (this!=&a)//首先查看是否赋值给自身,如果不是
	{
		delete[] name;//那么重新给它分配所需空间并拷贝字符串,保证目标对象和原对象各自拥有自己的拷贝字符串,从而完成深拷贝
	
	name=new char[strlen(a.name)+1];
	if (name!=0) strcpy(name,a.name);
	}
	return *this;
} 
int main()
{
	Namelist n1("first object"),n2("second object");//调用带参数的构造函数
	Namelist n3;//缺省构造函数
	cout<<"赋值前的数据:"<<endl;
	n1.display();
	n2.display();
	n3="third object";//调用第一个重载赋值运算符函数
	n2=n1;//调用第二个重载赋值运算符函数
	Namelist n4(n2);//调用拷贝构造函数函数
	Namelist n5=n3;//调用拷贝构造函数函数
	Namelist n6="sixth object";//调用带参数的构造函数等同于()的方法
	cout<<"赋值后的数据:"<<endl;
	n1.display();
	n2.display();
	n3.display();
	n4.display();
	n5.display();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

路过的小熊~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值