常變數、常物件、常引用、指向常物件或常變數的指標等在定義時都使用了const關鍵字,這是C++語言引入的一種資料保護機制,稱為const資料保護機制。例如通過const關鍵字主動地將被調函式形參進行限定,限定被調函式不能修改主調函式傳遞過來的資料。
下面通過一個計程車類(Taxi),更好的理解常成員和常函式成員:
class Taxi //定義計程車類 { private: int price; //計程車里程單價 int fare; //計程車收費總額 public: void SetPrice(int p) { price = p; } //設定里程單價 int GetPrice() { return price; } //獲取里程單價 void SetFare(int f) { fare = f; } //設定收費總額 int GetFare() { return fare; } //獲取收費總額 void AddFare(int f) { fare += f; } //累計收費總額 void Order() //執行一個叫車訂單 { int x = GetARand(5,100); //通過隨機數獲得一次訂單里程 AddFare(x*price); //計算一次訂單金額,加到收費總額裡 } Taxi(int p =0,int f=0) //帶預設形參值的建構函式 { price = p; fare = f; } }
一、常成員
在定義類時,使用const關鍵字進行限定的成員稱為常成員。資料成員和函式成員均可定義為常成員。
1、常資料成員
如果一個資料成員所儲存的數值在初始化以後不會改變,那麼可以將這個資料成員定義成常資料成員。換句話說,常資料成員只能在物件初始化時賦值,初始化後不得再修改。常資料成員和常變數的不同在於常資料成員在類中宣告時不得初始化,類中任何資料成員的初始化必須經過建構函式完成。
常資料成員的語法細則:
1)常資料成員定義:const 資料型別 常資料成員名;
2)初始化列表:為建構函式新增初始化列表是對常資料成員進行初始化的唯一途徑。語法形式如下:
建構函式名(形參列表):常資料成員名1(形參1),常資料成員名2(形參2),...
{
//函式體中對其他資料成員初始化
}
以計程車里程單價price為常資料成員為例:
const int price;
Taxi(int p=0;int f=0):price(p)
{
fare=f;
}
在建構函式頭後面新增初始化列表“:price(p)”是唯一能夠設定常資料成員price初始值的地方,其他任何地方都不得對price再次賦值。
3) 定義物件時初始化。定義含常資料成員類的物件時需要初始化,重點給出常資料成員的初始值。
程式設計師在設計類的時候,如果認為某個資料成員所儲存的數值在初始化以後不能在被修改,可以主動將其定義為常資料成員。
2、常函式成員
如果某個函式成員只需要讀取類中的資料成員(注意:不是常資料成員)的數值,而不會修改它們,那麼可以將其定義為常函式成員。
常函式成員定義語法形式:
1)行內函數。在類宣告部分直接定義的函式被當作行內函數處理,在函式頭後面加const關鍵字就可以將其定義為常函式成員。
示例:int GetPrice() const { return price; }
2) 非行內函數。此時需要在宣告和定義語句的函式頭後面分別加上const關鍵字。
示例:
類宣告中:int GetPrice() const;
類實現中:int Taxi::GetPrice() const { return price; }
常函式成員語法細則:
1)宣告、定義常函式成員須在函式頭後面加關鍵字const進行限定。
2)常函式成員只能讀取類中資料成員的數值,不能修改它們。
3)常函式成員只能呼叫其他常函式成員,以防止常函式通過其他函式間接修改資料成員。
4)通過常對像只能呼叫其常函式成員,以防止函式間接修改常物件的資料成員。
5)除了形參的個數和型別之外,還可以用關鍵字const區分類中的過載函式。
3、關於const過載類成員函式的問題
1)如何呼叫過載後的兩個函式?
const修飾的物件呼叫的是使用const修飾的方法,非const物件呼叫的是非const的方法。
2)過載是如何實現的?
經查資料,這些函式的引數中其實還有一個隱式的this指標,在呼叫的時候傳入。因為在非const修飾的函式中,this指標也是非const的。在const修飾的函式中,this指標是const修飾的。所以非const物件呼叫函式時,this指標是非const的,呼叫非const函式。const物件呼叫函式時,this指標是const的,呼叫的是const的函式。
注意:如果一個函式用const修飾了,但是這個函式沒有實現過載,那麼非const物件和const物件都能呼叫這個函式。
特別注意的是,const修飾的物件只能呼叫const修飾的函式。
二、靜態成員
物件導向的程式設計希望用類管理所有程式程式碼,使得程式中沒有遊離在類外的全域性變數和外部函式。對於一些共用的全域性變數或外部函式,可以將它們規劃到某個具有關聯關係的類中,與類中的其他成員一起進行統一管理。定義類時,使用關鍵字static進行限定的成員稱為靜態成員。資料成員和函式成員都可以定義為靜態成員。與靜態全域性變數、靜態區域性變數和靜態函式一樣靜態成員的定義與作用域密不可分。
1、靜態資料成員
靜態資料成員屬於一個類整體,不屬於某個物件。作用上相當於全域性變數,使用起來類似靜態區域性變數(靜態區域性變數作用域是某個函式,生存期是程式全域性)。
靜態資料成員的語法形式:
類宣告:static 資料型別 靜態資料成員名;//注意這裡只是對靜態資料成員的宣告,未繼進行定義。
初始化:資料型別 類名::靜態資料成員名 = 某值;//注意,靜態資料成員的定義只能在類實現中,即類實現部分定義靜態資料成員。定義時可以初始化。(那麼在類定義時初始化的靜態資料成員如何分配的記憶體空間哪?又如何將該初始化值傳遞給呼叫該類的其他程式哪?)
示例:
類宣告:static int totalFare;
初始化:int Taxi::totalFare = 0;
靜態資料成員可以是私有的(只能被該類的成員函式訪問),也可以是公有的(可以被類外其他函式訪問修改)。
靜態資料成員的語法細則:
1)關鍵字static。在類中宣告靜態資料成員需使用關鍵字static進行限定,宣告時不能初始化。
2)定義和初始化。必須在類宣告的大括號後面對靜態資料成員進行定義,定義時不能加static關鍵字。定義時可以初始化。
3)在類的函式成員中訪問。類中的函式成員直接使用成員名訪問靜態資料成員,訪問時不受許可權約束。這一點和普通資料成員一樣。
4)在類外其他函式中訪問。在類外其他函式中訪問靜態資料成員需以“類名::靜態資料成員名”的形式訪問,或通過任何一個該類物件以"物件名.靜態資料成員"的形式訪問,或通過任何一個該類物件指標以“物件指標名->靜態資料成員名”的形式訪問。
私有靜態資料成員具有類作用域,只能在類內訪問。公有靜態資料成員具有檔案作用域,可以被本檔案的任何函式訪問,並且可通過類宣告將其作用域擴充套件到任何程式檔案。
5)記憶體分配。和全域性變數一樣,靜態資料成員也是靜態分配,被分配在靜態儲存區。靜態資料成員在程式載入後立即分配記憶體,知道程式執行結束退出時才被釋放。這一點與類中的其他不同資料成員時完全不同的。類定義多個物件,每個物件的普通資料成員都各自分配記憶體單元,但所有物件額靜態資料成員會共用一個記憶體單元。
2、靜態函式成員
可以將外部函式劃歸到某個具體關聯關係的類中,作為類的靜態函式成員進行管理。
靜態函式成員的語法形式:
類宣告:static 返回值型別 靜態函式成員名(形參列表);
類實現:返回值型別::靜態函式成員名(形參列表) { 函式體 }
示例:
類宣告:static void AddTotal(int f);
類實現: void Taxi::AddToal(int f) {totalFare += f; }
靜態函式成員的語法細則:
1)宣告時使用關鍵字static進行限定,定義時不能在使用關鍵字static。
2)類中的其他函式成員可以呼叫靜態函式成員。呼叫時直接使用函式名,不受訪問控制許可權約束。
3)在類外呼叫靜態函式成員需以“類名::靜態函式成員名”的形式訪問,或通過任何一個該類物件以"物件名.靜態函式成員"的形式訪問,或通過任何一個該類物件指標以“物件指標名->靜態函式成員名”的形式訪問。
類外呼叫靜態函式成員受訪許可權約束,私有靜態函式成員具有類作用域,只能在類內呼叫。公有靜態函式成員具有檔案作用域,可以被本檔案的任何函式呼叫,並且可通過類宣告將其作用域擴充套件到任何程式檔案。
4)靜態函式成員只能訪問類中的靜態資料成員,也就是不能訪問類中的非靜態資料成員。因為靜態函式成員可以在沒有物件定義的情況下被呼叫,此時靜態資料成員還沒有分配記憶體空間,不能訪問。同樣,靜態函式成員只能呼叫類中的靜態函式成員,不能呼叫非靜態函式成員。
5)靜態函式成員不能是行內函數,因為編譯器在編譯時會調整行內函數,可能會用到靜態資料成員中的資料,但此時靜態資料成員還未初始化,所訪問的資料是錯誤的。
3、靜態成員的應用
1)以類的形式管理全域性變數和外部函式
本質上靜態資料成員就是一個全域性變數,靜態函式成員就是一個外部函式。將它們改為靜態成員好處便於分類組織和管理程式程式碼。
2)將具有相同屬性值的成員定義成靜態資料成員
利用靜態資料成員公用記憶體單元的特點,可以將具有想用屬性的成員定義成靜態資料成員,這樣就有效減少記憶體佔用。
例如:每個計程車物件都有price里程單價這一資料成員,現實中相同的計程車里程單間一樣,這樣為每個計程車都設定一個price就顯得有些記憶體浪費,設定一個靜態資料成員price表示全部計程車類的里程單價,就會節省很多記憶體資源。