1. 程式人生 > >3-6 靜態資料成員與靜態成員函式

3-6 靜態資料成員與靜態成員函式

一、const關鍵字

如果把const放在變數型別名前,說明這個變數的值是保持不變的,該變數必須在定義時初始化,初始化後對它進行的任何賦值都是非法的。

當指標或者引用指向一個常量時,必須在型別名前使用const標識這個指標或者引用指向的“變數”為常量,沒有的話就是語法錯誤。如: const int x = 5;      const int* px = &x;     const int& rx = x;這樣一來,直接修改x是不可能的,通過*px或者rx修改x也是不可能的。當然,這個指標還能指向其他的地方,因為指標本身並沒有被標識為const的。比如,px = &y;

假如變數是一個非常量變數,而指標或者引用的型別名前使用了const,那麼,可以直接修改變數,不能通過指標或者引用修改變數。


如果想讓一個指標本身成為const的,就要在*後加const,即int* const p = &x;這個時候,p就不能再指向其他變數。假如x是非常量的,那它可以通過指標進行修改,因為x並沒有標識為const。當然,引用天生就是const的,它必須被初始化,而且初始化後就不能再指向新的變數。比如,int x = 5; int& r = x; r = y;第二句程式碼不會讓r指向y,而是讓x的值變成了y。

如果在函式介面(引數)中使用const,和在值、指標中使用是類似的。但是,這就更難讓函式返回指向這個引數物件的指標或者引用了。如果允許的話,客戶程式碼就有可能通過別名修改常量。比如,


class Point

{

    int x, y;

public:

    Point closestPointVal(const Point& pt)

    { if( x*x+y*y < pt.x*pt.x+pt.y*pt.y)

        return *this;

    else return pt;}

    Point* closestPointPtr(const Point& pt)

    {   return (x*x+y*y < pt.x*pt.x+pt.y*pt.y) ? this : &pt;}

   Point& closestPointRef(const Point& pt)


    {   return (x*x+y*y < pt.x*pt.x+pt.y*pt.y) ? *this : pt;}

};

第一個函式是返回值的,不管用不用const,都不會修改實參pt。而第二個函式可以返回指向pt實參的指標。客戶程式碼就可以使用這個指標修改實際引數物件的狀態。同樣,第三個函式返回的引用也能修改實參物件的狀態。假如這樣的函式是合法的,那麼就有可能誤用,誤用後如果出了錯,很難弄清是這裡出的錯,所以編譯程式直接就認為這樣的函式不合法。也就是,引數物件使用了const,而返回型別沒有,這是錯誤的。

C++提供了三種方法解決這個問題:1.引數不使用const;2.在成員函式內部,使用const_cast運算子移去const屬性;3.在另外合適的地方新增const。一個真正的程式設計師是不會放過任何一個機會,將設計人員的設計意圖傳達給客戶程式碼程式設計師和維護人員的。所以,假如某個引數沒有被修改,就要用const來明確說明。而使用const_cast也很糟糕,不易理解。所以只能使用第三種方法,在函式返回值前加上const,接收該返回值的指標或者引用也要是const的。這樣的話,返回值只能作為右值,不能作為左值。這樣的返回值不能呼叫它所在類的成員函式。

C++提供const關鍵字,不是為了保證一個變數不被修改,而是為了方便編譯程式和維護人員弄清楚一個實體在程式中是否被修改了。如果函式介面中宣告引數為const,我們就認為這個引數不會改變,如果沒有宣告為const,就認為這個引數一定被改變了,而不管函式到底有沒有改變這個引數。

