《Effective C++》學(xué)習(xí)筆記(1)

1 讓自己習(xí)慣 C++

條款01:視 C++ 為一個(gè)語(yǔ)言聯(lián)邦

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

  • C:說(shuō)到底C++仍是以C為基礎(chǔ)。區(qū)塊,語(yǔ)句,預(yù)處理器,內(nèi)置數(shù)據(jù)類(lèi)型,數(shù)組,指針統(tǒng)統(tǒng)來(lái)自C。
  • Objective-Oriented C++:C with Classes所訴求的。這一部分是面向?qū)ο笤O(shè)計(jì)之古典守則在C++上的最直接實(shí)施。類(lèi),封裝,繼承,多態(tài),virtual函數(shù)等等...
  • Template C++:C++的泛型編程部分
  • STL:template程序庫(kù)。容器(containers),迭代器(iterators),算法(algorithms)以及函數(shù)對(duì)象(function objects)...

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


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

C++ 編譯過(guò)程:預(yù)處理 --> 編譯 --> 鏈接
預(yù)處理過(guò)程掃描源代碼,對(duì)其進(jìn)行初步的轉(zhuǎn)換,產(chǎn)生新的源代碼提供給編譯器。檢查包含預(yù)處理指令的語(yǔ)句和宏定義,并對(duì)源代碼進(jìn)行相應(yīng)的轉(zhuǎn)換。預(yù)處理過(guò)程還會(huì)刪除程序中的注釋和多余的空白字符。

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

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

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

class GamePlays{
private:
  static const int NumTurns = 5;      // static整數(shù)型class常量
  enum { NumTurns = 5 };             // 枚舉
  int scores[NumTurns];
... ...
}
  • 宏看起來(lái)像函數(shù),但不會(huì)招致函數(shù)調(diào)用帶來(lái)的額外開(kāi)銷(xiāo)。如果你想獲得高效,建議使用inline內(nèi)聯(lián)函數(shù)。

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

** note: **

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

條款03:盡可能使用 const

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

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

在C++中,只有被聲明為const的成員函數(shù)才能被一個(gè)const類(lèi)對(duì)象調(diào)用。

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

上面代碼進(jìn)行了兩次轉(zhuǎn)型:

  1. 第一次用static_cast來(lái)為*this添加const,這使接下來(lái)調(diào)用operator[ ]時(shí)得以調(diào)用const版本;
  2. 第二次則是用const_cast從const operator[]的返回值轉(zhuǎn)除const,以符合non-const返回值類(lèi)型。

** note: **

  1. 將某些東西聲明為const可幫助編譯器偵測(cè)出錯(cuò)誤用法。const可被施加于任何作用域內(nèi)的對(duì)象、函數(shù)參數(shù)、函數(shù)返回類(lèi)型、成員函數(shù)本體。
  2. 編譯器強(qiáng)制實(shí)施bitwise constness,但你編寫(xiě)程序時(shí)應(yīng)該使用“概念上的常量性”;
  3. 當(dāng)cosnt和non-const成員函數(shù)有著實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí),令non-const版本調(diào)用const版本可避免代碼重復(fù)。

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

  • 對(duì)內(nèi)置類(lèi)型(基本類(lèi)型)手動(dòng)進(jìn)行初始化。

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

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

  • “不同編譯單元內(nèi)定義之non-local-static對(duì)象”的初始化次序。
    static對(duì)象,其壽命從被構(gòu)造出來(lái)直到程序結(jié)束為止,包括global對(duì)象,定義于namespace作用域內(nèi)的對(duì)象,在classes內(nèi)、在函數(shù)內(nèi)、以及在file作用域內(nèi)被聲明為static的對(duì)象。函數(shù)內(nèi)的static對(duì)象被稱(chēng)為local static對(duì)象(因?yàn)樗鼈儗?duì)函數(shù)而言是local),其他static對(duì)象稱(chēng)為non-local static對(duì)象。

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

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

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;
}

經(jīng)過(guò)上面的處理,將non-local轉(zhuǎn)換了local對(duì)象,這樣做的原理是:函數(shù)內(nèi)的local static 對(duì)象會(huì)在"該函數(shù)被調(diào)用期間","首次遇上該對(duì)象之定義式"時(shí)被初始化,這樣就保證了對(duì)象被初始化。使用函數(shù)返回的“指向static對(duì)象”的reference,而不再使用static對(duì)象本身。這樣做的好處是不調(diào)用函數(shù)時(shí),不會(huì)產(chǎn)生對(duì)象的構(gòu)造和析構(gòu)。但對(duì)多線程這樣的方法會(huì)有問(wèn)題。

** note: **

  1. 為內(nèi)置對(duì)象進(jìn)行手工初始化,因?yàn)镃++不保證初始化它們;
  2. 構(gòu)造函數(shù)最好使用成員初始化列表,而不要在構(gòu)造函數(shù)本體內(nèi)使用賦值操作。初始化列表列出的成員變量,其排列次序應(yīng)該和它們?cè)陬?lèi)中的聲明次序相同;
  3. 為免除“跨編譯單元之初始化次序”問(wèn)題,請(qǐng)以local static對(duì)象替換non-local static對(duì)象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容

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