1. 程式人生 > >C++中的構造函數,拷貝構造函數,賦值函數

C++中的構造函數,拷貝構造函數,賦值函數

cpp 區域 操作 兩個 16px size 取值 基於 lan

C++中一般創建對象,拷貝或賦值的方式有構造函數,拷貝構造函數,賦值函數這三種方法。下面就詳細比較下三者之間的區別以及它們的具體實現

1.構造函數

構造函數是一種特殊的類成員函數,是當創建一個類的對象時,它被調用來對類的數據成員進行初始化和分配內存。(構造函數的命名必須和類名完全相同)

首先說一下一個C++的空類,編譯器會加入哪些默認的成員函數

·默認構造函數和拷貝構造函數

·析構函數

·賦值函數(賦值運算符)

·取值函數

**即使程序沒定義任何成員,編譯器也會插入以上的函數!

構造函數可以被重載,可以多個,可以帶參數;

析構函數只有一個,不能被重載,不帶參數

而默認構造函數沒有參數,它什麽也不做。當沒有重載無參構造函數時,

A a就是通過默認構造函數來創建一個對象

2.拷貝構造函數

拷貝構造函數是C++獨有的,它是一種特殊的構造函數,用基於同一類的一個對象構造和初始化另一個對象。

當沒有重載拷貝構造函數時,通過默認拷貝構造函數來創建一個對象

A a;

A b(a);

A b=a; 都是拷貝構造函數來創建對象b

強調:這裏b對象是不存在的,是用a 對象來構造和初始化b的!!

先說下什麽時候拷貝構造函數會被調用:

在C++中,3種對象需要復制,此時拷貝構造函數會被調用

1)一個對象以值傳遞的方式傳入函數體

2)一個對象以值傳遞的方式從函數返回

3)一個對象需要通過另一個對象進行初始化

系統提供的默認拷貝構造函數工作方式是內存拷貝,也就是淺拷貝

。這種方法如果對象中用到了需要手動釋放的對象(如指針),則會出現問題

下面說說深拷貝與淺拷貝:

淺拷貝:如果復制的對象中引用了一個外部內容(例如分配在堆上的數據),那麽在復制這個對象的時候,讓新舊兩個對象指向同一個外部內容,就是淺拷貝。(指針雖然復制了,但所指向的空間內容並沒有復制,而是由兩個對象共用,兩個對象不獨立,刪除空間存在)淺拷貝復制了一份對象,只復制了對象的本身

深拷貝:如果在復制這個對象的時候為新對象制作了外部對象的獨立復制,就是深拷貝。深拷貝,把空間也拷貝了一份

默認的淺拷貝的情況:

class cperson
{
public :
      int *a;//當a是整型變量時就不會發生淺拷貝問題
public :
  cperson(const cperson &aa)
  {
    this->a=aa.a;
  }
     ~cperson()
    {
          delete a;
          a=NULL;
     }  

};

cperson ps(ps1);//會出現錯誤,程序會崩潰

 原因就是淺拷貝,只復制了對象本身的內容,指針成員所指向的空間是沒有拷貝的,會出現兩個對象使用一個空間從而導致在釋放空間的時候一個空間被釋放兩次

解決的方法:

1.使用深拷貝,如下例子:

class cperson
{
public :
      int *a;
public :
  cperson(const cperson &aa)
  {
    this->a=new int (*(aa.a));//深拷貝
  }
     ~cperson()
    {
          delete a;
          a=NULL;
     }  

};

 2.不讓它執行拷貝構造,即對象的函數傳參時不要用值傳遞,要用引用如下面的例子

void qq(cperson ps1)
{

}
cperson ps;
qq(ps);//發生了淺拷貝,程序錯誤

  用引用代替後

void qq(cperson &ps1)
{

}
cperson ps;
qq(ps);//程序正確

特別要註意的是下面這種情況:

cperson qq(cperson &ps1)
{      
     cperson ps2; return ps2; } 

  原因是為了保證能把ps2的值拿出去,ps2是臨時對象,要想把它傳出去就把它的值復制出去,此時又發生淺拷貝了。可以用返回一個引用解決。

cperson &qq(cperson &ps1)
{      
     cperson ps2;
        return ps2;
} 

但實際上盡量不要返回局部對象。

3.賦值函數

當一個類的對象向該類的另一個對象賦值時,就會用到該類的賦值函數。

當沒有重載賦值函數(賦值運算符)時,通過默認賦值函數來進行賦值操作

A a;

A b;

b=a;

強調:這裏a,b對象是已經存在的,是用a 對象來賦值給b的!!

賦值運算的重載聲明如下:

A& operator = (const A& other),我們先來看一下下面的例子

class cperson
{
public :int *a;
public :
  cperson()
   {
    a=new  int(100);
   }
     cperson & operator=(const cperson&ps)
  {
              return *this;
  }
      ~cperson()
      {
    delete a;
     a=NULL;
      };
cperson ps;
cperson ps1;
ps1=ps;

 該程序會崩潰,原因是對象中含有指針,當用一個對象給另一個對象賦值的時候,又出現了兩個指針使用同一塊地址空間的問題,那麽如何解決呢?

我們可以通過先刪除原來指針的空間,再為被賦值的對象中的指針重新分配空間,如下述代碼:

class cperson
{
public :int *a;
public :
 cperson()
      {
       a=new  int(100);
      }
     cperson & operator=(const cperson&ps)
  {
         delete this->a;
              this->a=new int (*(ps.a));
              return *this;

  }
      ~cperson()
      {
    delete a;
    a=NULL;
      };
cperson ps;
cperson ps1;
ps1=ps;

通常大家會對拷貝構造函數和賦值函數混淆,這兒仔細比較兩者的區別:

1)拷貝構造函數是一個對象初始化一塊內存區域,這塊內存就是新對象的內存區,而賦值函數是對於一個已經被初始化的對象來進行賦值操作。

2)一般來說在數據成員包含指針對象的時候,需要考慮兩種不同的處理需求:一種是復制指針對象,另一種是引用指針對象。拷貝構造函數大多數情況下是復制,而賦值函數是引用對象

3)實現不一樣。拷貝構造函數首先是一個構造函數,它調用時候是通過參數的對象初始化產生一個對象。

賦值函數則是把一個新的對象賦值給一個原有的對象,所以如果原來的對象中有內存分配要先把內存釋放掉,而且還要檢察一下兩個對象是不是同一個對象,如果是,不做任何操作,直接返回。

!!!如果不想寫拷貝構造函數和賦值函數,又不允許別人使用編譯器生成的缺省函數,最簡單的辦法是將拷貝構造函數和賦值函數聲明為私有函數,不用編寫代碼。:

所以如果類定義中有指針或引用變量或對象,為了避免潛在錯誤,最好重載拷貝構造函數和賦值函數。

一句話記住三者:對象不存在,且沒用別的對象來初始化,就是調用了構造函數;

    對象不存在,且用別的對象來初始化,就是拷貝構造函數(上面說了三種用它的情況!)

      對象存在,用別的對象來給它賦值,就是賦值函數。

本文轉載於:http://blog.csdn.net/zcyzsy/article/details/52132936

C++中的構造函數,拷貝構造函數,賦值函數