C++的四個語言層次:
綜述:C++並不是一個帶有一組守則的一體語言:它是從四個次語言組成的聯綁政府,每個次語言都有自己的規約。
對於單純常量,最好以const對象或enums替換#define。
對於形似函數的宏,最好改用inline函數替換#define
STL的迭代器是以指針為根據塑模出來,所以迭代器的作用就像個T*的指針。聲明迭代器為const就像聲明指針為const一樣(即聲明一個T*const指針),表示這個迭代器不得指向不同的東西,但它所指的東西的值是可以改動的。如果希望迭代器所指的東西是不可被改動的,需要的是const_iterator。
vector<int> vec; const vector<int>::iterator = vec.begin(); *iter = 10; // 沒問題,改變iter所指物 ++iter; // 錯誤!iter是const vector<int>::const_iterator cIter = vec.begin(); *cIter = 10; // 錯誤!*cIter是const ++cIter; // 沒問題,改變cIter
請記住
- 將某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用哉內的對象、函數參數、函數返回類型、成員函數本體。
- 編譯器強制實施bitwise constness,但你編寫程序時應該使用“概念上的常量性”。
- 當const和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可以避免代碼重復。
類的構造函數的次序是先執行構造函數初始化列表,初始化所有成員變量,然後再執行構造函數體,構造函數體內的成員賦值已經不屬於初始化的范疇,成員都是用拷貝賦值。
如果類沒有初始化列表,則類會先執行默認構造函數,構造出所有成員變量後,再執行函數體內的拷貝賦值。
C++類的成員初始化是有著明顯的次序的,一般是基類的成員先初始化,然後派生類的成員按定義的順序初始化。所以類的構造函數初始化列表上的初始化順序跟類真實的成員初始化順序是沒有關系的。
“不同編譯單元內定義之non-local static對象”
static對象,其壽命從被構造出來直到程序結束為止。
函數內的static對象稱為local static對象(因為它們對函數而言是local),其他static對象稱為non-local static對象。程序結束時static對象會被自動銷毀,也就是它們的析構函數會在main()結束時被自動調用。
當我們的某個編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元的某個non-local static對象,它所用到的這個對象可以尚未被初始化。C++關於定義於不同編譯單元內的non-local static對象的初始化次序並無明確定義。
比如在a.cpp裡我們定義一個類,一個該類的對象
class FileSystem { public: size_t numDisks()const; }; extern FileSystem tfs;
現在同一個項目下的b.cpp文件中有一個類,類構造函數用到了tfs對象。
class Directory { public: Directory(params); }; Directory::Directory(params) { size_t disks = tfs.numDisks(); }
現在如果我們創建了一個Directory對象
Directory tempDir(params);
上面的代碼就可能會出問題,除非能保證tfs在tempDir之前先初始化,否則tempDir的構造函數會用到尚未初始化的tfs。
解決方案:
C++保證,函數內的local static對象會在該函數被調用期間,首次遇到該對象的定義的時候被初始化。
所以如我們把tfs和tempDir設計為一個函數,函數返回該類的一個static對象引用就可以解決問題了。
所以我們可以改寫上面的代碼:
FileSystem& tfs() { static FileSystem fs; return fs; } Directory& tempDir() { static Directory td; return td; }
請記住
- 為內置型對象進行手工初始化,因為C++不保證初始化它們。
- 構造函數最好使用成員初始化列表,而不要在函數體內使用賦值操作。初始列表列出的成員變量,其排列次序應該和它們在class中的聲明次序相同。
- 為免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。