拷贝构造函数(深拷贝、浅拷贝)、赋值操作符的重载

1、拷贝构造函数的概述

  在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):

 1) 一个对象作为函数参数,以值传递的方式传入函数体;
 2) 一个对象作为函数返回值,以值传递的方式从函数返回;
 3) 一个对象用于给另外一个对象进行初始化(常称为复制初始化);
 
  如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间(比如程序员面试宝典p95)。对于第三种情况来说,可以分为两种情况:一是在声明对象时直接用另一个对象对它初始化;二是先声明一个对象,然后用另一个对象对它赋值(此时,需要重载赋值操作符)。
  
    通常的原则是:①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;②在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。
 
  拷贝构造函数是一种特殊的构造函数,它必须以引用的形式传递(参数为const &)。 如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会自动生成一个来进行对象之间非static成员的位拷贝(Bitwise Copy),此时是浅拷贝 。

   2、浅拷贝和深拷贝

下面用一个例子来说明:
 class CExample
 {
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete []pBuffer;}
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
int nSize;
};

int main(int argc, char* argv[])
{
CExample theObjone;
theObjone.Init(40);
  CExample theObjtwo=theObjone;//在声明theObjtwo时直接用对象theObjone对其初始化
 ...
}
 回顾一下此语句的具体过程:首先建立对象theObjtwo,并调用其构造函数,然后成员被复制初始化。其完成方式是内存拷贝,复制所有成员的值。完成后,theObjtwo.pBuffer==theObjone.pBuffer。即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了(即是浅拷贝)。这样不符合要求,对象之间不独立了,并为空间的删除带来隐患。所以需要采用必要的手段来避免此类情况:可以在构造函数中添加操作来解决指针成员的这种问题。
   所以C++语法中除了提供缺省形式的构造函数外,还规范了另一种特殊的构造函数:拷贝构造函数,一种特殊的构造函数重载。上面的语句中,如果类中定义了拷贝构造函数,在对象复制初始化时,调用的将是拷贝构造函数,而不是缺省构造函数。在拷贝构造函数中,可以根据传入的变量,复制指针所指向的资源。(深拷贝)拷贝构造函数的格式为:类名(const 类名& 对象名);//拷贝构造函数的原型,参数是常量对象的引用。由于拷贝构造函数的目的是成员复制,不应修改原对象,所以建议使用const关键字。
 
 提供了拷贝构造函数后的CExample类定义为:
  class CExample
{
public:
CExample(){pBuffer=NULL; nSize=0;}
~CExample(){delete []pBuffer;}
CExample(const CExample&); //拷贝构造函数
void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
int nSize;
};

CExample::CExample(const CExample& RightSides) //拷贝构造函数的定义
{ 
nSize=RightSides.nSize; //复制常规成员
 pBuffer=new char[nSize]; //复制指针指向的内容
memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
}
  这样,定义新对象,并用已有对象初始化新对象时,CExample(const CExample& RightSides)将被调用,而已有对象用别名RightSides传给构造函数,以用来作复制。
   深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
 
    当对象直接作为参数传给函数时,函数将建立对象的临时拷贝,这个拷贝过程也将调用拷贝构造函数。例如:
    BOOL testfunc(CExample obj);
testfunc(theObjone); //对象直接作为参数。
BOOL testfunc(CExample obj)
{
//针对obj的操作实际上是针对复制后的临时拷贝进行的
}
  还有一种情况,也是与临时对象有关:当函数中的局部对象作为返回值被返回给函数调者时,也将建立此局部对象的一个临时拷贝,拷贝构造函数也将被调用。
  CTest func()
{
CTest theTest;
return theTest;
}
 总结:当某对象是按值传递时(无论是作为函数参数,还是作为函数返回值),编译器都会先建立一个此对象的临时拷贝,而在建立该临时拷贝时就会调用类的拷贝构造函数

3、赋值操作符重载

  下面的代码与上例相似
 
  int main(int argc, char* argv[])
 
  {
   CExample theObjone;
  theObjone.Init(40);
   CExample theObjthree;
  theObjthree.init(60);
  //现在需要一个对象赋值操作,被赋值对象的原内容被清除,并用右边对象的内容填充。
 theObjthree=theObjone;
  return 0;
  }
 
  这里也用到了"="号,但与"复制初始化"中的例子并不同。"复制初始化"的例子中,"="在对象声明语句中,表示初始化。更多时候,这种初始化也可用圆括号表示。例如:CExample theObjthree(theObjone);。而本例子中,"="表示赋值操作。将对象theObjone的内容复制到对象theObjthree,这其中涉及到对象theObjthree原有内容的丢弃,新内容的复制。 但"="的缺省操作只是将成员变量的值相应复制。由于对象内包含指针,将造成不良后果:指针的值被丢弃了,但指针指向的内容并未释放。指针的值被复制了,但指针所指内容并未被复制。
 
  因此,包含动态分配成员的类除提供拷贝构造函数外,还应该考虑重载"="赋值操作符号。 类定义变为:
  class CExample
  {
public:
 CExample(){pBuffer=NULL; nSize=0;}
 ~CExample(){delete pBuffer;}
 CExample(const CExample&); //拷贝构造函数
CExample& operator = (const CExample&); //赋值符重载
 void Init(int n){ pBuffer=new char[n]; nSize=n;}
private:
char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
 int nSize;
  };
//赋值操作符重载
 CExample& CExample::operator = (const CExample& RightSides)
 {
 if (this == &RightSides) // 如果自己给自己赋值则直接返回
 {return *this;}
 nSize=RightSides.nSize;//复制常规成员
 char *temp=new char[nSize]; //复制指针指向的内容
 memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
 delete []pBuffer;//删除原指针指向内容(将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
 pBuffer=temp;//建立新指向
return *this;
 }

转载于:https://www.cnblogs.com/marinna/archive/2012/08/20/2647279.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值