《Effective C++》學習筆記(1)

1 讓自己習慣 C++

條款01:視 C++ 為一個語言聯邦

將C++視為一個由相關語言組成的聯邦而非單一語言。在某個次語言(sublanguage)中,各種守則與通例都傾向簡單、直觀易懂、并且容易記住。然而當你從一個次語言移往另一個次語言,守則可能改變。

  • C:說到底C++仍是以C為基礎。區塊,語句,預處理器,內置數據類型,數組,指針統統來自C。
  • Objective-Oriented C++:C with Classes所訴求的。這一部分是面向對象設計之古典守則在C++上的最直接實施。類,封裝,繼承,多態,virtual函數等等...
  • Template C++:C++的泛型編程部分
  • STL:template程序庫。容器(containers),迭代器(iterators),算法(algorithms)以及函數對象(function objects)...

** note: **
C++高效編程守則視狀況而改變,取決于你使用C++的哪一部分。


條款02:盡量以 const,enum,inline 替換 #define

C++ 編譯過程:預處理 --> 編譯 --> 鏈接
預處理過程掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。檢查包含預處理指令的語句和宏定義,并對源代碼進行相應的轉換。預處理過程還會刪除程序中的注釋和多余的空白字符。

“寧可以編譯器替換預處理器”。就是盡量少用預處理。

  • 預處理器#define ASPECT_RATIO 1.653將所有出現ASPECT_RATIO的地方替換為1.653,ASPECT_RATIO可能并未進入記號表(symbol table)。因此,當出現錯誤時報的是1.653而不是ASPECT_RATIO,導致目標定位有問題,問題追蹤有困難。如果使用變量,則可輕易地判斷。
    此外,盲目地把ASPECT_RATIO替換為1.653可能會在目標碼中出現多份1.653,改用常量絕不會出現相同情況。所以盡量定義為常量,const double ASPECT_RATIO = 1.653

  • 如果在數組初始化的時候,編譯器需要知道數組的大小,且編譯器(錯誤地)不允許使用“static整數型class常量”進行數組初始化,這時可以使用枚舉類型enum來替代define。

class GamePlays{
private:
  static const int NumTurns = 5;      // static整數型class常量
  enum { NumTurns = 5 };             // 枚舉
  int scores[NumTurns];
... ...
}
  • 宏看起來像函數,但不會招致函數調用帶來的額外開銷。如果你想獲得高效,建議使用inline內聯函數。

有了consts 、enums 和inlines,我們對預處理器(特別是#define) 的需求降低了,但并非完全消除。#include 仍然是必需品,而 #ifdef / #ifndef 也繼續扮演控制編譯的重要角色。目前還不到預處理器全面引退的時候,但我們要盡量限制預處理器的使用。

** note: **

  1. 對于單純常量,最好以const對象或enum替換#define。
  2. 對于形似函數的宏,最好改用inline函數替換#define。

條款03:盡可能使用 const

const允許你告訴編譯器和其他程序員某值應保持不變,只要“某值”確實是不該被改變的,那就該確實說出來。如果const修飾變量,則表示這個變量不可變;如果const修飾指針,表示指針指向的位置不可改變。

  • 和指針有關的const判斷:
  1. 如果關鍵字const出現在星號左邊,表示被指物事常量。const char *pchar const *p兩種寫法意義一樣,都說明所致對象為常量。
  2. 如果關鍵字const出現在星號右邊,表示指針自身是常量。
const char * p = "hello"; // *p的hello不可變, 與char const * p = "hello"等價
char * const p = "hello"; // 表示p的值不可變,即p不能指向其它位置
  • STL迭代器的const:
  1. 聲明迭代器為const就像聲明指針為const一樣(即聲明一個T* const指針),表示這個迭代器不得指向不同的東西,但它所指的東西的值可以改變。
  2. 如果想要迭代器所指的東西不可改變(即模擬一個const T*指針),使用const_iterator。
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); //類似T* const
*iter = 10;  //沒問題,改變iter所指物
++iter;      //錯誤!iter是const
std::vector<int>const_iterator cIter = vec.begin();  //類似const T*
*iter = 10;  //錯誤,*iter是const
iter++;      //沒問題,可以改變iter
  • 令函數返回一個常量值,可以避免意外錯誤。
    如下代碼,錯把==寫成=,一般程序對*號之后進行賦值會報錯,但在自定義操作符面前不會(因為自定義*號后返回的是Rational對象實例的引用,可以拿來賦值,不會報錯)。如果*不寫成const,則下面的程序完全可以通過,但寫成const之后,再對const進行賦值就出現問題了。函數的參數,如果無需改變其值,盡量使用const,這樣可以避免函數中錯誤地將==等于符號誤寫為=賦值符號,而無法察覺。
class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational& rhs);
...
Rational a, b, c;
if(a * b = c)...  //把==錯寫成=,比較變成了賦值
  • const作用于成員函數,有兩個作用:
  1. 可以知道哪些函數可以改變對象內容,哪些函數不可以。
  2. 改善C++效率,通過pass by reference_to_const(const對象的引用)方式傳遞對象可改善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;
};
/* 使用operator[] */
TextBlock tb("hello");              //non-const 對象
cout<<tb[0]<<endl;    //調用的是non-const TextBlock::operator[]
tb[0] = 'x';          //沒問題,寫一個non-const對象
const TextBlock cTb("hello");    //const 對象
cout<<cTb[0]<<endl;   //調用的是const TextBlock:operator[]
cTb[0] = 'x';          //錯誤,寫一個const對象

