C++關(guān)鍵字的思考
本章內(nèi)容:
1 關(guān)鍵字的相關(guān)理解
1.1 const關(guān)鍵字
1.2 static關(guān)鍵字
1.3 非局部變量的初始化順序
1.4 非局部變量的銷毀順序
1 關(guān)鍵字的相關(guān)理解
- 在C++中,關(guān)鍵字const和static非常讓人困惑。這兩個(gè)關(guān)鍵字有很多的含義,每種用法都很微妙,下面內(nèi)容將講解各種具體的用法。
1.1 const關(guān)鍵字
-
const
是constant
的縮寫,指保持不變的量。編譯器會(huì)執(zhí)行這一要求,任何嘗試改變常量的行為都會(huì)當(dāng)做錯(cuò)誤處理。此外,當(dāng)啟用了優(yōu)化時(shí),編譯器可以利用此信息生成更好的代碼。關(guān)鍵字const
有兩種相關(guān)的用法,可以用這個(gè)關(guān)鍵字標(biāo)記變量或者參數(shù),也可以用其標(biāo)記方法。本小節(jié)將討論這兩種含義。
1.1.1 const變量和參數(shù)
-
可以使用
const
來“保護(hù)”變量不被修改。這個(gè)關(guān)鍵字的一個(gè)重要用法是替換#define
來定義常量。這是const
最直接的應(yīng)用。例如,可以這樣聲明常量PI
:const double PI = 3.141592653589792;
可以將任何變量標(biāo)記為
const
,包括全局變量和類成員數(shù)據(jù)。-
還可以使用
const
指定函數(shù)或者方法的參數(shù)保持不變。例如,下面的函數(shù)接受一個(gè)const
參數(shù)。在函數(shù)體內(nèi)不能修改整數(shù)param
。如果試圖修改這個(gè)變量,編譯器將會(huì)生成一個(gè)錯(cuò)誤。void func(const int param) { // 不允許修改param... }
下面將討論兩種特殊的
const
變量或者參數(shù):const
指針和const
引用。
(1) const指針
-
當(dāng)變量通過指針包含一層或者多層間接取值時(shí),
const
的應(yīng)用變得十分微妙。考慮如下代碼:int* pIP;//(1) pIP = new int[10];//(2) pIP[4] = 5;//(3)
假定要將
const
應(yīng)用到pIP
。暫時(shí)不考慮這么做有沒有作用,只考慮這樣做意味著什么。是想阻止修改pIP
變量本身,還是阻止修改pIP
所指向的值?-
為了阻止修改
pIP
所指向的值(第三行),可在pIP
的聲明中這樣添加關(guān)鍵字const
:const int* pIP;(1) pIP = new int[10];//(2) pIP[4] = 5;//(3) 編譯出錯(cuò)!
現(xiàn)在無法修改
pIP
所指向的值。-
下面是在語義上等價(jià)的另一種方法:
int const* pIP;(1) pIP = new int[10];//(2) pIP[4] = 5;//(3) 編譯出錯(cuò)!
將
const
放在int
前面還是后面并不影響其功能。-
如果要將
pIP
本身標(biāo)記為const
(而不是pIP
所指向的值),可以這樣做:int* const pIP = nullptr;//(1) pIP = new int[10];//(2) 編譯出錯(cuò)! pIP[4] = 5;//(3) 錯(cuò)誤:不能為空指針賦值!
-
現(xiàn)在
pIP
本身無法修改,編譯器要求在聲明pIP
時(shí)就執(zhí)行初始化,可以用前面的代碼中的nullptr
,也可以是用新分配的內(nèi)存,如下所示:int* const pIP = new int[10]; pIP[4] = 5;
-
還可以將指針和所指的值全部標(biāo)記為
const
,如下所示:const int* const pIP = nullptr;
-
下面是另一種等價(jià)的語法:
int const* const pIP = nullptr;
-
盡管這些語法看上去有點(diǎn)混淆,但規(guī)則實(shí)際上很簡單:
const
關(guān)鍵字應(yīng)用于直接位于它左側(cè)的任何內(nèi)容,再次思考這一行:int const* const pIP = nullptr;
從左到右,第一個(gè)
const
直接位于int
的右邊,因此const
應(yīng)用到pIP
所指的int
。從而指定無法修改pIP
所指向的值。第二個(gè)const
直接位于*的右邊,因此,它應(yīng)用于指向int
的指針,也就是pIP
變量。因此,無法修改pIP
(指針)本身。-
這一條規(guī)則由于一個(gè)例外而變得令人費(fèi)解:第一個(gè)
const
可以出現(xiàn)在變量前面,如下所示:const int* const pIP = nullptr;
這個(gè)“異常的”語法比其他語法更加常見。
-
可以將這個(gè)規(guī)則引用到任意層次的間接取值,例如:
const int* const* const* const pIP = nullptr;
注意:另一種易于記憶的,用于指出復(fù)雜變量聲明的規(guī)則:從右向左讀,考慮示例int* const pIP;從右向左讀這條語句,就可以知道pIP
是一個(gè)指向int
的const
指針。另一方面,int const* pIP讀作pIP
是一個(gè)指向const int
的指針。
(2) const引用
const
應(yīng)用于引用通常比它應(yīng)用于指針更簡單:首先,引用默認(rèn)為const
,無法改變引用所指的對(duì)象。因此,無需顯示地將引用標(biāo)記為const
。其次,無法創(chuàng)建一個(gè)引用的引用,所以引用通常只有一層間接取值。獲取多層間接取值的唯一方法是創(chuàng)建指針的引用。-
因此,C++程序員提到“
const
引用”時(shí),含義如下所示:int z; const int& zRef = z; zRef = 4; // 編譯出錯(cuò)!
由于將
const
引用到int
,因此無法對(duì)zRef
賦值,如前所示。記住,const int& zRef
等價(jià)于int const& zRef
。然而需注意,將zRef
標(biāo)記為const
對(duì)z
沒有影響。仍然可以修改z
的值。具體做法是直接改變z
,而不是通過引用。-
const
引用通常用作參數(shù),這非常有用。如果為了提高效率,想按引用傳遞某個(gè)值,但不想修改這個(gè)值,可將其標(biāo)記為const
引用。例如:void doSomething(const BigClass& arg) { // do something here... }
(2) const方法
-
常量對(duì)象的值不能改變。如果使用常量對(duì)象,常量對(duì)象的引用和指向常量對(duì)象的指針,編譯器將不允許調(diào)用對(duì)象的任何方法,除非這些方法承諾不改變?nèi)魏螖?shù)據(jù)成員。為了確保方法不改變數(shù)據(jù)成員,可以使用
const
關(guān)鍵字來標(biāo)記方法本身。下面的SpreadsheetCell
類包含了用const
標(biāo)記的不改變?nèi)魏螖?shù)據(jù)成員的方法。Class SpreadsheetCell { public: double getValue() const; const string& getString() const; };
-
const
規(guī)范是方法原型的一部分,必須放在方法的定義中:double SpreadsheetCell::getValue() const { return mValue; } const string& SpreadsheetCell::getString() const { return mString; }
將方法標(biāo)記為
const
,就是與客戶代碼立下契約,承諾不會(huì)在方法內(nèi)改變對(duì)象內(nèi)部的值。如果將實(shí)際上修改了數(shù)據(jù)成員的方法聲明為const
,編譯器將會(huì)報(bào)錯(cuò)。不能將靜態(tài)方法聲明為const
,因?yàn)檫@個(gè)是多余的,靜態(tài)方法沒有類的實(shí)例,因此不能改變類內(nèi)部數(shù)據(jù)成員的值。const
工作原理:是將方法內(nèi)的數(shù)據(jù)成員都標(biāo)記為const
引用,因此如果試圖修改數(shù)據(jù)成員,編譯器會(huì)報(bào)錯(cuò)。-
非
const
對(duì)象可以調(diào)用const
方法和非const
方法。然而,const
對(duì)象只能調(diào)用const
方法,下面是一些示例:SpreadsheetCell myCell(5); cout << myCell.getValue() << endl; // OK myCell.setString("6"); // OK const SpreadsheetCell& anotherCell = myCell; cout << anotherCell.getValue() << endl; // OK anotherCell.setString("6"); // 編譯錯(cuò)誤!
應(yīng)該養(yǎng)成習(xí)慣,將不修改對(duì)象的所有方法聲明為
const
,這樣就可以在程序中引用const
對(duì)象。注意const
對(duì)象也會(huì)被銷毀,它們的析構(gòu)函數(shù)也會(huì)被調(diào)用,因此不應(yīng)該將析構(gòu)函數(shù)標(biāo)記為const
。
mutable數(shù)據(jù)成員
-
有時(shí)編寫的方法“邏輯上”是
const
,但是碰巧改變了對(duì)象的數(shù)據(jù)成員。這個(gè)改動(dòng)對(duì)于用戶可見的數(shù)據(jù)沒有任何影響,但在技術(shù)上確實(shí)做了改動(dòng),因此編譯器不允許將這個(gè)方法聲明為const
。例如,假設(shè)電子表格應(yīng)用程序要獲取數(shù)據(jù)的讀取頻率。完成這個(gè)任務(wù)的基本辦法是在SpreadsheetCell
類中加入一個(gè)計(jì)數(shù)器,計(jì)算getValue()
和getString()
調(diào)用的次數(shù)。遺憾的是,這樣做使編譯器認(rèn)為這些方法是非const
的,這并非你的本意。解決方法是將計(jì)數(shù)器變量設(shè)置為mutable
,告訴編譯器在const()
方法中允許改變這個(gè)值。下面是新的SpreadsheetCell
類的定義:class SpreadsheetCell { private: double mValue; string mString; mutable int mNumAccesses = 0; };
-
下面是
getValue()
和getString()
的定義:double SpreadsheetCell::getValue() const { mNumAccesses++; return mValue; } const string& SpreadsheetCell::getString() const { mNumAccesses++; return mString; }
1.1.2 constexpr關(guān)鍵字
-
C++一直存在常量表達(dá)式的概念,在某些情況下需要常量表達(dá)式。例如,當(dāng)定義數(shù)組時(shí),數(shù)組的大小就必須是一個(gè)常量表達(dá)式。由于這一限制,下面的代碼在C++中是無效的:
const int getArraySize() { return 32; } int main(void) { int myArray[getArraySize()]; // 在C++中無效 return 0; }
-
可以使用
constexpr
關(guān)鍵字重新定義getArraySize()
函數(shù),把它變成常量表達(dá)式。常量表達(dá)式在編譯時(shí)計(jì)算。(constexpr
是C++11
中的內(nèi)容)constexpr int getArraySize() { return 32; } int main(void) { int myArray[getArraySize()]; // OK return 0; }
-
甚至可以這樣寫:
int myArray[getArraySize() + 1]; // OK
將函數(shù)聲明為constexpr
對(duì)函數(shù)的行為施加了一些限制,因?yàn)榫幾g器必須在編譯期間對(duì)constexpr
函數(shù)求值,函數(shù)也不允許有任何副作用。下面是幾個(gè)限制:
- 函數(shù)體是一個(gè)
return
語句,它不包含goto
語句或try catch
塊,也不能拋出異常,但是可以調(diào)用其他constexpr
函數(shù)。 - 函數(shù)的返回類型應(yīng)該是字面量類型,返回值不能是
void
。 - 如果
constexpr
函數(shù)是類的一個(gè)成員,這個(gè)函數(shù)不能是虛函數(shù)。 - 函數(shù)所有的參數(shù)都應(yīng)該是字面量類型。
- 在編譯單元(translation unit)中定義了
constexpr
函數(shù)之后,才能調(diào)用這個(gè)函數(shù),因?yàn)榫幾g器需要知道完整的定義。 - 不允許使用
dynamic_cast
。 - 不允許使用
new
和delete
。
通過定義constexpr
構(gòu)造函數(shù),可以創(chuàng)建用戶自定義類型的常量表達(dá)式變量。constexpr
構(gòu)造函數(shù)應(yīng)該滿足以下要求:
- 構(gòu)造函數(shù)的所有參數(shù)都應(yīng)該是字面量類型。
- 構(gòu)造函數(shù)體不應(yīng)該是
function-try-block
。 - 構(gòu)造函數(shù)體應(yīng)該滿足于
constexpr
函數(shù)體相同的要求。 - 所有數(shù)據(jù)成員都應(yīng)該用常量表達(dá)式初始化。
例如,下面的Rect
類定義了一個(gè)滿足上述要求的constexpr
構(gòu)造函數(shù),此外還定義了一個(gè)constexpr getArea()
方法,執(zhí)行一些計(jì)算。
class Rect
{
public:
constexpr Rect(int width, int height) : mWidth(width), mHeight(height) {}
constexpr int getArea() const
{
return mWidth * mHeight;
}
private:
int mWidth;
int mHeight;
};
-
使用這個(gè)類聲明
constexpr
對(duì)象相當(dāng)直接:constexpr Rect r(8, 2); int myArray[r.getArea()]; // OK
1.2 static關(guān)鍵字
- 在C++中
static
有多種用法,這些用法之間沒有太多關(guān)系。“重載”這個(gè)關(guān)鍵字的部分原因是避免在語言中引入新的關(guān)鍵字。
1.1.1 靜態(tài)數(shù)據(jù)成員和方法
- 可以聲明類的靜態(tài)數(shù)據(jù)成員和方法。靜態(tài)數(shù)據(jù)成員與非靜態(tài)數(shù)據(jù)成員不同,它不是對(duì)象的一部分。相反,這個(gè)數(shù)據(jù)成員只有一份副本,這個(gè)副本存在于類的任何對(duì)象之外。
- 靜態(tài)方法與此類型,存在于類層次(而不是對(duì)象層次)。靜態(tài)方法不會(huì)在某個(gè)特定對(duì)象環(huán)境中執(zhí)行。
(1) 靜態(tài)數(shù)據(jù)成員
有時(shí)讓類的所有對(duì)象都包含某個(gè)變量的副本是沒必要的,或者無法完成特定的任務(wù)。數(shù)據(jù)成員有可能只對(duì)類有意義,而每個(gè)對(duì)象都擁有其副本是不合適的。例如,每個(gè)電子表格或許需要一個(gè)唯一的數(shù)字ID,這需要一個(gè)從0開始的計(jì)數(shù)器,每個(gè)對(duì)象都可以從這個(gè)計(jì)數(shù)器得到自身的ID。電子表格的計(jì)數(shù)器確實(shí)屬于
Spreadsheet
類,但沒必要使每個(gè)Spreadsheet
對(duì)象都包含這個(gè)計(jì)數(shù)器的副本,因?yàn)楸仨氉屗械挠?jì)數(shù)器都保持同步。-
C++用靜態(tài)(static)數(shù)據(jù)成員解決了這個(gè)問題。靜態(tài)數(shù)據(jù)成員是屬于類而不是對(duì)象的數(shù)據(jù)成員,可將靜態(tài)數(shù)據(jù)成員當(dāng)做類的全局變量。下面是
Spreadsheet
類的定義,其中包含了新的靜態(tài)數(shù)據(jù)成員計(jì)數(shù)器:class Spreadsheet { private: static int sCounter; }
-
不僅要在類定義中列出
static
類成員,還需要在源文件中為其分配內(nèi)存,通常是定義類方法的那個(gè)源文件。在此還可以初始化靜態(tài)成員,但注意與普通變量和數(shù)據(jù)成員不同,在默認(rèn)情況下它會(huì)被初始化為0。static
指針會(huì)初始化為nullptr
。下面為sCounter
分配空間并初始化的代碼:int Spreadsheet::sCounter;
-
這行代碼在函數(shù)或者方法外部,與聲明全局變量類似,只是使用了作用域解析
Spreadsheet::
指出這是Spreadsheet
類的一部分。i. 在類方法內(nèi)訪問靜態(tài)數(shù)據(jù)成員
-
在類方法內(nèi)部,可以像使用普通數(shù)據(jù)成員那樣使用靜態(tài)數(shù)據(jù)成員。例如,為
Spreadsheet
類創(chuàng)建一個(gè)mId
成員,并在Spreadsheet
構(gòu)造函數(shù)中用sCounter
成員初始化它。下面是包含了mId
成員的Spreadsheet
類定義:class Spreadsheet { public: int getId() const; private: static int sCounter; int mId; };
-
下面是
Spreadsheet
構(gòu)造函數(shù)的實(shí)現(xiàn),在此賦予初始ID值:Spreadsheet::Spreadsheet(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight) { mId = sCounter++; mCells = new SpreadsheetCell* [mWidth]; for (int i=0; i<nWidth; i++) { mCells[i] = new SpreadsheetCell[mHeight]; } }
-
可以看出,構(gòu)造函數(shù)可以訪問sCounter,就像這是一個(gè)普通成員。在復(fù)制構(gòu)造函數(shù)中,也要對(duì)ID賦值:
Spreadsheet::Spreadsheet(const Spreadsheet& src) { mId = sCounter++; copyFrom(&src); }
-
在賦值運(yùn)算符中不應(yīng)該復(fù)制ID。一旦給某個(gè)對(duì)象指定了ID,就不應(yīng)該再改變。建議把mId設(shè)置為
const
數(shù)據(jù)成員。ii. 在方法外訪問靜態(tài)數(shù)據(jù)成員
-
訪問控制限定符適用于靜態(tài)數(shù)據(jù)成員:
sCounter
是private
,因此不能在類方法之外訪問。如果sCounter是公有的,就可以在類方法外訪問,具體方法是用::
作用域解析運(yùn)算符指出這個(gè)變量是Spreadsheet
類的一部分:int c = Spreadsheet::sCounter;
然而,建議不要使用公有數(shù)據(jù)成員,應(yīng)該提供公有
get/set()
方法來授予訪問權(quán)限。如果要訪問靜態(tài)的數(shù)據(jù)成員,應(yīng)該實(shí)現(xiàn)靜態(tài)的get/set()
方法。
(2) 靜態(tài)方法
-
與數(shù)據(jù)成員類似,方法有時(shí)會(huì)應(yīng)用于全部類對(duì)象而不是單個(gè)對(duì)象,此時(shí)可以像靜態(tài)數(shù)據(jù)成員那樣編寫靜態(tài)方法。以
SpreadsheetCell
中的兩個(gè)輔助方法為例:stringToDouble()
和doubleToString()
。這些方法沒有訪問特定對(duì)象的信息,因此可以是靜態(tài)的。下面的類定義將這些方法設(shè)置為靜態(tài):class SpreadsheetCell { private: static string doubleToString(double val); static double StringTodouble(string& str); };
這兩個(gè)方法的實(shí)現(xiàn)與前面的實(shí)現(xiàn)相同,在方法定義前不需要重復(fù)static關(guān)鍵字。然而,注意靜態(tài)方法不屬于特定對(duì)象,因此沒有
this
指針,當(dāng)用某個(gè)特定對(duì)象調(diào)用靜態(tài)方法時(shí),靜態(tài)方法不會(huì)訪問這個(gè)對(duì)象的非靜態(tài)數(shù)據(jù)成員。實(shí)際上,靜態(tài)方法就像一個(gè)普通函數(shù),唯一的區(qū)別在于這個(gè)方法可以訪問類的private
和protected
靜態(tài)數(shù)據(jù)成員。如果同一個(gè)類型的其他對(duì)象對(duì)于靜態(tài)方法可見(例如傳遞了對(duì)象的指針或引用),靜態(tài)方法也可以訪問其他對(duì)象的private
和protected
非靜態(tài)數(shù)據(jù)成員。類中任何方法都可以像調(diào)用普通函數(shù)那樣調(diào)用靜態(tài)方法,因此
SpreadsheetCell
中所有方法的實(shí)現(xiàn)都沒有改變。如果要在類的外面調(diào)用靜態(tài)方法,需要用類名和作用域解析運(yùn)算符來限定方法的名稱(就像靜態(tài)數(shù)據(jù)成員那樣),靜態(tài)方法的訪問控制與普通方法一樣。-
將
stringToDouble()
和doubleToString()
設(shè)置為public
,這樣類外面的代碼也可以使用它們。此時(shí),可以在任意位置這樣調(diào)用這兩個(gè)方法:string str = SpreadsheetCell::doubleToString(5.0);
1.1.2 靜態(tài)鏈接(staitc Linkage)
-
在解釋用于鏈接的
static
關(guān)鍵字之前,首先要理解C++中鏈接的概念。C++每個(gè)源文件都是單獨(dú)編譯的,編譯得到的目標(biāo)文件會(huì)彼此鏈接。C++源文件中的每個(gè)名稱,包括函數(shù)和全局變量,都有一個(gè)內(nèi)部或者外部的鏈接。外部鏈接意味著這個(gè)名稱在其他源文件中也有效,內(nèi)部鏈接(也稱作靜態(tài)鏈接)意味著在其他源文件中無效。默認(rèn)情況下:函數(shù)和全局變量都擁有外部鏈接。然而,可在聲明前面使用關(guān)鍵字static
指定內(nèi)部(或者靜態(tài))鏈接。例如,假定有兩個(gè)源文件FirstFile.cpp
和AnotherFile.cpp
,下面是FirstFile.cpp
:void f(); int main(void) { f(); return 0; }
-
注意這個(gè)文件提供了
f()
的原型,但沒有給出定義。下面是AnotherFile.cpp
:#include <iostream> using namespace std; void f(); void f() { cout << "f()\n"; }
這個(gè)文件同時(shí)提供了
f()
的原型和定義。注意在兩個(gè)不同文件中編寫的相同函數(shù)的原型是合法的。如果將原型放在頭文件中,并在每個(gè)源文件中都使用#include
包含這個(gè)頭文件,預(yù)處理器就會(huì)自動(dòng)在每個(gè)源文件中給出函數(shù)原型。使用頭文件的原因是便于維護(hù)(并保持同步)原型的一個(gè)副本。這兩個(gè)源文件都可以編譯成功,程序鏈接也沒有問題:因?yàn)?code>f()具有外部鏈接,
main()
可從另一個(gè)文件調(diào)用這個(gè)函數(shù)。-
現(xiàn)在假定在
AnotherFile.cpp
中將static
應(yīng)用到f()
原型。注意不需要在f()
的定義前面重復(fù)使用static
關(guān)鍵字。只要在函數(shù)名稱的第一個(gè)實(shí)例前面使用這個(gè)關(guān)鍵字,就不需要重復(fù)它:#include <iostream> using namespace std; static void f(); void f() { cout << "f()\n"; }
現(xiàn)在每個(gè)源文件都可以成功編譯,但是鏈接時(shí)將失敗,因?yàn)?code>f()具有內(nèi)部(靜態(tài))鏈接,
FirstFile.cpp
無法使用這個(gè)函數(shù)。如果在源文件中定義了靜態(tài)方法但是沒有使用它,有些編譯器會(huì)給出警告(指出這些方法不應(yīng)該是靜態(tài)的,因?yàn)槠渌募赡軙?huì)用到它們)。-
將
static
用于內(nèi)部鏈接的另一種方式是使用匿名名稱空間(anonymous namespace)。可將變量或者函數(shù)封裝到一個(gè)沒有名字的名稱空間,而不是使用static
,如下所示:#include <iostram> using namespace std; namespace { void f(); void f() { cout << "f()\n"; } }
在同一源文件中,可在聲明匿名名稱空間之后的任何位置訪問名稱空間中的項(xiàng),但不能在其他源文件中訪問。這語義與
static
關(guān)鍵字相同。
extern關(guān)鍵字
extern
關(guān)鍵字將它后面的名稱指定為外部鏈接。在某些情況下面可以使用這種方法。例如,const
和typedefe
在默認(rèn)情況下面是內(nèi)部鏈接,可以使用extern
使其變?yōu)橥獠挎溄印?/p>-
然而,
extern
有一點(diǎn)復(fù)雜。當(dāng)指定某個(gè)名稱為extern
時(shí),編譯器將這條語句當(dāng)做聲明,而不是定義。對(duì)于變量而言,這意味著編譯器不會(huì)為這個(gè)變量分配空間。必須為這個(gè)變量提供單獨(dú)的、不適用extern
關(guān)鍵字的定義行,例如:extern int x; int x = 4;
-
也可以在
extern
行初始化x
,這一行既是聲明又是定義:extern int x = 1;
-
這種情形下的
extern
并不是非常有用,因?yàn)?code>x默認(rèn)具有外部鏈接。當(dāng)另一個(gè)源文件FirstFile.cpp
使用x
時(shí),才會(huì)真正用到extern
:#include <iostream> using namespace std; extern int x; int main(void) { cout << x << endl; }
在此
FirstFile.cpp
使用了extern
聲明,因此可以使用x
。編譯器需要一個(gè)x
的聲明,才能在main()
函數(shù)中使用這個(gè)變量。然而,如果聲明x
時(shí)未使用extern
關(guān)鍵字,編譯器認(rèn)為這是個(gè)定義,就會(huì)為x
分配空間,導(dǎo)致鏈接步驟失敗(因?yàn)橛袃蓚€(gè)全局作用域的x
變量)。使用extern
,就可以在多個(gè)源代碼中全局訪問這個(gè)變量。然而,建議不要使用全局變量。全局變量會(huì)令人迷惑,并且容易出錯(cuò),在大型程序中尤其如此。為了獲取類似功能,可使用類的靜態(tài)數(shù)據(jù)成員和方法。
1.1.3 函數(shù)中的靜態(tài)變量
-
C++中
static
關(guān)鍵字的最終目的是創(chuàng)建離開和進(jìn)入作用域時(shí)都可以保留值得局部變量。函數(shù)中的靜態(tài)變量就像是一個(gè)只能在函數(shù)內(nèi)部訪問的全局變量。靜態(tài)變量最常用的用法是“記住”某個(gè)函數(shù)是否執(zhí)行了特定的初始化操作。例如,下面的代碼就使用了這一技術(shù):void performTask() { static bool initialized = false; if (!initialized) { cout << "Initializing\n"; // Perform Initialization. initialized = true; } // Perform the desired task. }
然而靜態(tài)變量容易令人迷惑,在構(gòu)建代碼時(shí)通常有更好的方法,以避免使用靜態(tài)變量。在此情況下,可編寫一個(gè)類,用構(gòu)造函數(shù)執(zhí)行所需的初始化。
1.3 非局部變量的初始化順序
-
上面討論了靜態(tài)數(shù)據(jù)成員和全局變量的相關(guān)內(nèi)容,以下討論下這些變量的初始化順序。程序中所有的全局變量和類的靜態(tài)數(shù)據(jù)成員都會(huì)在
main()
開始之前初始化。給定源文件中的變量以在源文件中出現(xiàn)的順序初始化。例如,在下面的文件中,Demo::x
一定會(huì)在y
之前初始化:class Demo { public: static int x; }; int Demo::x = 4; int y = 5;
然而,C++沒有提供規(guī)范,說明在不同源文件中初始化非局部變量的順序。如果在某個(gè)源文件中有一個(gè)全局變量
x
,在另一個(gè)文件中有一個(gè)全局變量y
,無法知道哪個(gè)變量先初始化。通常,不需要關(guān)注這一規(guī)范的缺失,但是如果某個(gè)全局變量或者靜態(tài)變量依賴于另一個(gè)變量,就可能引發(fā)問題。對(duì)象的初始化意味著調(diào)用構(gòu)造函數(shù),全局對(duì)象的構(gòu)造函數(shù)可能會(huì)訪問另一個(gè)全局對(duì)象,并假定另一個(gè)對(duì)象已經(jīng)構(gòu)建。如果這兩個(gè)全局對(duì)象在不同的源文件中聲明,就不能指望一個(gè)對(duì)象在另一個(gè)對(duì)象之前構(gòu)建,也無法控制他們的初始化順序。不同的編譯器可能有不同的初始化順序,即使同一編譯器的不同版本也可能如此,甚至項(xiàng)目中添加另一個(gè)文件也會(huì)影響初始化順序。警告:不同源文件中的非局部變量的初始化順序是不確定的。
1.4 非局部變量的銷毀順序
- 非局部變量按照其初始化的逆序進(jìn)行銷毀。不同源文件中的非局部變量的初始化順序是不確定的。所以其銷毀順序也是不確定的。