專欄簡介:本專欄主要面向C++初學者,解釋C++的一些基本概念和基礎語言特性,涉及C++標準庫的用法,面向對象特性,泛型特性高級用法。通過使用標準庫中定義的抽象設施,使你更加適應高級程序設計技術。希望對讀者有幫助!
目錄
- 7.6 類的靜態成員
- 聲明靜態成員
- 使用類的靜態成員
- 靜態成員的類內初始化
- 靜態成員能用于某些場景,而普通成員不能
7.6 類的靜態成員
有的時候類需要它的一些成員與類本身直接相關,而不是與類的各個對象保持關聯。例如,一個銀行賬戶類可能需要一個數據成員來表示當前的基準利率。在此例中,我們希望利率與類關聯,而非與類的每個對象關聯。從實現效率的角度來看,沒必要每個對象都存儲利率信息。而且更加重要的是,一旦利率浮動,我們希望所有的對象都能使用新值。
聲明靜態成員
我們通過在成員的聲明之前加上關鍵字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();//通過Account的對象或引用
r=ac2->rate();//通過指向Account對象的指針
成員函數不用通過作用域運算符就能直接使用靜態成員:
class Account{
public:
void calculate(){amount+=amount*interestRate;}
private:
static double interestRate;
//其他成員與之前的版本一致
};定義靜態成員和其他的成員函數一樣,我們既可以在類的內部也可以在類的外部定義靜態成員函數。當在類的外部定義靜態成員時,不能重復static關鍵字,該關鍵宇只出現在類內部的聲明語句:```cpp
void Account::rate(double newRate)
{interestRate=newRate;
}因為靜態數據成員不屬于類的任何一個對象,所以它們并不是在創建類的對象時被定義的。這意味著它們不是由類的構造函數初始化的。而且一般來說,我們不能在類的內部初始化靜態成員。相反的,必須在類的外部定義和初始化每個靜態成員。和其他對象一樣,-個靜態數據成員只能定義一次。類似于全局變量,靜態數據成員定義在任何函數之外。因此一旦它被定義,就將一直存在于程序的整個生命周期中。我們定義靜態數據成員的方式和在類的外部定義成員函數差不多。我們需要指定對象的類型名,然后是類名、作用域運算符以及成員自己的名字:```cpp
//定義并初始化一個靜態成員
double Account::interestRate=initRate();
這條語句定義了名為interestRate的對象,該對象是類Account的靜態成員,其類型是double。從類名開始,這條定義語句的剩余部分就都位于類的作用域之內了。因此,我們可以直接使用initRate函數。注意,雖然initRate是私有的,我們也能用它初始化interestRate。和其他成員的定義一樣interestRate的定義也可以訪問類的私有成員。
要想確保對象只定義一次,最好的辦法是把靜態數據成員的定義與其他非內聯函數的定義放在同一個文件中。
靜態成員的類內初始化
通常情況下,類的靜態成員不應該在類的內部初始化。然而,我們可以為靜態成員提供const整數類型的類內初始值,不過要求靜態成員必須是字面值常量類型的constexpr(參見7.5.6節,第267頁)。初始值必須是常量表達式,因為這些成員本身就是常量表達式,所以它們能用在所有適合于常量表達式的地方。例如,我們可以用一個初始化了的靜態數據成員指定數組成員的維度:
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的維度,則不需要在hccount外面專門定義period。此時,如果我們忽略了這條定義,那么對程序非常微小的改動也可能造成編譯錯誤,因為程序找不到該成員的定義語句。舉個例子,當需要把Account::period傳遞給一個接受const int &的函數時,必須定義period。如果在類的內部提供了一個初始值,則成員的定義不能再指定一個初始值了:
//一個不帶初始值的靜態成員的定義
constexpr intRccount::period;//初始值在類的定義肉提供
即使一個常量靜態數據成員在類內部被初始化了,通常情況下也應該在類的外部定義一下該成員。
靜態成員能用于某些場景,而普通成員不能
如我們所見,靜態成員獨立于任何對象。因此,在某些非靜態數據成員可能非法的場合,靜態成員卻可以正常地使用。舉個例子,靜態數據成員可以是不完全類型。特別的,靜態數據成員的類型可以就是它所屬的類類型。而非靜態數據成員則受到限制,只能聲明成它所屬類的指針或引用:
class Bar{
public:
// ...
private:
static Bar meml;//正確:靜態成員可以是不完全類型
Bar* mem2;//正確:指針成員可以是不完全類型
Bar mem3;//錯誤:數據成員必須是完全類型
}靜態成員和普通成員的另外一個區別是我們可以使用靜態成員作為默認實參:```cpp
class Screen{
public:
//bkground表示一個在類中稍后定義的靜態成員
Screen &clear(char=bkground);
private::
static const char bkground;
}
非靜態數據成員不能作為默認實參,因為它的值本身屬于對象的一部分,這么做的結果是無法真正提供一個對象以便從中獲取成員的值,最終將引發錯誤。