條款01:視C++爲一個聯邦語言
一開始C++只是在C的基礎上加了一些面向對象 的特性。隨着語言的逐漸成熟。我們發現它相比其他語言更加的靈活和自由。有時候甚至對其產生迷惑。我在使用的時候也會經常遇到這樣的疑惑,好像它的每一個“適當用法”都是例外,這樣會讓人產生糾結。
條款一告訴我們,我們應該視其爲一個語言聯邦而非單一語言,正因爲它的約束規則相比其他語言少了很多,它纔給了開發者更多的自用,來創造不同的使用方法。根據條款一的說法,我們爲了更好的理解C++這門語言,其實只要讀懂它的4個語言:
1. C。 C++仍是以C爲基礎,它的區塊、語句、預處理、內置數據類型、數組、指針等都來自C++,這是C的語法基礎,也是C++的語法基礎。
2. Object-Oriented C++。 這部分就是C with Classes,它包括類、封裝、繼承、多態、虛函數,這是面嚮對象語言的基礎。
3. Template C。 這是C++的泛型編程部分。掌握好這部分,可以使你的工作效率事半功倍,這也是大部分C++程序員(包括我)所缺乏的部分。
4. STL。 STL是一個模板程序庫。在C++項目工程中,我們會很常見。它對容器、算法、迭代器、函數對象的規約有極佳的緊密配合和協調。
這四個次語言都有各自的規約,如果我們將C++視爲一個語言聯邦,當我們在C++中切換使用這四種次語言時,就不會感到奇怪了。
總結
C++ 高效編程守則視狀況而變化,取決於你使用C++的哪一部分。
條款02:儘量以const,enum, inline替換#define
1. 使用const替換#define
通常情況下,我們會在頭文件中使用#define定義一些常量,考慮如下情況:
#define ASPECT_RATIO 1.653
這樣使用確實沒有問題。但是,會有如下幾個小問題:
(1). 如果在使用該常量的地方出現編譯錯誤,我們可能不會第一時間知道它錯誤的地方。編譯器提示的錯誤可能是1.653而不是ASPECT_RATIO,這是因爲ASPECT_RATIO在預處理器的 時候已經被替換成了1.653,編譯器並不知道記號ASPECT_RATIO的存在。
(2). 使用 ASPECT_RATIO可能會導致更多的目標碼。這是因爲預處理器會將所有使用ASPECT_RATIO的地方替換成1.653,而使用const則不會出現這種情況。
(3). 使用ASPECT_RATIO時,沒有安全類型檢查,與const相比,它時不安全的。
所以,我們可以在頭文件中替換它爲:
const double AspectRatio = 1.653;
當然在使用const的時候需要注意如下幾點:
(1)對於指針,我們使用const,保證指針不被改變,以及指針所指向的內容不被改變。
const char* const authorName = "edwin";
或者使用標準庫:
const std::string authorName = "edwin";
(2)如果需要在類中定義const成員,需要使用static進行修飾。(當然這種情況很少使用),通常在類中,我們會使用enum替代#define。
2. 使用enum替換#define
在實際項目中,我們常常會使用一些序列值表示類型。如讀寫操作。這時,又不想該類型爲全局的,所以就需要在類中定義枚舉來替代#define的使用:
class MyFile{
private:
enum {
Read_Only = 0,
Write_Only = 1,
Read_Write = 2
}
...
}
3. 使用inline替換#define
Effective C++提供瞭如下使用#define的例子,這也是大多時候我們使用的方式:
#define CALL_WITH_MAX(a, b) f((a) > f(b) ? (a) : (b))
這是比較兩個數的大小的宏函數。猶豫宏時預處理器自動替換的語法糖。所以如果不加下括號,在使用過程中很可能出現錯誤的輸出。但即使加了這麼多括號,也會出現我們意想不到的情況:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); // a被累加了兩次
CALL_WITH_MAX(++a, b + 10); //a被累加了一次
如上,a的累加次數,取決於它的比較對象。當它的比較對象小於它時,它被累加了兩次,這顯然不是我們想要的結果。
所以對於這種宏函數,我們最好使用inline函數來替換:
template<typename T>
inline void CallWithMax(const T& a, const T& b) {
f(a > b ? a : b);
}
總結
- 對於單純常量,最好以const對象或enums替換#define;
- 對於形似函數的宏(macros),最好使用inline函數替換#define;
條款03:儘可能使用const
const在c++中經常使用。它指定了一個語義約束–指定一個不被改動的對象。const可以修飾全局變量、命名空間內的變量、類中的靜態和非靜態成員變量、指針、函數等。
1. const指針
考慮如下的關於const對於指針的修飾:
char cP[] = "hello";
const char* p1 = cP; //const 指針所指向的數據
char* const p2 = cP; //const 指針本身
const char* const p3 = cP; //const指針且const指針所指向的數據
這裏區分它們的方法也很簡單,如果const在*左邊這修飾的時指針所指向的數據,反之修飾的是指針本身。
2. const迭代器
c++標準庫提供一套迭代器的使用。
std::vector<int> vec;
...
const std::vector<int>::iterator iter1 = vec.begin(); //const修飾的是迭代器iter1,所以iter不可以改變
std::vector<int>::const_iterator iter2 = vec.begin(); //const修飾的是迭代器iter2所指向的內容
3. 函數返回const常量
設置函數返回值爲const常量,可以避免用戶的錯誤使用而造成的意外。這樣做即保證了其安全性,也保證了其安全性。
class Rational{...}
const Rational Add operator* (const Rational& a, const Rational& b);
如上所示,如果用戶使用了重載操作符*,做如下的操作:
Rational a, b, c;
if(a * b = c) {...};
這個時候編譯時,編譯器會提示錯誤,因爲重載*函數返回的時const常量,值不能被改變。如果我們不使用const修飾,這樣編譯會通過,但是運行會得到意想不到的結果。
5. const成員函數
const成員函數的作用於const對象上,即const成員函數內部不能對成員變量進行改寫。
(1). c++中的一個重要特性。
兩個成員函數如果只是常量性不同,可以被重載。
所以就會出現如下的情況:
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;
}
那麼該如何調用以上的重載函數呢。
void print(const TextBlock& ctb, TextBlock& tb) {
std::cout << tb[0];
tb[0] = 'x';
std::cout << ctb[0];
ctb[0] = 'x'; //錯誤,對const TextBlock對象進行改寫。
}
上面的註釋是因爲operator[]的返回類型是const,另外non_const的operator[]返回類型是char&,如果是char的話,程序是無法編譯過的。
(2). const成員函數修改成員變量。
如果我們需要在const成員函數中寫成員變量,則需要使用mutable修飾成員變量。這個是logical constness的主張者。
(3). 在const 和non-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]);
}
private:
std::string text;
}
如上,在non-const版本中將this先轉換成const對象,然後調用const的重載,然後再將返回的const對象轉換成非const引用類型。需要注意的是,我們不能用const成員函數調用non-const的版本,這是因爲const成員函數絕不改變其對象的邏輯狀態。
總結
將某些東西聲明爲const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體。
編譯器強制實施bitwise constness ,但你編寫程序時應該使用“概念上的常量性”(conceptual constness)。
當const和non-const成員函數有着實質等價的實現時,令non-const版本調用const版本可避免代碼重複。
條款04:確定對象被使用前已先被初始化
該條款相對好理解。主要有如下:
- 爲內置型對象進行手工初始化,因爲C++不保證初始化它們。
- 構造函數最好使用成員初值列,而不要再構造函數本體內使用賦值操作。初值列列出的成員變量,其排列次序應該和它們再class中的聲明次序相同。
- 爲免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。
關於第三條,因爲non-local static對象不能保證初始化的次序,所以使用local static替換non-local static對象。這樣就可以保證第二條的初始化次序。