最近由於工作的需要,讓我重拾c++程式語言;重溫了bjarne stroustrup的大作《the c++ programming language》,仍然還有很多東西不是十分明白,但還是希望能夠把所學的經驗總結下來。雖然這本書不適合c++語言初學者,但是書中的很多細節都能讓乙個有經驗的程式開發人員獲益匪淺。
其中關於本書的第二部分「抽象機制」(abstraction mechanisms)在精讀了三遍之後,我個人覺得作者在寫這部分的時候有乙個主線就是「如何能讓開發人員有效地設計和開發類(乙個類或類層次結構)」。圍繞這個主線,看下本書第二部分的目錄結構,也會大致猜出為什麼作者要把章節的順序安排成這個樣子。
類層次結構的基礎當然是如何有效地定製乙個類;第十章第三節做了如下的描述:
[quote]1. 建構函式【與析構函式】(方括號部分是我自己加的)
2. 一組類成員檢視函式(const標記)
3. 一組類成員操作函式,當然這部分也包括運算子過載,使得操作起來感覺更自然
4. 一組隱式定義的函式,可以使定義類自由地複製(拷貝建構函式和拷貝賦值操作)
5. 乙個與該定義相關的異常類,用於通過異常類報告或者處理出錯的情況[/quote]有效地這樣的乙個描述針對設計乙個類顯然具有典型意義,但是類定製與類的設計並不僅限於此。在沒有考慮更深的類層次結果設計之前,上述的每一步都值得**一番。
[b]1. 建構函式【與析構函式】[/b]
除了考慮預設建構函式,成員應該如何初始化以及如何銷毀之外,應該還需要考慮這個類例項物件的建立方式與對應的銷毀方式。那麼到底有哪些建立及相應的銷毀方式呢?bjarne stroustrup給出如下幾種方式:
[quote]a. 乙個命名的自動物件,每次程式執行到其宣告時建立、程式離開它所出現的塊時銷毀;
b. 乙個堆物件,通過new建立,通過delete銷毀(注意:一定要成對使用,如果在呼叫new時,有中括號,相應地delete必須加中括號。簡單地講,用delete銷毀陣列時,會有記憶體洩漏產生,那麼用delete銷毀乙個非陣列物件時,更會招致記憶體洩漏或「不確定性為」更大的麻煩。
c. 乙個非靜態成員物件,作為另乙個類物件成員,在它作為成員的那個物件建立或銷毀時,它隨之被建立和銷毀
d. 乙個陣列元素,在它作為元素的那個陣列被建立和銷毀的時候建立和銷毀;
e. 乙個區域性靜態物件,在程式執行第一次遇到它的宣告時建立一次,在程式終止時銷毀一次;(注意,static關鍵字的語義,一旦靜態變數被建立,其生命週期與程式生命週期相同)
f. 乙個全域性物件、名字空間物件,類靜態物件,它們只在「程式開始時」建立一次,在程式終止時銷毀一次。(這裡需要引起注意的是,靜態變數的跨編譯單元的初始化次序是未定義的,因此《effective c++》給出的建議是用區域性靜態物件替換非區域性靜態物件。)
g. 乙個臨時物件,作為表示式求值的一部分被建立,在它所出現的那個完整表示式的最後被銷毀。通常情況下,我們盡量避免臨時物件的產生,要知道浪費在臨時物件的建立與銷毀的開銷對於那些效能要求嚴格的應用程式來說,的確不是什麼好的主意。
h. 乙個在分配操作中由所提供的引數控制,在通過使用者提供的函式獲得的儲存裡放置的物件。
i. 乙個union成員,它不能有建構函式和析構函式。[/quote]
在進行詳細設計時,必須考慮到類物件與類物件之間的耦合關係或者依賴關係,因為這些關係在很大程度上決定了這種類建立或者銷毀的方式。
針對a方式來說,這種方式能夠幫助我們設計管理資源的類,因為通過這種方式即使在丟擲異常的情況下,也能釋放掉類管理的資源。而利用a方式的技巧,c++賦予了乙個非常好聽的名字「資源獲得即初始化」(resource acquisition is initialization: raii)。當然方式的利用方式的具體表現形式c++提供了大致兩種:
i. 建立auto_ptr, std::tr1::shared_ptr;
ii. 建立管理資源對應的類,如:
class file_ptr
file_ptr(file *pp)
~file_ptr()
operator file*() //函式呼叫
};
乙個類的建構函式建立時,還需要進一步考慮如何初始化成員變數和初始化的次序;建議使用成員初始化列表,為什麼?這裡的主要原因是因為拷貝建構函式和拷貝複製操作的語義引起的;簡單地看下面的**:
#include
#include
#include
using namespace std;
class phonenumber ;
class a
a::a(const string& name, const string& address, const list& phones)
上面的這個建構函式定義簡直再熟悉不過,當然a物件會帶有你期望初始化的值,但不是最佳做法。為什麼呢?實際上,在a建構函式內,thename, theaddress和thephones都不是被初始化,而是被賦值。初始化發生的時間更早,發生於這些成員的預設構造函式呼叫之時。所以這些成員先是通過預設建構函式建立並初始化,然後通過a建構函式的實參進行拷貝操作完成賦值操作的。如果通過成員初始化列表的方式來進行的話,就等同於直接將實參傳遞給各成員的建構函式進行建立並初始化的工作。因此,通常情況下,後者的效率遠遠高於前者。
在成員初始化列表中的成員初始化的次序可以任意指定,但是建構函式不會理會這個你指定的次序,而總是按照類變數在其類宣告中的次序依次進行。
在上面的描述中,我提及到了預設建構函式,好吧,c++真的是幫助開發人員做了許多內部工作。如果任何乙個類在宣告時並未構造任何實際的建構函式的話,c++會替我們建立乙個編譯器產生的無參建構函式,這個建構函式就被稱為預設建構函式。一旦類宣告中包含有其他帶有引數的建構函式,預設函式就不會被建立了。這個原因很簡單,編譯器通過讀取類宣告,知道它如果再幫你產生這樣乙個預設建構函式無異於畫蛇添足。除了預設建構函式外,c++編譯器還會幫我們預設建立拷貝建構函式,拷貝賦值操作和析構函式,如果這些都沒有在類宣告中宣告的話。針對拷貝建構函式、拷貝賦值操作和析構函式,我會在下面講解中更詳細的描述我的總結。
[b]拷貝建構函式與拷貝賦值操作[/b]
class order ;
order o1; // call default constructor
order o2(o1); // call copy constructor
o1 = o2; // call copy assignment operator
order o3 = o2; // call copy constructor
幸運的是,這兩者還是可以區別開來的,雖然看上去感覺很容易迷惑。如果乙個新物件被定義(例如上述語句中的o3),一定會有個建構函式被呼叫,不可能呼叫賦值操作,如果沒有新物件被定義(例如上述語句中o1=o2),就不會有建構函式被呼叫,那麼當然就是賦值操作被呼叫。
拷貝建構函式在c++語言中絕對需要引起注意,因為這個函式如果稍微不加注意,便會引起不必要的麻煩。通常編譯器產生的拷貝建構函式和拷貝賦值操作都是淺拷貝。所謂淺拷貝的語義是memwise copy。簡單點說,會複製類物件中的每乙個成員。如果類宣告中,只是包含一些簡單的內建型別,如int,double等,淺拷貝的語義是正確的。但是一旦類中涉及指標變數或者引用變數,淺拷貝的語義就是簡單地拷貝指標的內容而不會拷貝指標(引用)所指向(引用)的內容。
看起來淺拷貝的語義是正確的!難道這種語義針對指標成員變數或者引用成員變數,會引起什麼問題嗎?當然會引起問題,而且引起的問題不小。看下面的**:
class name;
class table
~table()
name* lookup(const char *);
bool insert(name*);
};void h()
觀察上述**,t2=t1呼叫了table的拷貝建構函式,由於p是乙個name指標,所以t2當中的p會指向t1物件中p所指向的name(預設為15個大小的陣列),這個陣列中的內容不會被拷貝兩份,當t1物件和t2物件相繼被銷毀時,table的析構函式會相繼被呼叫兩次,那麼delete就會再次試圖銷毀已經被銷毀的name陣列。這就會使程式執行到乙個不確定的行為,後果很嚴重。所以,如果類宣告中包含了指標(引用)成員變數時,建議開發人員視情況自己定義拷貝建構函式與拷貝賦值操作。
有時候我們對c++編譯器預設產生這些建構函式的幫助實在是盛情難卻,但的確它們的產生又給程式造成了影響。當然,沒有冒犯編譯器大人的意思,那麼請用private修飾拷貝建構函式和拷貝賦值操作吧,這樣就表示你給編譯器乙個明顯的訊號,告訴它不要為我的類產生這兩個函式了。注意,上面的這句話確切說是有點錯誤的。不是編譯器不自動產生,還是會自動產生的,但是通過private修飾,可以成功地組織別人呼叫它)。當然這麼做,也不絕對安全,為什麼呢?因為一些friend類或者成員函式仍然可以訪問這個類的私有成員。不過,到現在為止,還沒有什麼其他更好的辦法。如果你看到了更好的辦法,一定要告訴我!
[b]析構函式[/b]
析構函式相對來說注意的問題相對來說簡單,總結呢主要有兩個:
1. 多型基類宣告析構函式時,一定要加virtual修飾
2. 析構函式中一定要捕獲所有異常,不要讓異常逃離析構函式
3. 針對建構函式和析構函式,不要在呼叫過程中使用virtual函式。
上述三個注意事項請參考《effective c++》
建立乙個類
c 是一門物件導向的程式語言,而物件導向的基礎就是類 使用c 建立乙個student類 class student 學生類 輸出學生的資訊 void outputstudent void void student input char name,int age,int no void student...
建立乙個CTabView類
標頭檔案 pragma once ctabview class ctabview public cctrlview 原始檔 include stdafx.h include tabview.h ctabview implement dyncreate ctabview,cctrlview ctabv...
建立乙個Date類
標頭檔案 1 data class.h ifndef i date ed define i date ed include include using namespace std year應當是1800到2200之間的整數 month必須是1到12之間的整數 day必須是1到給定 月的天數之間的整數...