其實,const還有一個含義,就是在函式的引數列表的“)”之後,函式體的“{”之前。這種用法說明函式不會修改目標物件的值。

二、靜態類成員

每建立一個類物件例項,都會為該例項建立一個單獨的資料成員集合。不管是將物件定義為有名的區域性變數,有名的全域性變數,或者使用new定義為未命名的動態變數,或者按值傳遞一個物件作為函式引數,或者從一個函式按值返回一個物件。而對於成員函式,它們的目的碼卻只產生一次。因為每個程式函式都有一個隱含的引數,即this指標。

1 用全域性變數作為類特性

類為物件例項提供了一個藍圖或者說是模板,類的成員變數都是這個類的所有例項的共性,類的每一個例項都有一個單獨的副本。但是有時我們需要為一個類的所有例項提供共有的資料成員副本,這比在每個類物件中維護單獨的副本能更有效的合理利用記憶體。最常見的例子就是對類的例項進行計數。假如這個計數變數count被定義在類中,在建構函式中對其加1,在解構函式中對其減1,似乎能實現計數作用。但是實際上卻是做不到的。因為count是類的成員變數,每個例項都有一個count,建構函式對其加1,只是對自己的count加了1。

使用全域性變數就能解決這個問題。但是將它用在大型程式中,存在一些問題,這些問題在前面都講過了。首先在客戶程式碼任意地方都可能使用count,增加了模組之間的依賴性。其次,全域性變數名增加了重名機會。最重要的是,count作為全域性變量出現時,維護人員不能很好的瞭解它是做什麼的,除非看註釋或者查閱大量其他程式碼。

2 static的含義

static和const一樣很複雜。

首先,將static用於一個全域性變數,是說明該全域性變數只對定義在同一個檔案中的函式可見。即使在另一個檔案中使用了extern,另一個檔案中的函式也不能訪問這個全域性變數。

static的第二個含義是用於一個函式前,說明該函式只能在同一個檔案中呼叫。

第三個含義是static用於函式的區域性變數。它表明該變數的值不會因為函式終止而消失,它會被儲存下來。再次呼叫該函式時,這個儲存下來的值會用來初始化該變數。

第四個含義就是用於類的成員變數。它表明對類的所有物件,這個資料成員都只有一個例項。這個例項被所有物件共有。static的成員變數可以是private、public、protected的。定義和訪問的語法也和其他資料成員一樣。

3 靜態資料成員的初始化

靜態資料成員在類規格說明外部被初始化,這一點和全域性變數相似。但是全域性變數可以被隱式地初始化為0,而靜態成員變數作為類的資料成員,必須被顯式地初始化,因為類的所有的資料成員都必須被顯式初始化。賦值和初始化存在著重要的區別,如果型別名後緊跟著變數名,就是初始化,如果變數名前沒有型別名,就是賦值。初始化對於公共的和非公共的靜態資料成員是合法的,但是對非公共的靜態資料成員進行賦值卻是不合法的。比如,如果count是private的,那麼Point::count = 0就是錯誤的。

靜態資料成員只能被初始化一次,所以它應該和其他的成員函式的定義一起放在.cpp檔案中,而不能放在標頭檔案中。一個靜態資料成員不能是聯合的成員,也不能是位域的類。聯合和位域都表示屬於某個特定物件的特殊記憶體用途,而靜態資料成員卻不屬於特定物件,它屬於整個類。

4 靜態成員函式

static的第五種含義是用在類的成員函式前,表明這個函式不訪問非靜態資料成員,它只能訪問它的引數、類的靜態資料成員、全域性變數。注意到靜態成員函式訪問的三種類型的資料,都不是描述物件狀態的。而在函式引數列表後面使用const表明該函式不會修改該函式的目標物件的資料成員。所以,一個靜態成員函式沒有必要在引數列表之後新增一個const。

靜態成員函式可以通過物件來呼叫,也可以直接使用類名來呼叫。因為它是類的特性,而不是物件的性質。

定義類的某個資料成員為靜態變數,以表明此全域性資料邏輯上屬於該類。定義類的成員函式為靜態函式,表明此全域性函式邏輯上屬於該類,而該函式只對靜態資料、全域性資料、引數進行操作。