註:一下來自侯捷著《深入淺出mfc 第二版》
靜態成員(變數與函數)
我想你已經很清楚了,如果你依據一個類產生出三個對象,每一個對象將各有一份成員變數。有時候這並不是你要的。假設你有一個類,專門用來處理存款賬戶,它至少應該要有存戶的姓名、地址、存款額、利率等成員變數:
class SavingAccount
{
private:
char m_name[40]; // 存戶姓名
char m_addr[60]; // 存戶地址
double m_total; // 存款額
double m_rate; // 利率
...
};
這家行庫採用浮動利率,每個賬戶的利息都是根據當天的掛牌利率來計算。這時候m_rate 就不適合成為每個賬戶對象中的一筆資料,否則每天一開市,光把所有賬戶內容叫出來,修改 m_rate 的值,就花掉不少時間。m_rate 應該獨立在各對象之外,成為類獨一無二的資料。怎麼做?在 m_rate 前面加上 static 修飾詞即可:
class SavingAccount
{
private:
char m_name[40]; // 存戶姓名
char m_addr[60]; // 存戶地址
double m_total; // 存款額
static double m_rate; // 利率
...
};
static 成員變數不屬於對象的一部分,而是類的一部分,所以程式可以在還沒有誕生任何對象的時候就處理此種成員變數。但首先你必須初始化它。
不要把 static 成員變數的初始化動作安排在類的建構函式中,因為建構函式可能一再被調用,而變數的初值卻只應該設定一次。也不要把初始化動作安排在標頭檔中,因為它可能會被含入許多地方,因此也就可能被執行許多次。你應該在實現檔案中且類以外的任何位置設定其初值。例如在 main 之中,或全域函數中,或任何函數之外:
double SavingAccount::m_rate = 0.0075; //
設立 static 成員變數的初值
void main() { ... }
這麼做可曾考慮到 m_rate 是個 private 資料?沒關係,設定 static 成員變數初值時,不受任何存取許可權的束縛。請注意,static 成員變數的類型也出現在初值設定句中,因為這是一個初值設定動作,不是一個數量指定(assignment)動作。事實上,static 成員變數是在這時候(而不是在類聲明中)才定義出來的。如果你沒有做這個初始化動作,會產生連結錯誤:
error LNK2001: unresolved external symbol "private: static double
SavingAccount::m_rate"(?m_rate@SavingAccount@@2HA)
下面是存取static成員變數的一種方式,注意,此刻還沒有誕生任何對象實體:
// 第一種存取方式
void main()
{
SavingAccount::m_rate = 0.0075; // 欲此行成立,須把 m_rate 改為 public
}
下面這種情況則是產生一個對象後,通過對象來處理static成員變數:
// 第二種存取方式
void main()
{
SavingAccount myAccount;
myAccount.m_rate = 0.0075; // 欲此行成立,須把 m_rate 改為 public
}
你得搞清楚一個觀念,static 成員變數並不是因為對象的實現而才得以實現,它本來就存在,你可以想象它是一個全域變數。因此,第一種處理方式在意義上比較不會給人錯誤的印象。
只要access level允許,任何函數(包括全域函數或成員函數,static 或 non-static)都可以存取static成員變數。但如果你希望在產生任何object之前就存取其class的private static 成員變數,則必須設計一個static成員函數(例如以下的 setRate):
class SavingAccount
{
private:
char m_name[40]; // 存戶姓名
char m_addr[60]; // 存戶地址
double m_total; // 存款額
static double m_rate; // 利率
...
public:
static void setRate(double newRate) { m_rate = newRate; }
...
};
double SavingAccount::m_rate = 0.0075; // 設立 static 成員變數的初值
void main()
{
SavingAccount::setRate(0.0074); // 直接調用類的 static 成員函數
SavingAccount myAccount;
myAccount.setRate(0.0074); // 通過對象調用 static 成員函數
}
由於static成員函數不需要藉助任何對象,就可以被調用執行,所以編譯器不會為它暗加一個this指標。也因為如此,static成員函數無法處理類之中的 non-static 成員變數。還記得嗎,我在前面說過,成員函數之所以能夠以單一一份函數代碼處理各個對象的資料而不紊亂,完全靠的是this指標的指示。
static 成員函數「沒有this 參數」的這種性質,正是我們的MFC應用程式在準備callback函數時所需要的。第6章的 Hello World 例中我就會舉這樣一個執行個體。