`
蒙面考拉
  • 浏览: 155694 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

复制构造函数(拷贝构造函数)以及深浅拷贝

 
阅读更多

对于普通对象而言复制是很简单的,一般是将变量或者常量赋值给某一个变量:int a=88;int a=b;而对于类对象而言,由于其内部结构复杂,复制过程便有一些道道:

#include <iostream>
using namespace std;

class CExample {
private:
     
int a;
public:
     CExample(
int b)
     
{ a=b;}
     
void Show ()
     
{
        cout
<<a<<endl;
    }

}
;

int main()
{
     CExample A(
100);
     CExample B
=A;
     B.Show ();
     
return 0;
}   代码的输出为:100。系统为对象B分配了内存并且完成了与对象A的复制过程。就对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制的

 

把参数传递给函数有三种方法:值传递,传地址,传引用。前者与后两者不同的地方在于:当使用值传递的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是完全相同的。当原始参数是一个对象的时候,它也会产生对象的副本。我们需要注意的是:一般对象产生时会触发构造函数,但是产生对象的副本时执行的却是复制构造函数,也就是拷贝构造函数。为什么呢?一般的构造函数完成的是一些成员属性初始化工作,对象传递给某一个函数时,对象的某些属性可能已经被改变了,如果再产生对象副本时再执行对象的构造函数,可能会使对象的属性又再恢复到原始状态。所以在产生对象副本时会执行一个默认的复制构造函数。但是新的问题出现了:在函数执行完返回的时候,对象副本会执行析构函数,如果你的析构函数是空的话没有任何问题,但是一般的析构函数会执行一些清理工作如释放指针所指向的内存,这时问题就出现了。假如构造函数里为一个指针变量分配了内存,在析构函数里面释放分配给这个指针所指向的内存空间。在使用对象进行值传递时,首先有一个对象的副本产生了,这个副本也有一个指针,它和原始对象的指针指向同一个内存空间。在函数返回时,对象副本的析构函数将会执行,即释放对象副本里面指针所指向的内存空间,但是这个内存空间对原始对象还是有用的,对程序本省而言这是一个严重的错误。并且当原始对象被销毁的时候析构函数会再次执行,对同一块系统动态分配的内存空间再次释放,将会产生严重的错误。

 

我们解决上述问题的办法就是拷贝构造函数==复制构造函数

概念:特殊的构造函数,它的唯一的一个参数是对本对象的引用,是不可改变的。这个函数一般用在下面三种情况:

          1.一个对象以值传递的方式传入函数体

          2.一个对象以值传递的方式从函数返回

          3.一个对象被另一个同类对象初始化

补充:初始化与赋值的不同:

          初始化:创建一个新的对象,并且初值来源于一个已存在的对象

          赋值:在已存在的两个对象之间进行的,此时没有创建任何对象,其实就是给一个已存在的对象一个新值

          int a=3;// 初始化     Thing t=x;//初始化,调用拷贝构造函数

          a=4;//赋值               t=x;//赋值,调用的operator=操作符函数,初始化调用的是构造函数

如果在类中没有显式的声明一个拷贝构造函数,编译器会私下制定一个函数来进行对象之间的位拷贝,也就是浅拷贝。

 

开头代码的带有拷贝构造函数的版本:

#include <iostream>
using namespace std;

class CExample {
private:
    
int a;
public:
    CExample(
int b)
    
{ a=b;}
    
    CExample(
const CExample& C)
    
{
        a
=C.a;
    }

    
void Show ()
    
{
       cout
<<a<<endl;
    }

}
;

int main()
{
    CExample A(
100);
    CExample B
=A;
    B.Show ();
    
return 0;
}
 

 当用一个已初始化过的自定义类类型对象去初始化另一个新构造的对象时,拷贝构造函数会被自动调用

 

 深浅拷贝的由来及作用:

 

        某些情况下,类内成员变量需要动态的开辟内存,如果实行位拷贝,也就是浅拷贝,也就是默认生成的拷贝,会把对象里的值完全复制给另一个对象,如A=B。如果B中有一个成员变量指针已经申请了内存,那么A中的那个指针成员变量也会指向同一块内存。已经开辟的一端堆地址原来是属于对象A的,因为由于复制过程发生,B对象取得的是a已经开辟的堆地址。当B把内存释放,这时A的指针就变成了野指针,会出现严重的错误。如何解决这个问题呢?

 

下面将给出深拷贝的代码示例:

 

#include <iostream>
using namespace std;
class CA
{
 public:
  CA(int b,char* cstr)
  {
   a=b;
   str=new char[b];//初始化时开辟内存
   strcpy(str,cstr);
  }
  CA(const CA& C)
  {
   a=C.a;
   str=new char[a]; //深拷贝的体现
   if(str!=0)
    strcpy(str,C.str);
  }
  void Show()
  {
   cout<<str<<endl;
  }
  ~CA()
  {
   delete str;
  }
 private:
  int a;
  char *str; //指针变量
};

int main()
{
 CA A(10,"Hello!");
 CA B=A;
 B.Show();
 return 0;
}
上述代码演示了如何用深拷贝解决上述问题的方法:对象B的str属性采取了新开辟内存的方式避免了归属不清所导致析构函数释放空间的错误。对象副本在调用析构函数时释放的空间是自己在复制过程中新开辟的内存空间,与原始对象的内存空间没有什么联系。

深拷贝和浅拷贝的区别:

      如果一个类拥有资源(堆或者其他系统资源),当这个类的对象发生复制过程的时候,被赋值的对象会开辟新的资源,这个过程叫做深拷贝。反之对象存在资源但复制过程并未复制资源,即没有开辟新的资源的情况视为浅拷贝。

      浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错,这点尤其需要注意!
其他网络资源:http://blog.csdn.net/feiyond/article/details/1807068

                     http://pcedu.pconline.com.cn/empolder/gj/c/0503/570112.html

                     http://hi.baidu.com/pweiwen/blog/item/6d3edf7e329f693d0cd7da37.html

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics