當前位置:編程學習大全網 - 編程語言 - 如何閱讀 effective c++

如何閱讀 effective c++

轉載 記得前段時間又壹次拿起《Effective C++》的時候,有種豁然開朗的感覺,所以翻出了我第壹遍讀時做的筆記。只做參考以及查閱之用。如有需要請參閱《Effective C++》書本。 by shenzi/2010.5.17

壹.讓自己習慣C++

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

為了更好的理解C++,我們將C++分解為四個主要次語言:

C。說到底C++仍是以C為基礎。區塊,語句,預處理器,內置數據類型,數組,指針統統來自C。

Object-Oreinted C++。這壹部分是面向對象設計之古典守則在C++上的最直接實施。類,封裝,繼承,多態,virtual函數等等...

Template C++。這是C++泛型編程部分。

STL。STL是個template程序庫。容器(containers),叠代器(iterators),算法(algorithms)以及函數對象(function objects)...

請記住:

這四個次語言,當妳從某個次語言切換到另壹個,導致高效編程守則要求妳改變策略。C++高效編程守則視狀況而變化,取決於妳使用C++的哪壹部分。

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

這個條款或許可以改為“寧可 以編譯器替換預處理器”。即盡量少用預處理。

編譯過程:.c文件--預處理-->.i文件--編譯-->.o文件--鏈接-->bin文件

預處理過程掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。檢查包含預處理指令的語句和宏定義,並對源代碼進行相應的轉換。預處理過程還會刪除程序中的註釋和多余的空白字符。可見預處理過程先於編譯器對源代碼進行處理。預處理指令是以#號開頭的代碼行。

例:#define ASPECT_RATIO 1.653

記號名稱ASPECT_RATIO也許從未被編譯器看見,也許在編譯器開始處理源代碼之前它就被預處理器移走了。即編譯源代碼時ASPECT_RATIO已被1.653取代。ASPECT_RATIO可能並未進入記號表(symbol table)。

替換:const double AspectRatio = 1.653;

好處應該有:多了類型檢查,因為#define 只是單純的替換,而這種替換在目標碼中可能出現多份1.653;改用常量絕不會出現相同情況。

常量替換#define兩點註意:

定義常量指針:

const char *authorName = “Shenzi”;

cosnt std::string authorName("Shenzi");

類專屬常量:

static const int NumTurns = 5;//static 靜態常量 所有的對象只有壹份拷貝。

萬壹妳編譯器不允許“static整數型class常量”完成“in calss初值設定”(即在類的聲明中設定靜態整形的初值),我們可以通過枚舉類型予以補償:

enum { NumTurns = 5 };

*取壹個const的地址是合法的,但取壹個enum的地址就不合法,而取壹個#define的地址通常也不合法。如果妳不想讓別人獲取壹個pointer或reference指向妳的某個整數常量,enum可以幫助妳實現這個約束。

例:#define CALL_WITH_MAX(a,b) f((a) > (b)) ? (a) : (b))

宏看起來像函數,但不會招致函數調用帶來的額外開銷,而是壹種簡單的替換。

替換:

template<typename T>

inline void callWithMax(cosnt T &a, cosnt T &b)

{

f(a > b ? a : b);

}

callWithMax是個真正的函數,它遵循作用於和訪問規則。

請記住:

對於單純常量,最好以const對象或enums替換#defines;

對於形似函數的宏,最好改用inline函數替換#defines。

條款03:盡可能使用const

const允許妳告訴編譯器和其他程序員某值應保持不變,只要“某值”確實是不該被改變的,那就該確實說出來。

關鍵字const多才多藝:

例:

char greeting[] = "Hello";

char *p = greeting; //指針p及所指的字符串都可改變;

const char *p = greeting; //指針p本身可以改變,如p = &Anyother;p所指的字符串不可改變;

char * cosnt p = greeting; //指針p不可改變,所指對象可改變;

const char * const p = greeting; //指針p及所致對象都不可改變;

說明:

如果關鍵字const出現在星號左邊,表示被指物事常量。const char *p和char const *p兩種寫法意義壹樣,都說明所致對象為常量;

如果關鍵字const出現在星號右邊,表示指針自身是常量。

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

const成員函數使class接口比較容易被理解,它們使“操作const對象”稱為可能;

說明:聲明為const的成員函數,不可改變non-static成員變量,在成員變量聲明之前添加mutable可讓其在const成員函數中可被改變。

const_cast<char &>(static_cast<const TextBlock &>(*this))[position];

//static_cast 將TextBlock &轉為const TextBlock &;

//const_cast將返回值去掉const約束;

請記住:

將某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體;

編譯器強制實施bitwise constness,但妳編寫程序時應該使用“概念上的車輛”(conceptual constness);

當cosnt和non-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重復。

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

永遠在使用對象之前先將它初始化。對於無任何成員的內置類型,妳必須手工完成此事。至於內置類型以外的任何其它東西,初始化責任落在構造函數身上,確保每壹個構造函數都將對象的每壹個成員初始化。

賦值和初始化:

C++規定,對象的成員變量的初始化動作發生在進入構造函數本體之前。所以應將成員變量的初始化置於構造函數的初始化列表中。

ABEntry::ABEntry(const std::string& name, const std::string& address,

const std::list<PhoneNumber>& phones)

