一、讓自己習慣C++
條款01:視C++爲一個語言聯邦
將C++視爲一個由相關語言組成的聯邦而非單一語言,由四個主要的次語言組成。
- C。說到底C++仍是以C爲基礎。區塊,語句,預處理器,內置數據類型,數組,指針統統來自C。
- Object-Oreinted C++。這一部分也就是C with Classes所訴求的。類,封裝,繼承,多態,virtual函數(動態綁定)。
- Template C++。這是C++泛型編程部分。由於templates威力強大,它們帶來嶄新的編程範型,也就是所謂的template metaprogramming。
- STL。STL是個template程序庫。它對容器,迭代器,算法以及函數對象的規約有極佳的緊密配合與協調。
請記住:
- C++高效編程守則視狀況而變化,取決於你使用C++的哪一部分。
條款02:儘量以const,enum,inline替換#define
- const替換#define
#define ASPECT_RATIO 1.653
這樣你的ASPECT_RATIO可能並未進入記號表,導致你對1.653以及它來自何處毫無概念,於是你將因爲追蹤它而浪費時間。
解決之道:const double AspectRatio = 1.653;
AspectRatio肯定會被編譯器看到,當然就會進入記號表內。
常量替換#define兩點注意:
定義常量指針:
const char *authorName= “Shenzi”;
cosnt std::string authorName("Shenzi");
建議使用第二種。
類專屬常量:
爲了將常量的作用域限制於class內,你必須讓它成爲class的一個成員;而爲確保常量至多隻有一份實體,你必須讓它成爲一個static成員:
class GamePlayer
{
private:
static const int NumTurns = 5;//常量聲明式,如果不取它們的地址,你可以聲明並使用它們而無須提供定義式。
int scores[NumTurns];//使用該常量
….
}
然而#define不能夠用來定義class專屬常量。
- enum替換#define
萬一你編譯器不允許“static整數型class常量”完成“in class初值設定”,可改用所謂的“the enum hack”補償做法。
class GamePlayer
{
private:
enum{NumTurns=5};
intscores[NumTurns];
….
}
取一個const的地址是合法的,但取一個enum的地址就不合法,而取一個#define的地址通常也不合法。如果你不想讓別人獲取一個pointer或reference指向你的某個整數常量,enum可以幫助你實現這個約束。
- inline替換#define
宏有着太多的缺點,光是想到它們就讓人痛苦不堪。
#define CALL_WITH_MAX(a,b) f((a) > (b)) ? (a) :(b))
替換:
template<typename T>
inline voidcallWithMax(cosnt T &a, cosnt T &b)
{
f(a > b ? a : b);
}
請記住:
- 對於單純常量,最好以const對象或enums替換#defines;
- 對於形似函數的宏,最好改用inline函數替換#defines。
條款03:儘可能使用const條款
關鍵字const多才多藝:
面對指針:
char greeting[] = "Hello";
char *p = greeting;//non-const pointer,non-const data;
const char *p = greeting; //non-const pointer,const data;
char * const p = greeting; //constpointer,non-const data;
const char * const p = greeting;// const pointer, const data;
面對STL:
const std::vector<int>::interator iter =vec.begin();//作用像T *const,++iter錯誤!
iter是const
std::vector<int>::const_iterator cIter = vec.begin();//作用像const T*,*cIter = 10錯誤!
*cIter是const
令函數返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而不至於放棄安全性和高效性。
例:const Rational operator*(const Rational &lhs, cosnt Rational &rhs)//防止出現 c= a*b
- const成員函數
兩個成員函數如果只是常量性不同,可以被重載。
成員函數如果是const,這有兩個流行概念:bitwise constness和logical constness。bitwise const陣營的人相信,成員函數只有在不更改對象之任何成員變量時纔可以說是const。也就是說它不更改對象內的任何一個bit。它對常量性的定義,因此const成員函數不可以更改對象內任何non-static成員變量。
不幸的是許多成員函數雖然不十足具備const性質卻能通過bitwise測試。更具體地說,一個更改了“指針所指物”的成員函數雖然不能算是const,但如果只有指針(而非其所指物)隸屬於對象,那麼此函數爲bitwise const不會引發編譯器異議。
class CTextBlock
{
public:
char &operator[](std::size_t position) const //bitwise const聲明,
{returnpText[position];} //但其實不適當
};
這個class不適當地將其operator[]聲明爲const成員函數,而該函數卻返回一個reference指向對象內部值。
constCTextBlock cctb(“Hello”);
char *pc =&cctb[0];
*pt =’J’;//但你終究還是改變了它的值。
利用mutable釋放掉non-static成員變量的bitwise constness約束。
- 在const和non-const成員函數中避免重複
當cosnt和non-const成員函數有着實質等價的實現時,令non-const版本調用const版本可避免代碼重複。
請記住:
- 將某些東西聲明爲const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體;
- 編譯器強制實施bitwise constness,但你編寫程序時應該使用“概念上的車輛”(conceptual constness);
- 當cosnt和non-const成員函數有着實質等價的實現時,令non-const版本調用const版本可避免代碼重複。
條款04:確定對象被使用前已先被初始化
永遠在使用對象之前先將它初始化。對於無任何成員的內置類型,你必須手工完成此事。至於內置類型以外的任何其它東西,初始化責任落在構造函數身上,確保每一個構造函數都將對象的每一個成員初始化。
- 賦值和初始化:
C++規定,對象的成員變量的初始化動作發生在進入構造函數本體之前。所以應將成員變量的初始化置於構造函數的初始化列表中。
ABEntry::ABEntry(conststd::string& name, const std::string& address,conststd::list<PhoneNumber>& phones)
{
theName = name;//這些都是賦值,而非初始化
theAddress = address;//這些成員變量在進入函數體之前已調用默認構造函數,接着又調用賦值函數。
thePhones = phones;
numTimesConsulted = 0;
}
ABEntry::ABEntry(const std::string& name, conststd::string& address,conststd::list<PhoneNumber>& phones)
: theName(name), //這些纔是初始化
theAddress(address), //這些成員變量只用相應的值進行拷貝構造函數,所以通常效率更高。
thePhones(phones),
numTimesConsulted(0){ }
如果成員變量時const或reference,它們就一定需要初值,不能被賦值。 C++有着十分固定的“成員初始化次序”。次序總是相同:base classes更早於其derivedclasses被初始化,而class的成員變量總是以其聲明次序被初始化。當在成員初始化列表中列各成員時,最好總是以其聲明次序爲次序。
- non-local static對象初始化次序
如果某編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,它所用到的這個對象可能尚未初始化,因爲c++對“定義於不同編譯單元內的non-local static對象”的初始化次序並無明確定義。
將每個non-local static對象搬到自己的專屬函數內(該對象在次函數內被聲明爲static)。這些函數返回一個reference指向它所含的對象。所以non-local static對象被local static對象替換了。這是Singleton模式的一個常見實現手法。
任何一種non-const static對象,不論它是local或non-local,在多線程環境下“等待某事發生”都會有麻煩。處理的一種做法是:在程序的單線程啓動階段手工調用所有reference-returning函數,這可消除與初始化有關的“競速形勢”。
請記住:
- 爲內置對象進行手工初始化,因爲C++不保證初始化它們;
- 構造函數最好使用成員初始化列表,而不要在構造函數本體內使用賦值操作。初始化列表列出的成員變量,其排列次序應該和它們在類中的聲明次序相同;
- 爲免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。