C++ 复制构造函数

1. 概念:只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰)。

2. 首先看一个小例子:

.h中:

class Test
{
public:
    Test(int m,float n):a(m),b(n) { }

private:
    int a;
    float b;
};
.cpp中:

int  main()

{

      Test test1(5, 5.5);

      Test test2(1, 1.1);

      cout<<test2.a<<endl;       //很明显,输出1

      cout<<test2.b<<endl;      //很明显,输出1.1

      Test test3(test1);      //或者test3 = test1

      cout<<test3.a<<endl;          //输出5

      cout<<test3.b<<endl;          //输出5.5

      return 0;

}

test3使用了编译器提供的默认复制构造函数,即将类类型对象test1(与test3同类型)的非static成员复制给test3了(如有数组成员,则会逐个数组元素复制。因为数组不支持直接复制,只能遍历复制,所以默认复制构造函数会进行遍历复制数组元素)。。  程序中没有显示提供复制构造函数,所以编译器会合成一个。

 

2. 显示提供复制构造函数

.h中:

class Test
{
public:
 Test()          //方便定义test2
 {
 }
 Test(int m,float n):a(m),b(n) { }

 Test &test(const Test&);    //显示声明的复制构造函数

public:
 int a;
 float b;
};

.cpp 中

Test &Test::test(const Test& obj)
{
     a = obj.a;       //拷贝的关键.如果注释掉,则当前对象的a和b都是随机值
     b = obj.b;
     return *this;    //返回当前对象
}

 

int main()

{

     Test test1(2, 5.4);
     Test test2;
     test2.test(test1);     //调用复制构造函数
     cout<<test2.a<<endl;
     cout<<test2.b<<endl;

     return 0;

}

 

3.  深拷贝实例

.h文件中

class Test
{
public:
 Test()
 {
 }
 Test(int m,float n):a(m),b(n)
 {
      p = new char[10];
      memset(p, 0, 10);
      strcpy(p, "qinwg");
 }

 Test &test(const Test&);    //复制构造函数

 ~Test()
 {
  delete [] p;
  p = NULL;
 }
public:
 int a;
 float b;
 char *p;     //类中有指针成员,必须显示定义复制构造函数
};

 

.cpp中

Test &Test::test(const Test& obj)
{
 a = obj.a;  //拷贝的关键
 b = obj.b;
 p = new char[strlen(obj.p) + 1];   //深拷贝的关键
 strcpy(p, obj.p);
 return *this;
}

 

int main()

{

    Test test1(2, 5.4);
     Test test2;
     test2.test(test1);          //调用复制构造函数
     cout<<test2.a<<endl;
     cout<<test2.b<<endl;
     cout<<test2.p<<endl;
 
 system("pause");
 return 0;

}

 

4. 在C++中,下面三种对象需要拷贝的情况。因此,复制构造函数将会被调用。
   1). 一个对象以值传递的方式传入函数体
   2). 一个对象以值传递的方式从函数返回
   3). 一个对象需要通过另外一个对象进行初始化

如果在前两种情况不使用复制构造函数的时候,就会导致一个指针指向已经被删除的内存空间(前提是:类中有指针成员)。事实上,复制构造函数是由普通构造函数和赋值操作符共同实现的。

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

 

5.

        一般对象产生时都会触发构造函数的执行,但是在产生对象的副本时却不会这样,这时执行的是对象的复制构造函数。为什么会这样?一般的构造函数都是会完成一些成员属性初始化的工作,在对象传递给某一函数之前,对象的一些属性可能已经被改变了,如果在产生对象副本的时候再执行对象的构造函数,那么这个对象的属性又再恢复到原始状态,这并不是我们想要的。

         所以在产生对象副本的时候,构造函数不会被执行,被执行的是一个复制构造函数。当函数执行完毕要返回的时候,对象副本会执行析构函数,如果你的析构函数是空的话,就不会发生什么问题,但一般的析构函数都是要完成一些清理工作,如释放指针所指向的内存空间。这时候问题就可能要出现了。假如你在构造函数里面为一个指针变量分配了内存,在析构函数里面释放分配给这个指针所指向的内存空间,那么在把对象传递给函数至函数结束返回这一过程会发生什么事情呢?首先有一个对象的副本产生了,这个副本也有一个指针,它和原始对象的指针是指向同块内存空间的。函数返回时,对象的析构函数被执行了,即释放了对象副本里面指针所指向的内存空间,但是这个内存空间对原始对象还是有用的啊,就程序本身而言,这是一个严重的错误。然而错误还没结束,当原始对象也被销毁的时候,析构函数再次执行,对同一块系统动态分配的内存空间释放两次是一个未知的操作,将会产生严重的错误。


    上面说的就是我们会遇到的问题. 复制构造函数就是在产生对象副本的时候执行的,我们可以定义自己的复制构造函数。在复制构造函数里面我们申请一个新的内存空间来保存构造函数里面的那个指针所指向的内容。这样在执行对象副本的析构函数时,释放的就是复制构造函数里面所申请的那个内存空间。即所谓的深拷贝问题。
    除了将对象传递给函数时会存在以上问题,还有一种情况也会存在以上问题,就是当函数返回对象时,会产生一个临时对象,这个临时对象和对象的副本性质差不多。

 

6.

如果一个类中有指针成员,使用缺省的复制构造函数初始化对象就会出现问题。为了说明存在的问题,我们假定对象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);
}

演示了深拷贝的问题,对对象b的cname属性采取了新开辟内存的方式避免了内存归属不清所导致析构释放空间时候的错误,最后我必须提一下。

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


普通对象和类对象同为对象,他们之间的特性有相似之处也有不同之处,类对象内部存在成员变量,而普通对象是没有的,当同样的复制方法发生在不同的对象上的时候,那么系统对他们进行的操作也是不一样的,就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
当一个类没有自定义的拷贝构造函数的时候系统会自动提供一个默认的拷贝构造函数,来完成复制工作。
当用一个已经初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用,如果你没有自定义拷贝构造函数的时候系统将会提供给一个默认的拷贝构造函数来完成这个过程

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值