C++程式設計思想筆記之三 (轉)

worldblog發表於2007-12-13
C++程式設計思想筆記之三 (轉)[@more@]

第7章  常量

一、值替代:用C 語言進行設計時,預可以不受限制地建立宏並用它來替代值。因為前處理器只做文字替代,它既沒有型別檢查思想,也沒有型別檢查工具,所以前處理器的值替代會產生一些微小的問題,這些問題在C + +中可透過使用const而避免。C + +中的const 預設為內部連線,也就是說,const 僅在const 被定義過的裡才是可見的,而在連線時不能被其他編譯單元看到。當定義一個常量const時,必須賦一個值給它,除非用extern作了清楚的說明:
extern const bufsize ;C + +通常並不為const分配空間,相反它把這個定義儲存在它的符號表裡。當const 被使用時,它在編譯時會進行常量摺疊.

對常量進行分配的兩個情況:1、上面的extern 強制進行了儲存空間分配 (內部連線的常量不要分配地址,而外部連線的常量需要分配地址) 2、另外還有一些情況,如取一個const的地址,也要進行儲存空間分配 。對於在大量場合使用的內部資料型別,包括常量,編譯器都能常量摺疊。當然,絕對不為任何const分配儲存是不可能的,尤其對於複雜的結構。這種情況下,編譯器建立儲存,這會阻止常量摺疊。這(有時會給常量分配記憶體)就是const為什麼必須預設內部連線,即連線僅在特別編譯單元內的原因;否則,由於眾多的const在多個cpp檔案內分配儲存,容易引起連線錯誤,連線程式在多個檔案裡看到同樣的定義就會“抱怨”了(發現同名的廠量)。然而,因為const預設內部連線,所以連線程式不會跨過編譯單元連線那些定義,因此不會有衝突。(C預設const 是外部連線的)

當處理const指標時,編譯器仍將努力阻止儲存分配並進行常量摺疊。我們可以把一個非const 物件的地址賦給一個const指標,因為也許有時不想改變某些可以改變的東西。然而,不能把一個const物件的地址賦給一個非const指標,因為這樣做可能透過被賦值指標改變這個const指標。當然,總能用型別轉換強制進行這樣的賦值(int *a=(int*)&e強行取得地址),但是,這不是一個好的程式設計習慣,因為這樣就打破了物件的const屬性以及由const提供的性。

引數和返回值:用const限定函式引數及返回值是常量概念另一個容易被混淆的地方。如果以值傳遞物件時,對(函式的者)來講,用const限定沒有意義(它意味著傳遞的引數在函式里是不能被修改的),但是對函式的設計者來說,卻有著一定的作用,保證某個引數不會改變。所以它其實是函式建立者的工具,而不是函式呼叫者的工具。為了不使呼叫者混淆,在函式內部用const限定引數優於在參數列裡用const限定引數。如果以常量返回使用者定義型別的一個物件的值,這意味著返回值不能被修改。如果傳遞並返回地址,const將保證該地址內容不會被改變。對於內部資料型別int,float,char等基礎資料型別,不包括指標和結構、陣列)來說,返回值是否是常量並沒有關係,所以返回一個內部資料型別的值時,應該去掉const從而使使用者程式設計師不混淆。處理使用者定義的型別時,返回值為常量是很重要的。如果一個函式返回一個類物件的值,其值是常量,那麼這個函式的返回值不能是一個左值(即它不能被賦值,也不能被修改)。返回一個內部資料型別的值時,const沒有意義的原因是:編譯器已經不讓它成為一個左值(因為它總是一個值而不是一個變數)。僅當返回使用者定義的型別物件的值時,才會出現上述問題。如果傳遞或返回一個指標(或一個引用),使用者取指標並修改初值是可能的。如果使這個指標成為常量指標,就會阻止這類事的發生,這是非常重要的。事實上,無論什麼時候傳遞一個地址給一個函式,我們都應該儘可能用const修飾它,如果不這樣做,就使得帶有指向const的指標函式不具備可用性。是否選擇返回一個指向const的指標,取決於我們想讓使用者用它幹什麼.

由於引用的語法(看起來像值傳遞)的原因,傳遞一個臨時物件給帶有一個引用的函式是可能的,但不能傳遞一個臨時物件給帶有一個指標的函式----因為它必須清楚地帶有地址。所以,透過引用傳遞會產生一個在C 中不會出現的新情形:一個總是常量的臨時變數,它的地址可以被傳遞給一個函式。這就是為什麼臨時變數透過引用被傳遞給一個函式時,這個函式的引數一定是常量(const)引用。在函式引數中使用常量引用特別重要。這是因為我們的函式也許會接受臨時的物件,這個臨時物件是由另一個函式的返回值創立或由函式使用者顯式地創立的。臨時物件總是不變的,因此如果不使用常量引用,引數將不會被編譯器接受。 f(1)

