條款03: 儘可能使用const
本條款總結了const的使用情境和使用它帶來的好處。
關鍵字const多才多藝。
char greeting[]="hello":char *p=greeting;//non-const pointer,non-const dataconst char* p=greeting;//non-const pointer,const datachar* const p=greeting;//const pointer,non-const dataconst char* const p=greeting;//const pointer,const data
const文法雖然變化多端,但並不莫測高深,如果關鍵字const出現在星號左邊,表示被指物是常量,如果出現在星號右邊,表示指標自身是常量;如果出現在星號兩邊,表示被指物和指標兩者都是常量。
如果被指物是常量,有些程式員會將關鍵字const寫在類型之前,有些人會把它寫在類型之後、星號之前。這兩種寫法的意義相同,所以下面兩個函數接收的參數類型是一樣的:
void f1(const Widget* pw);void f2(Widget const *pw);
下面是STL中vector迭代器中const迭代器和const_iterator的區別
const std::vector<int>::iterator iter=v.begin();*iter=10;//正確++iter;//錯誤
std::vector<int>::const_iterator citer=v.begin();*citer=10;//錯誤++citer;//正確
const最具威力的用法是面對函式宣告時的應用。在一個函式宣告式內,const可以和函數傳回值、各參數、函數自身(如果是成員函數)產生關聯。
令函數返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而又不至於放棄安全性和高效性。
例如:
class Rational{......};const Rational operator*(const Rational& lhs,const Rational& rhs);Rational a,b,c;(a*b)=c;if(a*b=c)...
const可以預防“沒意義的賦值操作”和“==意外換成=”的煩惱;
const成員函數
將const實施於成員函數的目的,是為了確認該成員函數可以用於const對象身上。這一類成員函數之所以重要,基於兩個理由。
1.它們使得class介面比較容易理解,得知哪個函數可以改動對象內容而哪個函數不行。
2.它們使“操作const對象”成為可能
class TextBlock{ public: ... const char& operator[](std::size_t position) const{ return text[position];} char& operator[](std::size_t position){ return text[position];} private: std::string text;}//使用operator[]TextBlock tb("hello");std::cout<<tb[0];//調用non-const[]const TextBlock ctb("world");std::cout<<ctb[0];//調用const[]
std::cout<<tb[0];//正確tb[0]='x';//正確std::cout<<ctb[0];//正確ctb[0]='x';//錯誤
成員函數如果是const意味著什嗎?這兩個流行概念:bitwise constness和logical constness
bitwise const 陣營的人相信,成員函數只有在不更正對象之任何成員變數時才可以說是const,也就是說它不更改對象內的任何一個bit。
如果成員變數的是一個指標,但是後面改變了指標地址對應的內容,這就說不過去了。
下面使用mutable釋放掉non-static成員變數的bitwise constness約束
class CtextBlock{ public: ... std::size_t length() const; private: char* pText; mutable std::size_t textLength;//這些成員變數總是會被更改,即使在const成員函數內 mutable bool lengthIsValid;};std::size_t CTextBlock::length() const{ if(!lengthIsValid) { textLength=std::strlen(pText); lengthIsValid=true; } return textLength;}
此外,一個編程技巧是:當const 和non-const 成員函數有著實質等價的實現時,令non-const 版本調用const 版本可避免代碼重複
class TextBlock{ public: ...... const char& operator[](std::size_t position) const{ ... ... ... return text[position];}char& operator[](std::size_t position){ return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);}...};
這裡有兩次轉型,第一次用來*this添加const.第二次則是從const operator[]的傳回值移除const
請記住:
某些東西聲明為const可協助編譯器偵測出錯誤用法,const可被施加於任何範圍內的對象,函數參數,函數傳回型別、成員函數本體
編譯器強制實施bitwise constness,但你編寫程式時應該使用“概念上的常量性”
當const和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重複。
條款04: 確定對象被使用前已先被初始化
本條款告誡程式員,在C++程式設計中,應該對所有對象初始化,以避免不必要的錯誤,同時,給出了高效初始化對象的方法和正確初始化對象的方法。
(1)初始化建構函式最好使用成員初值列(member initialization list) ,而不要在建構函式本體內使用賦值操作(assignment) 。初值列出的成員變數,其排列次序應該和它們在class 中的聲明次序相同。
考慮一個用來表現通訊錄的class ,其建構函式如下:
ABEntry::ABEntry(const string&name,const string& address,const list<PhoneNumber>&phones){ theName=name;//這些都是賦值 theAddress=address;//並非初始化 thePhones=phones; numTimeConsulted=0;}
ABEntry::ABEntry(const string& name,const string& address,const list<PhoneNumber>&phones):theName(name,theAddress(address),thePhones(phones),numTimesConsulted(0))//現在使用的是初始化{//建構函式本體不必有任何動作}
有些情況下即使面對的成員變數屬於內建類型(那麼其初始化與賦值的成本相同),也一定得使用初始化列表。如果成員變數是const或references,它們就一定需要初值,不能賦值。避免任何麻煩,任何情況下都使用初始化列表
C++ 有著十分固定的”成員初始化次序”。次序總是相同: base class早於其derived classes 被初始化,而class 的成員變數總是以其聲明次序被初始化。回頭看看ABEntry. 其theName 成員永遠最先被初始化,然後是theAddress,再來是thePhones,最後是numTimesConsulted。即使它們在成員初值列中以不同的次序出現(很不幸那是合法的),也不會有任何影響。
(2)C++ 對”定義於不同編譯單元內的non-local static 對象”的初始化次序並無明確定義。為免除”跨編譯單元之初始化次序”問題,請以local static 對象替換non-local static 對象。
所謂static對象,包括global對象,定義於namespace範圍內的對象、在class內,在函數內、以及在file範圍內被聲明為static的對象。函數內的static對象稱為local static對象(因為它們對函數而言是local),其他的static對象稱為non-local static對象。程式結束時static對象會被自動銷毀,也就是它們的解構函式會在main結束時被調用。
class FileSystem{...};FileSystem& tfs(){ static FileSystem fs;//以local static的方式定義和初始化object return fs;}class Directory{...};Directory::Directory(params){ ... std::size_t disks=tfs().numDisks(); ...}Director& tmpDir(){ static Directory td; return td;}
為了避免在對象初始化之前過早的使用它們,你需要做三件事:
1.手工初始化內建型non-member對象
2.使用建構函式成員初始化列表初始化
3.在“初始化次序不確定性”氛圍下加強你的設計
請記住:
1.為內建對象進行手工初始化,因為C++不保證初始化它們
2.建構函式最好使用初始化列表,而不要在建構函式中使用賦值操作,其排列次序應該和它們在class中的聲明次序相同
3.為避免“跨編譯單元之初始化序”問題,請以local static對象代替non-local static 對象