{

theName = name; //這些都是賦值,而非初始化

theAddress = address; //這些成員變量在進入函數體之前已調用默認構造函數,接著又調用賦值函數,

thePhones = phones; //即要經過兩次的函數調用。

numTimesConsulted = 0;

}

ABEntry::ABEntry(const std::string& name, const std::string& address,

const std::list<PhoneNumber>& phones)

: theName(name), //這些才是初始化

theAddress(address), //這些成員變量只用相應的值進行拷貝構造函數,所以通常效率更高。

thePhones(phones),

numTimesConsulted(0)

{ }

所以,對於非內置類型變量的初始化應在初始化列表中完成,以提高效率。而對於內置類型對象,如numTimesConsulted(int),其初始化和賦值的成本相同,但為了壹致性最好也通過成員初始化表來初始化。如果成員變量時const或reference,它們就壹定需要初值,不能被賦值。

C++有著十分固定的“成員初始化次序”。基類總是在派生類之前被初始化,而類的成員變量總是以其說明次序被初始化。所以:當在成員初始化列表中列各成員時,最好總是以其聲明次序為次序。

請記住:

為內置對象進行手工初始化,因為C++不保證初始化它們;

構造函數最好使用成員初始化列表,而不要在構造函數本體內使用賦值操作。初始化列表列出的成員變量,其排列次序應該和它們在類中的聲明次序相同;

為免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象。

二.構造/析構/賦值運算

幾乎妳寫的每個類都會有壹或多個構造函數、壹個析構函數、壹個拷貝賦值操作符。如果這些函數犯錯,會導致深遠且令人不愉快的後果,遍及整個類。所以確保它們行為正確時生死攸關的大事。

條款05:了解C++默默編寫並調用哪些函數

如果妳自己美聲明,編譯器就會為類聲明(編譯器版本的)壹個拷貝構造函數,壹個拷貝賦值操作符和壹個析構函數。此外如果妳沒有聲明任何構造函數,編譯器也會成為妳聲明壹個默認構造函數。所有這些函數都是public且inline。

惟有當這些函數被需要(被調用),它們才會被編譯器創建出來。即有需求,編譯器才會創建它們。

默認構造函數和析構函數主要是給編譯器壹個地方用來放置“藏身幕後”的代碼,像是調用基類和非靜態成員變量的構造函數和析構函數(要不然它們該在哪裏被調用呢)。

註意:編譯器產生的析構函數是個non-virtual,除非這個類的基類自身聲明有virtual析構函數。

至於拷貝構造函數和拷貝賦值操作符,編譯器創建的版本只是單純地將來源對象的每壹個非靜態成員變量拷貝到目標對象。

如壹個類聲明了壹個構造函數(無論有沒參數),編譯器就不再為它創建默認構造函數。

編譯器生成的拷貝賦值操作符:對於成員變量中有指針,引用,常量類型,我們都應考慮建立自己“合適”的拷貝賦值操作符。因為指向同塊內存的指針是個潛在危險,引用不可改變,常量不可改變。

請記住:

編譯器可以暗自為類創建默認構造函數、拷貝構造函數、拷貝賦值操作符,以及析構函數。

條款06:若不想使用編譯器自動生成的函數,就該明確拒絕

通常如果妳不希望類支持某壹特定技能,只要不說明對應函數就是了。但這個策略對拷貝構造函數和拷貝賦值操作符卻不起作用。因為編譯器會“自作多情”的聲明它們,並在需要的時候調用它們。

由於編譯器產生的函數都是public類型,因此可以將拷貝構造函數或拷貝賦值操作符聲明為private。通過這個小“伎倆”可以阻止人們在外部調用它,但是類中的成員函數和友元函數還是可以調用private函數。解決方法可能是在壹個專門為了阻止拷貝動作而設計的基類。(Boost提供的那個類名為noncopyable)。

請記住:

為駁回編譯器自動(暗自)提供的機能,可將相應的成員函數聲明為private並且不予實現。使用像noncopyable這樣的基類也是壹種做法。

條款07:為多態基類聲明virtual析構函數

當基類的指針指向派生類的對象的時候,當我們使用完,對其調用delete的時候,其結果將是未有定義——基類成分通常會被銷毀,而派生類的充分可能還留在堆裏。這可是形成資源泄漏、敗壞之數據結構、在調試器上消費許多時間。

消除以上問題的做法很簡單:給基類壹個virtual析構函數。此後刪除派生類對象就會如妳想要的那般。

任何類只要帶有virtual函數都幾乎確定應該也有壹個virtual析構函數。

如果壹個類不含virtual函數,通常表示它並不意圖被用做壹個基類,當類不企圖被當做基類的時候,令其析構函數為virtual往往是個餿主意。因為實現virtual函數,需要額外的開銷(指向虛函數表的指針vptr)。

STL容器都不帶virtual析構函數,所以最好別派生它們。

請記住:

帶有多態性質的基類應該聲明壹個virtual析構函數。如果壹個類帶有任何virtual函數,它就應該擁有壹個virtual析構函數。

壹個類的設計目的不是作為基類使用,或不是為了具備多態性,就不該聲明virtual析構函數。

  • 上一篇:各位,誰買過 對講機啊 ?我想咨詢個問題
  • 下一篇:哪位高手知道:泛海三江GBQTL2100消防聯動報警控制器,這個主機的設備組成,結構,原理。
  • copyright 2024編程學習大全網