`
lgh1992314
  • 浏览: 315682 次
文章分类
社区版块
存档分类
最新评论

复制构造函数(拷贝构造函数)

 
阅读更多
也许很多C++的初学者都知道什么是构造函数,但是对复制构造函数(copy constructor)却还很陌生。对于我来说,在写代码的时候能用得上复制构造函数的机会并不多,不过这并不说明复制构造函数没什么用,其实复制构造函数能解决一些我们常常会忽略的问题。
为了说明复制构造函数作用,我先说说我们在编程时会遇到的一些问题。对于C++中的函数,我们应该很熟悉了,因为平常经常使用;对于类的对象,我们也很熟悉,因为我们也经常写各种各样的类,使用各种各样的对象;对于指针的操作,我们也不陌生吧?嗯,如果你还不了解上面三个概念的话,我想这篇文章不太适合你,不过看看也无碍^_^。我们经常使用函数,传递过各种各样的参数给函数,不过把对象(注意是对象,而不是对象的指针或对象的引用)当作参数传给函数的情况我们应该比较少遇见吧,而且这个对象的构造函数还涉及到一些内存分配的操作。嗯,这样会有什么问题呢?
把参数传递给函数有三种方法,一种是值传递,一种是传地址,还有一种是传引用。前者与后两者不同的地方在于:当使用值传递的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是相同的。当原始参数是一个类的对象时,它也会产生一个对象的副本,不过在这里要注意。一般对象产生时都会触发构造函数的执行,但是在产生对象的副本时却不会这样,这时执行的是对象的复制构造函数。为什么会这样?嗯,一般的构造函数都是会完成一些成员属性初始化的工作,在对象传递给某一函数之前,对象的一些属性可能已经被改变了,如果在产生对象副本的时候再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,这并不是我们想要的。所以在产生对象副本的时候,构造函数不会被执行,被执行的是一个默认的构造函数。当函数执行完毕要返回的时候,对象副本会执行析构函数,如果你的析构函数是空的话,就不会发生什么问题,但一般的析构函数都是要完成一些清理工作,如释放指针所指向的内存空间。这时候问题就可能要出现了。假如你在构造函数里面为一个指针变量分配了内存,在析构函数里面释放分配给这个指针所指向的内存空间,那么在把对象传递给函数至函数结束返回这一过程会发生什么事情呢?首先有一个对象的副本产生了,这个副本也有一个指针,它和原始对象的指针是指向同块内存空间的。函数返回时,对象的析构函数被执行了,即释放了对象副本里面指针所指向的内存空间,但是这个内存空间对原始对象还是有用的啊,就程序本身而言,这是一个严重的错误。然而错误还没结束,当原始对象也被销毁的时候,析构函数再次执行,对同一块系统动态分配的内存空间释放两次是一个未知的操作,将会产生严重的错误。
上面说的就是我们会遇到的问题。解决问题的方法是什么呢?首先我们想到的是不要以传值的方式来传递参数,我们可以用传地址或传引用。没错,这样的确可以避免上面的情况,而且在允许的情况下,传地址或传引用是最好的方法,但这并不适合所有的情况,有时我们不希望在函数里面的一些操作会影响到函数外部的变量。那要怎么办呢?可以利用复制构造函数来解决这一问题。复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。
除了将对象传递给函数时会存在以上问题,还有一种情况也会存在以上问题,就是当函数返回对象时,会产生一个临时对象,这个临时对象和对象的副本性质差不多。
拷贝构造函数,经常被称作X(X&),是一种特殊的构造函数,他由编译器调用来完成一些基于同一类的其他对象的构件及初始化。它的唯一的一个参数(对象的引用)是不可变的(因为是const型的)。这个函数经常用在函数调用期间于用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。
附另外一篇关于复制构造函数的文章:

对一个简单变量的初始化方法是用一个常量或变量初始化另一个变量,例如:


int m = 80;
  int n = m;
  我们已经会用构造函数初始化对象,那么我们能不能象简单变量的初始化一样,直接用一个对象来初始化另一个对象呢?答案是肯定的。我们以前面定义的Point类为例:


 Point pt1(15, 25);
  Point pt2 = pt1;
后一个语句也可以写成:
  Point pt2( pt1);
它是用pt1初始化pt2,此时,pt2各个成员的值与pt1各个成员的值相同,也就是说,pt1各个成员的值被复制到pt2相应的成员当中。在这个初始化过程当中,实际上调用了一个复制构造函数。当我们没有显式定义一个复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个内联的、公有的成员,它具有下面的原型形式:
 
 Point:: Point (const Point &);


可见,复制构造函数与构造函数的不同之处在于形参,前者的形参是Point对象的引用,其功能是将一个对象的每一个成员复制到另一个对象对应的成员当中。
  虽然没有必要,我们也可以为Point类显式定义一个复制构造函数:
  
Point:: Point (const Point &pt)
  {
   xVal=pt. xVal;
   yVal=pt. yVal;
  } 


  如果一个类中有指针成员,使用缺省的复制构造函数初始化对象就会出现问题。为了说明存在的问题,我们假定对象A与对象B是相同的类,有一个指针成员,指向对象C。当用对象B初始化对象A时,缺省的复制构造函数将B中每一个成员的值复制到A的对应的成员当中,但并没有复制对象C。也就是说,对象A和对象B中的指针成员均指向对象C,实际上,我们希望对象C也被复制,得到C的对象副本D。否则,当对象A和B销毁时,会对对象C的内存区重复释放,而导致错误。为了使对象C也被复制,就必须显式定义复制构造函数。下面我们以string类为例说明,如何定义这个复制构造函数。

class String
{
 public:
  String(); //构造函数
  String(const String &s); //复制构造函数
  ~String(); //析构函数
  // 接口函数
  void set(char const *data);
  char const *get(void);
 private:
  char *str; //数据成员ptr指向分配的字符串
};
String ::String(const String &s)
{
 str = new char[strlen(s.str) + 1];
 strcpy(str, s.str);
}


我们也常用无名对象初始化另一个对象,例如:

  Point pt = Point(10, 20);
  类名直接调用构造函数就生成了一个无名对象,上式用左边的无名对象初始化右边的pt对象。
  构造函数被调用通常发生在以下三种情况,第一种情况就是我们上面看到的:用一个对象初始化另一个对象时;第二种情况是当对象作函数参数,实参传给形参时;第三种情况是程序运行过程中创建其它临时对象时。下面我们再举一个例子,就第二种情况和第三种情况进行说明:
  

Point foo(Point pt) 
  { 
   … 
   return pt;
  }
  void main()
  {
   Point pt1 = Point(10, 20);
   Point pt2;
   …
   pt2=foo(pt);
   …
  }


  在main函数中调用foo函数时,实参pt传给形参pt,将实参pt复制给形参pt,要调用复制构造函数,当函数foo返回时,要创建一个pt的临时对象,此时也要调用复制构造函数。

缺省的复制构造函数
  在类的定义中,如果没有显式定义复制构造函数,C++编译器会自动地定义一个缺省的复制构造函数。下面是使用复制构造函数的一个例子:

例题 例10-12
#include <iostream.h>
#include <string.h>
class withCC
{
 public:
 withCC(){}
 withCC(const withCC&)
 {
  cout<<"withCC(withCC&)"<<endl;
 }
};
class woCC
{
 enum{bsz = 100};
 char buf[bsz];
public:
 woCC(const char* msg = 0)
 {
  memset(buf, 0, bsz);
  if(msg) strncpy(buf, msg, bsz);
 }
 void print(const char* msg = 0)const
 {
  if(msg) cout<<msg<<":";
  cout<<buf<<endl;
 }
};
class composite
{
 withCC WITHCC;
 woCC WOCC;
public:
 composite() : WOCC("composite()"){}
 void print(const char* msg = 0)
 {
  WOCC.print(msg);
 }
};
void main()
{
 composite c;
 c.print("contents of c");
 cout<<"calling composite copy-constructor"<<endl;
 composite c2 = c;
 c2.print("contents of c2");
}

  类withCC有一个复制构造函数,类woCC和类composite都没有显式定义复制构造函数。如果在类中没有显式定义复制构造函数,则编译器将自动地创建一个缺省的构造函数。不过在这种情况下,这个构造函数什么也不作。
  类composite既含有withCC类的成员对象又含有woCC类的成员对象,它使用无参的构造函数创建withCC类的对象WITHCC(注意内嵌的对象WOCC的初始化方法)。
  在main()函数中,语句:
  composite c2 = c;
通过对象C初始化对象c2,缺省的复制构造函数被调用。
  最好的方法是创建自己的复制构造函数而不要指望编译器创建,这样就能保证程序在我们自己的控制之下。
分享到:
评论

相关推荐

    构造函数和复制构造函数

    构造函数和复制构造函数的详细介绍 构造函数是C++中的一种特殊函数,它们...构造函数、复制构造函数和拷贝构造函数是C++中三个基本的函数,它们之间有着紧密的联系,并且在对象的初始化和复制操作中扮演着重要的角色。

    没有可用的复制构造函数或复制构造函数声明

    在C++编程中,"没有可用的复制构造函数或复制构造函数声明"是一个常见的错误,通常出现在尝试复制一个对象,而该对象的类没有定义复制构造函数时。在这个特定的情境中,问题出在一个名为`CArray, int&gt;`的自定义数组...

    C++复制构造函数详解

    - **浅拷贝与深拷贝**:默认情况下,复制构造函数执行的是浅拷贝,只复制对象的成员变量的指针,而不是指针所指向的数据。如果类中有动态分配的资源,需要确保执行深拷贝以避免共享资源和内存泄漏。 - **自赋值优化...

    复制构造函数

    ### 复制构造函数:深度复制与浅复制详解 在C++编程中,对象的复制是一种常见的需求,尤其是在处理复杂的类实例时。复制构造函数,作为C++中一种特殊的构造函数,专门用于创建一个对象的复制品。然而,简单地复制...

    C++简单类(构造函数,析构函数以及拷贝构造函数)的实现

    然后,通过拷贝构造函数创建了另一个`cPerson`对象`b`,将`a`的所有属性复制给了`b`。最后,分别打印了两个对象的信息,展示了它们具有相同的属性值。 ### 总结 通过以上分析,我们可以看到,构造函数、析构函数...

    C++实现 类string的 普通构造函数, 拷贝构造函数 析构函数 和赋值函数

    这里的拷贝构造函数确保了`data_`的正确复制,避免了原始对象和副本之间的数据冲突。 **析构函数**: 析构函数在对象生命周期结束时(如离开作用域或显式删除)自动调用,用于清理对象分配的资源。对于`std::string...

    C++类对象的拷贝构造函数

    与普通类型的对象不同,类对象内部结构一般较为复杂,存在各种成员变量,因此需要通过拷贝构造函数来完成整个复制过程。 拷贝构造函数的工作过程可以理解为:当用一个已初始化过了的自定义类类型对象去初始化另一个...

    C++面试试题-拷贝构造函数

    然而,默认的拷贝构造函数通常执行的是浅复制(shallow copy),即仅仅复制对象的指针或其他引用,而不是真正地复制其所指向的数据。这种行为在某些情况下可能导致问题,尤其是在处理动态分配的内存时。 #### 题目...

    c++中拷贝构造函数实例

    - **浅复制**:如果类的成员变量是基本类型(如int、double)或指针,拷贝构造函数默认执行浅复制,即只复制指针本身,不复制指针所指向的数据。这样,新旧对象共享同一块内存,修改一个对象可能会影响另一个。 - **...

    C 拷贝构造函数.rar

    拷贝构造函数的实现通常包括对类中各个成员的逐个复制,以确保新对象与原对象的数据一致。 拷贝构造函数的三种主要用法包括: 1. **直接初始化**:当创建一个新对象并用已存在对象进行初始化时,拷贝构造函数被调用...

    拷贝构造函数.rar

    1. **直接初始化**:当使用现有的对象来创建新对象时,如`Obj obj2(obj1)`,编译器会调用拷贝构造函数来复制`obj1`的所有成员到`obj2`。 2. **隐式类型转换**:如果一个函数或方法期望一个对象作为参数,但传入的是...

    拷贝构造函数..........

    拷贝构造函数是C++中一个非常重要的概念,主要用于在对象之间进行深度复制,确保每个对象拥有自己独立的一份数据。拷贝构造函数通常在以下几种情况被调用: 1. 当一个新对象通过已存在的对象初始化时。 2. 当一个...

    C++构造函数,复制构造函数和析构函数专题[1].pdf

    默认的复制构造函数仅执行浅拷贝,可能导致意外的共享状态。程序员通常需要自定义复制构造函数来实现期望的行为,如: ```cpp class MyClass { public: MyClass(const MyClass& other) { myValue = other.myValue...

    C++复制(拷贝)构造函数实验代码

    C++复制(拷贝) 构造函数实验代码 每个类只有一个析构函数 和一个赋值函数 ,但可以有多个构造函数 (包含一个拷贝构造函数 ,其它的称为普通构造函数 )。对于任意一个类 A ,如果不想编写上述函数, C++ 编译器将...

    复制构造函数的用法实例

    总有人会不很了解复制构造函数的用法,通过实例就可以清除了解其用法。

    C++拷贝构造函数和赋值操作

    ### C++拷贝构造函数与赋值操作详解 在C++编程中,拷贝构造函数与赋值操作是实现类的拷贝管理的关键机制。它们主要用于处理类的对象之间的拷贝和复制过程,尤其是在处理含有动态分配内存的类时尤为重要。 #### ...

    详解C++ 拷贝构造函数

    拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创建的对象。 复制对象把它作为参数传递给...

    拷贝构造函数

    拷贝构造函数的存在是为了满足在C++中对象的复制需求,它可以在三个情况下被自动调用:第一种情况是当用类的一个对象去初始化该类的另一个对象时;第二种情况是如果函数的形参是类的对象,调用函数时,进行形参和...

Global site tag (gtag.js) - Google Analytics