類的靜態成員
有的時候類需要它的一些成員與類本身直接相關,而不是與類的各個物件保持關聯。
例如,一個銀行賬戶類可能需要一個數據成員來表示當前的基準利率。在此例中,我們希望利率與類關聯,而非與類的每個物件關聯。從實現效率的角度來看,沒必要每個物件都│儲存利率資訊。而且更加重要的是,一旦利率浮動,我們希望所有的物件都能使用新值。
宣告靜態成員
我們通過在成員的宣告之前加上關鍵字 static使得其與類關聯在一起。
和其他成員一樣,靜態成員可以是 public的或private 的。靜態資料成員的型別可以是常量、引用、指標、類型別等。
舉個例子,我們定義一個類,用它表示銀行的賬戶記錄∶
class Account{ public: void calculate() {amount += amount * interestRate;} static double rate() {return interestRate;} static void rate(double); private: std::string owner; double amount; static double interestRate; static double initRate(); };
類的靜態成員存在於任何物件之外,物件中不包含任何與靜態資料成員有關的資料。
因此,每個Account物件將包含兩個資料成員∶owner和amount。
只存在一個interestRate物件而且它被所有 Account 物件共享。
類似的,靜態成員函式也不與任何物件繫結在一起,它們不包含this指標。作為結果,靜態成員函式不能宣告成 const的,而且我們也不能在static函式體內使用this指標。這一限制既適用於 this 的顯式使用,也對呼叫非靜態成員的隱式使用有效。
使用類的靜態成員
我們使用作用域運算子直接訪問靜態成員:
double r; r = Account::rate(); // 使用作用域運算子訪問靜態成員
雖然靜態成員不屬於類的某個物件,但是我們仍然可以使用類的物件、引用或者指標來訪問靜態成員∶
Account ac1;
Account *ac2 = &ac1;
// 呼叫靜態成員函式rate的等價形式
r = ac1.rate(); // 通過Amount的物件或引用
r = ac1 -> rate(); // 通過指向Amount物件的指標
成員函式不用通過作用域運算子就能直接使用靜態成員∶
class Amount{ public: void calculate() {amount += amount * interestRate;} private: static double interestRate; // 其他成員與之前版本一致 }
定義靜態成員
和其他的成員函式一樣,我們既可以在類的內部也可以在類的外部定義靜態成員函式。
當在類的外部定義靜態成員時,不能重複static關鍵字,該關鍵字只出現在類內部的宣告語句∶
void Amount::rate(double rewRate){
interestRate = rewRate;
}
因為靜態資料成員不屬於類的任何一個物件,所以它們並不是在建立類的物件時被定義的。這意味著它們不是由類的建構函式初始化的。
而且一般來說,我們不能在類的內部初始化靜態成員。相反的,必須在類的外部定義和初始化每個靜態成員。和其他物件一樣,一個靜態資料成員只能定義一次。
靜態資料成員定義在任何函式之外。因此一旦它被定義,就將一直存在於程式的整個生命週期中。
我們定義靜態資料成員的方式和在類的外部定義成員函式差不多。我們需要指定物件的型別名,然後是類名、作用域運算子以及成員自己的名字∶
// 定義並初始化一個靜態成員
double Amount::interestRate = initRate();
這條語句定義了名為 interestRate 的物件,該物件是類 Account 的靜態成員,其型別是 double。
從類名開始,這條定義語句的剩餘部分就都位於類的作用域之內了。因此,我們可以直接使用 initRate 函式。注意,雖然 initRate 是私有的,我們也能用它初始化interestRate。和其他成員的定義一樣,interestRate的定義也可以訪問類的私有成員。
靜態成員的類內初始化
通常情況下,類的靜態成員不應該在類的內部初始化。然而,我們可以為靜態成員提供 const 整數型別的類內初始值,不過要求靜態成員必須是字面值常量型別的 constexpr。
初始值必須是常量表達式,因為這些成員本身就是常量表達式,所以它們能用在所有適合於常量表達式的地方。例如,我們可以用一個初始化了的靜態資料成員指定陣列成員的維度∶
class Account{
public:
static double rate() {return interestRate;}
static void rate(double);
private:
static constexpr int period = 30; // period是常量表達式
double daily_tbl[period];
};
如果某個靜態成員的應用場景僅限於編譯器可以替換它的值的情況,則一個初始化的 const 或constexpr static不需要分別定義。相反,如果我們將它用於值不能替換的場景中,則該成員必須有一條定義語句。
例如,如果 period 的唯一用途就是定義 daily_tbl的維度,則不需要在Account外面專門定義period。此時,如果我們忽略了這條定義,那麼對程式非常微小的改動也可能造成編譯錯誤,因為程式找不到該成員的定義語句。舉個例子,當需要把 Account∶∶period傳遞給一個接受 const int&的函式時,必須定義 period。
如果在類的內部提供了一個初始值,則成員的定義不能再指定一個初始值了∶
// 一個不帶初始值的靜態成員的定義
constexpr int Account::period; // 初始值在類的定義內提供
即使一個常量靜態資料成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員。
靜態成員能用於某些場景,而普通成員卻不能
如我們所見,靜態成員獨立於任何物件。因此,在某些非靜態資料成員可能非法的場合,靜態成員卻可以正常地使用。
舉個例子,靜態資料成員可以是不完全型別。特別的,靜態資料成員的型別可以就是它所屬的類型別。而非靜態資料成員則受到限制,只能宣告成它所屬類的指標或引用∶
class Bar{
public:
//...
private:
static Bar mem1; // 正確:靜態成員可以是不完全型別
Bar *mem2; // 正確:指標成員可以是不完全型別
Bar mem3; // 錯誤:資料成員必須是完全型別
};
靜態成員和普通成員的另外一個區別是我們可以使用靜態成員作為預設實參
class Screen{
public:
//
Screen& clear(char = background);
private:
static const char background;
}
非靜態資料成員不能作為預設實參,因為它的值本身屬於物件的一部分,這麼做的結果是無法真正提供一個物件以便從中獲取成員的值,最終將引發錯誤。