在C++中,只有被聲明為const的成員函數才能被一個const類對象調用。

  • 成員函數是const意味著什么?
  1. bitwise const主張const成員函數不可以改變對象內任何non-static成員變量。但一個更改了“指針所指物”的成員函數雖然不能算const,但如果只有指針(而非其所指物)隸屬于對象,那么稱此函數為bitwise const不會引發編譯器異議。
  2. logical const主張成員函數可以修改它所處理的對象內的某些bits,但要在客戶端偵測不出的情況下才得如此。
    編譯器默認執行bitwise。如果想要在const函數中修改non-static變量,需將變量聲明為mutable(可變的)。
class TextBlock{
    private:
        char* pText;
        mutable std::size_t textLength;    // 即使在const成員函數內,
        mutable bool lengthIsValid;        // 這些成員變量也可能會被更改。
    public:
        ...
        std::size_t length() const; 
};
std::size_t TextBlock::length() const{
    if (!lengthIsValid){
        textLength = std::strlen(pText);  //加上mutable修飾后,便可以修改其值
        lengthIsValid = true;
    }
}
  • 避免const和non-const成員函數重復
    如果const和non-const成員函數功能相當、代碼重復,編譯時間、維護等會是一個大問題,這時就用non-const函數去調用const函數,但不能反過來。這是因為non-const函數可能會改變對象,const函數承諾不改變對象,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]);
        } 
};

上面代碼進行了兩次轉型:

  1. 第一次用static_cast來為*this添加const,這使接下來調用operator[ ]時得以調用const版本;
  2. 第二次則是用const_cast從const operator[]的返回值轉除const,以符合non-const返回值類型。

** note: **

  1. 將某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加于任何作用域內的對象、函數參數、函數返回類型、成員函數本體。
  2. 編譯器強制實施bitwise constness,但你編寫程序時應該使用“概念上的常量性”;
  3. 當cosnt和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重復。

條款04:確定對象被使用前已先被初始化

  • 對內置類型(基本類型)手動進行初始化。

  • 內置類型以外的類型,初始化要靠構造函數,要確保每一個構造函數都將對象的每一個成員初始化。
    類的構造函數使用成員初值列(member initialization list),而不是在構造函數中進行賦值操作,這樣通常效率更高。因為賦值的版本其實是先進行初始化再進行賦值,而成員初值列版本是直接進行初始化,這對于非內置類型(std::string等)來說顯然后者效率更高。對于內置類型,其初始化和賦值的成本相同,但為了一致性最好也通過成員初值列來初始化。對于const和reference類型必須是初始化,賦值操作是不允許的。

  • base classes更早于derived classes被初始化,class的初值列成員變量的排列順序與其聲明順序相同。

  • “不同編譯單元內定義之non-local-static對象”的初始化次序。
    static對象,其壽命從被構造出來直到程序結束為止,包括global對象,定義于namespace作用域內的對象,在classes內、在函數內、以及在file作用域內被聲明為static的對象。函數內的static對象被稱為local static對象(因為它們對函數而言是local),其他static對象稱為non-local static對象。

// FileSystem源文件 class FileSystem{ public: ... std::size_t numDisks() const; };
extern FileSystem tfs;
// Directory源文件,與FileSystem處于不同的編譯單元
class Directory{
    public:
        Directory(params);
        ...
};
Directory::Directory(params){
    ...
    //調用未初始化的tfs會出現錯誤
    std::size_t disks = tfs.numDisks();
}

C++對“定義于不同編譯單元內的non-local static對象”的初始化相對次序并無明確定義,因此,為防止A的初始化需要B,但B尚未初始化的錯誤,將每個non-local static對象搬到自己的專屬函數內(該對象在此函數內被聲明為static),然后用戶調用這些函數,而不直接涉及這些對象。

class FileSystem { ... };
FileSystem& tfs(){
    static FileSystem fs;
    return fs;
}
class Directory { ... };
Directory::Directory(params){
    std::size_t disks = tfs().numberDisks();
}
Directory& tempDir(){
    static Directory td;
    return td;
}

經過上面的處理,將non-local轉換了local對象,這樣做的原理是:函數內的local static 對象會在"該函數被調用期間","首次遇上該對象之定義式"時被初始化,這樣就保證了對象被初始化。使用函數返回的“指向static對象”的reference,而不再使用static對象本身。這樣做的好處是不調用函數時,不會產生對象的構造和析構。但對多線程這樣的方法會有問題。

** note: **

  1. 為內置對象進行手工初始化,因為C++不保證初始化它們;
  2. 構造函數最好使用成員初始化列表,而不要在構造函數本體內使用賦值操作。初始化列表列出的成員變量,其排列次序應該和它們在類中的聲明次序相同;
  3. 為免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容

  • C++運算符重載-下篇 本章內容:1. 運算符重載的概述2. 重載算術運算符3. 重載按位運算符和二元邏輯運算符4...
    Haley_2013閱讀 1,450評論 0 49
  • 1. 讓自己習慣C++ 條款01:視C++為一個語言聯邦 為了更好的理解C++,我們將C++分解為四個主要次語言:...
    Mr希靈閱讀 2,830評論 0 13
  • 接著上節 condition_varible ,本節主要介紹future的內容,練習代碼地址。本文參考http:/...
    jorion閱讀 14,808評論 1 5
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,529評論 1 51
  • (1) 浣衣池邊纖手滌 非往昔佳人 淚落紅妝散 (2) 徐徐春風梓木馨 黃昏落日是三金 羨煞眾人心! (3) 雨夜...
    劍飛先森閱讀 302評論 0 5