c++編譯器幫我們做了些什麼

我們平時編寫代碼都是使用的集成開發工具,很多時候都忽略了c++中隱藏的成員函數。具體來說,c++編譯器會自動提供一下幾個基本函數:

●默認構造函數,如果沒有定義構造函數

●複製構造函數,如果沒有定義

●析構函數,如果沒有定義

●賦值操作符,如果沒有定義

●地址操作符

class Stack
{
   private:
      char *str;
   public:
      Stack(); //默認構造函數
      ~Stack();//析構函數
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      Stack (const Stack &); //複製構造函數 
      Stack (char *str )    // 構造函數
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
      Stack & Stack::operator=(const Stack  &); //賦值操作符
      Stack & operator =(const Stack &);  //地址操作符
      void Add() // 成員函數
};

剛開始學習c++的時候見的比較多的也就默認構造函數和析構函數了,其他的幾個很少用到,甚至在代碼中沒有見到過,今天在這裏梳理一下。

1、默認構造函數。如果沒有提供任何構造函數,c++將創建一個沒有任何參數和行爲的默認構造函數。如果定義了構造函數,c++將不會定義默認構造函數。如果希望在創建對象時顯式地對它進行初始化,或需要創建對象數組時,則必須顯式地定義默認構造函數。這裏需要說明一下,在創建對象數組時,這個類必須提供默認構造函數,因爲初始化對象數組的方案是,首先使用默認構造函數創建數組元素,然後花括號中的構造函數將創建臨時對象,然後將臨時對象的內容複製到相應的元素中。

   有兩種方式可以用來定義默認構造函數,一種是給已有構造函數的所有參數提供默認值:

Stock(const char* a = "Error", int m = 0,double dr = 0.0);

另一種方式是通過函數重載來定義另一個構造函數——一個沒有參數的構造函數:

Stock();

   由於只能有一個默認構造函數,因此不要同時採用這兩種方式。


2、複製構造函數

又叫拷貝構造函數。它用於將一個對象複製到新創建的對象中。也就是說,它用於初始化過程中,而不是常規的賦值過程中。類的複製構造函數原型通常如下:

class_name (const class_name &)

它接受一個指向類對象的常量引用作爲參數。

這裏需要注意複製構造函數和賦值操作符的區別,以免混淆兩者的功能。


1)新建一個對象並將其初始化爲同類現有對象時,複製構造函數都將被調用。每當程序生成了對象副本時,編譯器都將使用複製構造函數,具體說,當函數按值傳遞對象或函數返回對象時,都將使用複製構造函數。假設motto是一個Stack類對象,下面幾種都會調用複製構造函數。

Stack ditto(motto);
Stack ditto = motto;
Stack ditto = Stack(motto);
Stack *pDitto = new Stack(motto);


2)默認的複製構造函數逐個複製非靜態成員(淺複製),複製的是成員的值。如果成員本身是類的對象,則將使用類的複製構造函數複製類的成員對象,靜態函數不受影響,因爲靜態函數屬於類本身,不屬於任何一個類對象。這裏需要注意的問題:對資源的複製。我們需要實現深度複製。比如說,Stack類對象ditto,該對象的str指向一塊堆內存。現在利用它初始化另一個對象motto,這裏如果簡單的對其進行淺複製,則ditto.str和motto.str所保存的內存地址是同一塊內存,這顯然是不合適的。因此,這裏我們應該在複製的同時給motto.str重新分配一塊內存,然後將diito.str字符串複製過來,這就實現了深度複製。

Stack::Stack (const Stack & st)
{
    int len  = strlen(st.str);
    str = new char[len + 1];
    str[len] = '\0';
    strcpy(str,st.str);
}


3、析構函數

析構函數用來完成資源清理工作。一個類中只能有一個析構函數,且析構函數也可以沒有返回值和聲明類型,不帶任何參數。

需要注意的是,我們不應該在代碼中顯式的調用析構函數。如果調用的是靜態存儲類對象,則其析構函數將在程序結束時自動被調用。如果創建的是自動存儲類對象,則其析構函數將在程序執行完代碼塊時自動被調用。此外,應注意析構函數和構造函數對堆資源的管理。用new申請的應該用delete釋放,用new[]申請的應該使用delete[]釋放。爲了簡單,建議都是用new[]和delete[]。


4、賦值操作符

函數原型如下:

Class_name & Class_name::operator= (const Class_name &);

將已有的對象賦值給另外一個對象,將使用重載賦值操作符。

這裏應該要弄清楚初始化和賦值的區別,不然不好弄清楚賦值操作符和複製構造函數的區別:賦值操作符所作用的兩個對象都是已有的,而複製構造函數所作用的兩個對象一個是已有的,一個是全新的。

與複製構造函數類似,賦值操作符的隱式實現也對成員進行逐個複製,如果成員本身就是類對象,則程序將使用爲這個類定義的賦值操作符來複制該成員,但靜態數據成員不受影響。同時,這裏和複製構造函數有一個相似的問題,就是對資源的開闢。

Stack & Stack::operator= (const Stack & st)
{
   if (this == &st)
       return *this;
                                                                                                                    
   delete[] str;
                                                                                                                    
   int len = strlen(st.str);
   str = new char[len+1];
   str[len] = '\0';
   strcpy(str,st.str);
    return *this;
}


這裏需要注意繼承的問題,MSDN上有一句:

All overloaded operators except assignment (operator=) are inherited by derived classes

如果派生類中聲明的成員與基類的成員同名,那麼,基類的成員會被覆蓋或隱藏,哪怕基類的成員與派生類的成員的數據類型和參數個數都完全不同所以,賦值運算符重載函數不是不能被派生類繼承,而是被派生類的默認賦值運算符重載函數給覆蓋了。

附上:重載、覆蓋和隱藏的區別?

函數的重載是指C++允許多個同名的函數存在,但同名的各個函數的形參必須有區別:形參的個數不同,或者形參的個數相同,但參數類型有所不同。


覆蓋(Override)是指派生類中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體 (花括號中的部分)不同,當派生類對象調用子類中該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋,但要求有virtual關鍵字。


隱藏是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

1) 如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。

2) 如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。


參考鏈接:

http://www.cnblogs.com/sujz/archive/2011/05/12/2044365.html



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章