二、集合:const可以用於集合(陣列) const int a={1,2,3,4},但編譯器不能把一個集合存放在它的符號表裡,所以必須分配記憶體。在這種情況下,const 意味著“不能改變的一塊儲存”。然而,其值在編譯時不能被使用,因為編譯器在編譯時不需要知道儲存的內容,只有在執行的時候才知道(雖然早已確定它的值,但是不知道它的存放位置阿)!

三、類裡的const 和enum

起初讀者可能認為合乎邏輯的選擇是把一個const放在類裡。但這不會產生預期的結果。在一個類裡,const恢復它在C 中的一部分意思。它在每個類物件裡分配儲存並代表一個值,這個值一旦被初始化以後就不能改變。在一個類裡使用const的意思是“在這個物件壽命期內,這是一個常量”。然而,對這個常量來講,每個不同的物件可以含一個不同的值。在一個類裡建立一個const 時,不能給它初值。這個初始化工作必須發生在建構函式里,並且,要在建構函式的某個特別的地方。因為const必須在建立它的地方被初始化,所以在建構函式的主體裡,const必須已初始化了,否則,就只有等待,直到在建構函式主體以後的某個地方給它初始化,這意味著過一會兒才給const初始化。

四、const 物件和成員函式

編譯器強調物件為const的,因此它必須保證物件的資料成員在物件壽命期內不被改變。可以很容易地保證公有資料不被改變,但是怎麼知道哪個成員函式會改變資料?又怎麼知道哪個成員函式對於const物件來說是“安全”的呢?如果宣告一個成員函式為const函式,則等於告訴編譯器可以為一個const物件呼叫這個函式。一個沒有被特別宣告為const的成員函式被看成是將要修改物件中資料成員的函式,而且編譯器不允許為一個const物件呼叫這個函式。。僅僅宣告一個函式在類定義裡是const的,不能保證成員函式也如此定義,所以編譯器迫使程式設計師在定義函式時要重申const說明。(const已成為函式識別符的一部分,所以編譯器和連線程式都要檢查const)。

五、 volatile

volatile的語法與const是一樣的,但是volatile的意思是“在編譯器認識的範圍外,這個資料可以被改變”。不知何故,環境正在改變資料(可能透過多工處理),所以,volatile告訴編譯器不要擅自做出有關資料的任何假定—在期間這是特別重要的。如果編譯器說:“我已經把資料讀進暫存器,而且再沒有與暫存器接觸”。一般情況下,它不需要再讀這個資料。但是,如果資料是volatile修飾的,編譯器不能作出這樣的假定,因為可能被其他程式改變了,它必須重讀這個資料而不是最佳化這個程式碼。

第8章  行內函數

一、行內函數和編譯器

為了理解內聯何時有效,應該先理解編譯器遇到一個行內函數時將做什麼。對於任何函式,編譯器在它的符號表裡放入函式型別(即包括名字和引數型別的函式原型及函式的返回型別)。另外,編譯器看到行內函數和行內函數的分析沒有錯誤時,函式的程式碼也被放入符號表。程式碼是以源程式形式存放還是以編譯過的指令形式存放取決於編譯器。呼叫一個行內函數時,編譯器首先確保呼叫正確,即所有的引數型別必須是正確型別或編譯器必須能夠將型別轉換為正確型別,並且返回值在目標表示式裡應該是正確型別或可改變為正確型別。當然,編譯器對任何型別函式都是這樣做的,這與前處理器顯著不同,因為前處理器不能檢查型別和進行轉換。假如所有的函式型別資訊符合呼叫的上下文的話,行內函數程式碼就會直接替換函式呼叫,消除了呼叫的開銷。假如行內函數也是成員函式,物件的地址(this)就會被放入合適的地方,這當然也是前處理器不能執行的。

二、標誌貼上

標誌貼上在寫程式碼時是非常有用的。它讓我們設兩個識別符號並把它們貼上在一起自動產生一個新的識別符號。

#define  FIELD(A) char* A##_string
main()
{
  FIELD(a);
 
  a_string ="aaaa";
  cout<}


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992750/,如需轉載,請註明出處,否則將追究法律責任。

相關文章