拷贝构造函数--程序员面试宝典学习

        拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)。此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。

1、拷贝构造函数概述

调用拷贝构造函数的情形

  在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):
  1) 一个对象作为函数参数,以 值传递的方式传入函数体;
  2) 一个对象作为函数返回值,以 值传递的方式从函数返回;
  3) 一个对象用于给另外一个对象进行 初始化(常称为 复制初始化);
  如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说, 初始化和赋值的不同含义是拷贝构造函数调用的原因。 事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的。
   通常的原则是:①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;②在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。原因详见后文。
   拷贝构造函数必须以引用的形式传递(参数为引用值)。其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象,这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。

隐式的拷贝构造函数

  如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会自动生成一个来进行对象之间 非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; 
  }; 
  这个类的主要特点是包含指向其他资源的指针,pBuffer指向堆中动态分配的一段内存空间。 
  int main(int argc, char* argv[]) 
  { 
  CExample theObjone; 
  theObjone.Init(40); 
  //现在需要另一个对象,并将它初始化为theObjone 
  CExample theObjtwo=theObjone; 
  ... 
  } 

  语句"CExample theObjtwo=theObjone;"用theObjone初始化theObjtwo。
  回顾一下此语句的具体过程: 首先建立对象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; 
  } 
   重载的注意事宜
  拷贝构造函数和赋值函数的功能是相同的,为了不造成重复代码,拷贝构造函数实现如下:
   CExample::CExample(const CExample& RightSides) 
  { 
  *this=RightSides; //调用重载后的"=" 
  } 
  
4、拷贝构造函数的格式与示例

拷贝构造函数的格式

拷贝构造函数的声明: 
  class 类名 
  { 
  public: 
       类名(形参参数)//构造函数的声明/原型 
       类名(类名&对象名)//拷贝构造函数的声明/原型 
  ... 
  }; 
  拷贝构造函数的实现: 
  类名::类名(类名&对象名)//拷贝构造函数的实现/定义 
  {函数体} 

  
拷贝构造函数的示例
Class Point 
  { 
  Public: 
        Point(int xx=0,int yy=m)(X=xx;Y=yy;) 
        Point(Point& p); 
        Int getX() {return X;} 
        Int getY(){ return Y;} 
  Private : 
        Int X,Y; 
  } 
  Point::Point(Point& p) 
  { 
     X=p.X; 
     Y=p.Y; 
     Cout<<"拷贝构造函数调用"<<endl; 
  }

  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值