每日一句英語學習,每天進步一點點: “Action may not always bring happiness; but there is no happiness without action.” 「行動不見得一定帶來快樂,但沒有行動就沒有快樂。」 前言 我在閱讀 《Effective C++ ...
每日一句英語學習,每天進步一點點:
- “Action may not always bring happiness; but there is no happiness without action.”
- 「行動不見得一定帶來快樂,但沒有行動就沒有快樂。」
前言
我在閱讀 《Effective C++ (第三版本)》 書時做了不少筆記,從中收穫了非常多,也明白為什麼會書中前言的第一句話會說:
對於書中的「條款」這一詞,我更喜歡以「細節」替換,畢竟年輕的我們在打 LOL 或 王者的時,總會說註意細節!細節!細節~ —— 細節也算伴隨我們的青春的字眼
針對書中的前兩個章節,我篩選了 10 個 細節(條款)作為了本文的內容,這些細節也相對基礎且重要。
針對這 10 細節我都用較簡潔的例子來加以闡述,同時也把本文所提及細節中的「小結」總結繪畫成了一副思維導圖,便於大家的閱讀。
溫馨提示:本文較長(萬字),建議收藏閱讀。
後續有時間也會繼續分享後面章節的筆記,喜歡的小伙伴「點擊左上角」關註我~
正文
1 讓自己習慣C++
細節 01:儘量以const,enum,inline 替換 #define
#define 定義的常量有什麼不妥?
首先我們要清楚程式的編譯重要的三個階段:預處理階段,編譯階段和鏈接階段。
#define
是不被視為語言的一部分,它在程式編譯階段中的預處理階段的作用,就是做簡單的替換。
如下麵的 PI
巨集定義,在程式編譯時,編譯器在預處理階段時,會先將源碼中所有 PI
巨集定義替換成 3.14
:
1#define PI 3.14
程式編譯在預處理階段後,才進行真正的編譯階段。在有的編譯器,運用了此 PI
常量,如果遇到了編譯錯誤,那麼這個錯誤信息也許會提到 3.14 而不是 PI,這就會讓人困惑哪裡來的3.14
,特別是在項目大的情況下。
解決之道:以 const 定義一個常量替換上述的巨集(#define)
作為一個語言變數,下麵的 const 定義的常量 Pi
肯定會被編譯器看到,出錯的時候可以很清楚知道,是這個變數導致的問題:
1const doule Pi = 3.14;
如果是定義常量字元串,則必須要 const
兩次,目的是為了防止指針所指內容和指針自身不能被改變:
1const char* const myName = "小林coding";
如果是定義常量 string
,則只需要在最前面加一次 const
,形式如下:
1const std::string myName("小林coding");
#define 不重視作用域,所以對於 class 的專屬常量,應避免使用巨集定義。
還有另外一點巨集無法涉及的,就是我們無法利用 #define
創建一個 class
專屬常量,因為 #define
並不重視作用域。
對於類里要定義專屬常量時,我們依然使用 static
+ const
,形式如下:
1class Student {
2private:
3 static const int num = 10;
4 int scores[num];
5};
6
7const int Student::num; // static 成員變數,需要進行聲明
如果不想外部獲取到 class 專屬常量的記憶體地址,可以使用 enum 的方式定義常量
enum
會幫你約束這個條件,因為取一個 enum
的地址是不合法的,形式如下:
1class Student {
2private:
3 enum { num = 10 };
4 int scores[num];
5};
#define 實現的函數容易出錯,並且長相醜陋不易閱讀。
另外一個常見的 #define
誤用情況是以它實現巨集函數,它不會招致函數調用帶來的開銷,但是用 #define
編寫巨集函數容易出錯,如下用巨集定義寫的求最大值的函數:
1#define MAX(a, b) ( { (a) > (b) ? (a) : (b); } ) // 求最大值
這般長相的巨集有著太的缺點,比如在下麵調用例子:
1int a = 6, b = 5;
2int max = MAX(a++, b);
3
4std::cout << max << std::endl;
5std::cout << a << std::endl;
輸出結果(以下結果是錯誤的):
17 // 正確的答案是 max 輸出 6
28 // 正確的答案是 a 輸出 7
要解釋出錯的原因很簡單,我們把 MAX
巨集做簡單替換:
1int max = ( { (a++) > (b) ? (a++) : (b); } ); // a 被累加了2次!
在上述替換後,可以發現 a
被累加了 2 次。我們可以通過改進 MAX
巨集,來解決這個問題:
1#define MAX(a, b) ({ \
2 __typeof(a) __a = (a), __b = (b); \
3 __a > __b ? __a : __b; \
4})
簡單說明下,上述的 __typeof
可以根據變數的類型來定義一個相同類型的變數,如 a
變數是 int 類型,那麼 __a
變數的類型也是 int 類型。改進後的 MAX
巨集,輸出的是正確的結果,max 輸出 6,a 輸出 7。
雖然改進的後 MAX
巨集,解決了問題,但是這種巨集的長相就讓人困惑。
解決的方式:用 inline 替換 #define 定義的函數
用 inline
修飾的函數,也是可以解決函數調用的帶來的開銷,同時閱讀性較高,不會讓人困惑。
下麵用用 template inline 的方式,實現上述巨集定義的函數::
1template<typename T>
2inline T max(const T& a, const T& b)
3{
4 return a > b? a : b;
5}
max
是一個真正的函數,它遵循作用域和訪問規則,所以不會出現變數被多次累加的現象。
模板的基礎知識記憶體,可移步到我的舊文進行學習 --> 泛型編程的第一步,掌握模板的特性!
細節 01 小結 - 請記住
- 對於單純常量,最好以 const 對象或 enum 替換 #define;
- 對於形式函數的巨集,最好改用 inline 函數替換 #define。
細節 02:儘可能使用 const
const 的一件奇妙的事情是:它允許你告訴編譯器和其他程式員某值應該保持不變。
1. 面對指針,你可以指定指針自身、指針所指物,或兩者都(或都不)是 const:
1char myName[] = "小林coding";
2char *p = myName; // non-const pointer, non-const data
3const char* p = myName; // non-const pointer, const data
4char* const p = myName; // const pointer, non-const data
5const char* const p = myName; // const pointer, const data
- 如果關鍵詞const出現在星號(
*
)左邊,表示指針所指物是常量(不能改變 *p 的值); - 如果關鍵詞const出現在星號(
*
)右邊,表示指針自身是常量(不能改變 p 的值); - 如果關鍵詞const出現在星號(
*
)兩邊,表示指針所指物和指針自身都是常量;
2. 面對迭代器,你也指定迭代器自身或自迭代器所指物不可被改變:
1std::vector<int> vec;
2
3const std::vector<int>::iterator iter = vec.begin(); // iter 的作用像 T* const
4*iter = 10; // 沒問題,可以改變 iter 所指物
5++iter; // 錯誤! 因為 iter 是 const
6
7std::vector<int>::const_iterator cIter = vec.begin(); // cIter 的作用像 const T*