高品質C++編程指南學習筆記第9章—thanks to林銳

來源:互聯網
上載者:User

第九章     類的建構函式、解構函式與賦值函數

類對象之間的賦值只是對資料成員賦值。

每個類只有一個解構函式和一個賦值函數,但可有多個建構函式(包含一個拷貝建構函式,其他的稱為普通建構函式)。這幾個函數都不被繼承。對任意一個類A,若不想編寫上述函數,C++編譯器將自動為A產生四個預設的函數。有預設的,為什麼還要程式員編寫:

1)若使用“預設的無參建構函式”和“預設的解構函式”,等於放棄了自主“初始化”和“清除”的機會。

2)“預設的拷貝建構函式”和“預設的賦值函數”均採用“位拷貝”而非“值拷貝”的方式來實現,若類中含有指標變數,這兩個函數將出錯。

class String

{

public:

         String(const char *str = NULL);        //普通建構函式

         String(const String &other);             //拷貝建構函式

         ~String(void);                                        //解構函式

         String & operator =(const String &other);       //賦值函數

private:

         char *m_data;                   //用於儲存字串

}

9.1建構函式的初始化表

        
建構函式特殊在其初始化方式(初始化表和函數體內賦值兩種方式)和執行時間。

        
建構函式有個特殊的初始化方式叫“初始設定式表”(簡稱初始化表)。初始化表位於函數參數後,卻在函數體{}前,說明該表裡的初始化工作發生在函數體內的任何代碼被執行之前。建構函式的初始化表使用規則:

1) 
若類存在繼承關係,衍生類別必須在其初始化表裡調用基類的建構函式。如:

B::B(int x, int y) : A(x) {……}//初始化表裡調用A的建構函式。若基類含無參建構函式,則不必在衍生類別的初始化表裡調用基類建構函式,但構造衍生類別時會調用基類的無參建構函式。所以最好還是寫上。

         2)類的const常量只能在初始化表裡被初始化,因為它不能在函數體內用賦值的方式來初始化。

         3)類的資料成員的初始化可以採用初始化表或函數體內賦值兩種方式,這兩種方式的效率不完全相同。非內部資料類型的成員對象應採用第一種方式初始化,以擷取更高的效率。對內部資料類型,兩者幾乎無差別。

B::B(const A &a) : m_a(a){…}類B的建構函式在其初始化表裡調用類A的拷貝建構函式,將成員對象m_a初始化。B::B(const
A &a){m_a=a;}先建立m_a對象,再調用A的賦值函數,將參數a賦給m_a。

9.2構造和析構的次序

        
構造從類層次的最根處開始,在每層中,先調用基類的建構函式,然後調用成員對象的建構函式。析構則嚴格按照與構造相反的次序進行。

        
成員對象的初始化次序不受它們在初始化表中次序的影響,只由成員對象在類中聲明的次序決定。

9.3樣本:類String的建構函式與解構函式

//String的普通建構函式

String::String(cosnt char *str)

{

         if (NULL == str)

{

         m_data = new char[1];

         *m_data = ‘\0’;

}

else

{

         int length = strlen(str);

         m_data = new char[length+1];

         strcpy(m_data, str);

}

}

//String的解構函式

String::~String(void)

{

         delete [] m_data;//因m_data是內部資料類型,也可寫成delete m_data。

}

9.4不要輕視拷貝建構函式與賦值函數

1)不主動編寫拷貝建構函式和賦值函數,編譯器將以“位拷貝”的方式自動產生預設的函數。若類中有指標變數,那麼這兩個預設的函數就隱含了錯誤。“位拷貝”又稱“淺拷貝”,拷貝的是地址;而“值拷貝”又稱“深拷貝”,拷貝的是內容。以類String的兩個對象a,b為例,假設a.m_data的內容為“hello”,b.m_data的內容為“world”。

將a賦值給b,若預設賦值函數,則執行的是“位拷貝”:b.m_data = a.m_data。這將造成三個錯誤:

Ø 
b.m_data原有的記憶體沒被釋放,造成記憶體泄露。

Ø 
b.m_data和a.m_data執行同一記憶體,a或b任一方改變都會影響第三方。

Ø 
在對象被析構時,m_data被釋放兩次。

int *p = new int;int *q=p;   ---------淺拷貝
int *p = new int;int *q = new int;*p = *q;    --------深拷貝

         2)拷貝建構函式和賦值函數易混淆。區分:拷貝建構函式是在對象被建立時調用的,而賦值函數只能被已經存在了的對象調用。試著區分以下語句:

         String a(“Hello”);     String b(“World”);

         String c = a;                                           //調用了拷貝建構函式,最後寫成c(a);

                    C = b;                                           //調用了賦值函數。

9.5樣本:類String的拷貝建構函式與賦值函數

//拷貝建構函式

String::String(const String &other)

{

         //允許操作other的私人成員

         int length = strlen(other.m_data);

         m_data = new char[length+1];

         strcpy(m_data, other.m_data);

}

//賦值函數

String &String::operator =(const String &other)

{

         //1)檢查自賦值

         if (this==&other)

                   return *this;

         //2)釋放原有的記憶體資源

         delete []m_data;

         //3)分配新的記憶體資源,並複製內容

         int length = strlen(other.m_data);

         m_data = new char[length+1];

         strcpy(m_data, other.m_data);

         //4)返回本對象的引用

         return *this;

}

1)第一步,檢查自賦值。有人可能會寫出間接指派陳述式,如:

//內容自賦值                                                                            //地址自賦值

b = a;                                                                                            b = &a;

……                                                                                            ……

c = b;                                                                                             a = *b;

……

a = c;

為何要避免出現自賦值,因第二部要delete,自殺後還能複製自己嗎?為何要delete,怕原來分配的記憶體不夠用,第三步分配足夠記憶體。注意不要將檢查自賦值的if語句if(this==&ohter)寫成if(*this==other)。

2)第二步,釋放原記憶體,若不釋放,將會造成記憶體泄露。

3)第三步,分配新記憶體並分配字串。注意strlen返回的是有效字串長度,不包含’\0’。

4)第四步,返回本對象的引用,目的是為了實現像a=b=c這樣的鏈式運算式。注意不要將return *this寫成return this。

9.6偷懶的辦法處理拷貝建構函式與賦值函數

        
若不想編寫拷貝建構函式與賦值函數,又不允許別人使用編譯器產生的預設函數,偷懶的方法是:將建構函式與賦值函式宣告為私人函數,不用編寫代碼。若有人試圖調用這倆函數,編譯器將指出錯誤。

9.7如何在衍生類別中實作類別的基本函數(即構造、析構、賦值函數)

        
基類的構造、析構和賦值函數不被繼承。若類之間存在繼承關係,如何編寫這些函數:

Ø 
衍生類別的建構函式應在其初始化表裡調用基類的建構函式。

Ø 
基類的解構函式應為virtual,即覆蓋,否則會被隱藏。最好將衍生類別解構函式也設為virtual,以便後續的再繼承。

Ø 
在編寫衍生類別的賦值函數時,要對基類的資料成員重新賦值。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.