【Effective C++ 學習筆記】Item1-Item4

【1】 視C++爲一個語言聯邦(View C++ as a federation of languages.)

最初: C with classes

現在: 支持過程形式(procedural)、面向對象形式(object-oriented)、函數形式(functional)、泛型形式(generic)、元編程形式(metaprogramming)的語言。

C++語言聯邦中包含的次語言:

  • C 區塊、語句、預處理器、內置數據類型、數組、指針沒有模板、異常、重載
  • Object-oriented C++ classes、封裝、繼承、多態、virtual函數等等
  • Template C++
  • STL 它對容器、迭代器、算法以及函數對象的規約有着極佳的緊密配合與協調

C++高效編程守則視狀況而變化,取決於你使用C++的哪一部分。


【2】 儘量以const,enum,inline替換#define (prefer consts, enums, and inlines to #define.)

class GamePlayer{
private:
	static const int NumTurns = 5;	//常量聲明式
	int scores[NumTurns];			//使用該常量
};
const int GamePlayer::NumTurns;		//NumTurns的定義式
舊式的編譯器如果不支持上述語法的話可以將初值放在定義式,即

class CostEstimate {
private:
	static const double FudgeFactor;			//static class 常量聲明
};
const double CostEstimate::FudgeFactor = 1.35;	//static class 常量定義

另一個常見的#define誤用情況是以它來實現宏。


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

你可能知道爲宏中的所有實參加上小括號,但是縱使如此:

int a = 5, b = 0;
CALL_WITH_MAX(++a, b);           //a被累加兩次
CALL_WITH_MAX(++a, b+10);        //a被累加一次


在這裏,調用f之前,a的遞增次數竟然取決於“它被拿來和誰比較”!
幸運的是你只需要寫出template inline函數

template<typename T> inline void callWithMax (const T& a, const T& b)
{
	f(a > b ? a : b);
}

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

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


【3】儘可能使用const (Use const whenever possible.)


char greeting[] = "Hello";
char* p = greeting;
const char* p = greeting;
char* const p = greeting;
const char* const p = greeting;

如果const在星號左邊,代表被指物是常量;若果出現在星號右邊,表示指針自身是常量;如果出現在星號兩邊,表示被指物和指針兩者都是常量。


STL的迭代器的作用就像個T*指針。聲明迭代器爲const就像是聲明指針爲const一樣(即聲明一個T* const指針),表示這個迭代器不得指向不同的東西,但是它所指向的東西的值是可以改動的。如果你希望迭代器指向的東西不可改動(即希望STL模擬一個const T*指針),你需要的是const_iterator。


vector<int> vec;
...
const vector<int>::iterator iter = vec.begin();
*iter = 10;    //正確,改變所指之物
++iter;        //錯誤,iter是const

vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;   //錯誤,*cIter是const
++cIter;       //沒問題,改變cIter

再看一個例子:



class TextBlock {
public:
    ...
    const char& operator[] (size_t position) const
    {    return text[position];    }
    char& operator[](size_t position)
    {    return text[position];    }
private:
    string text;
};

只要重載了operator[]並對不同版本給予不同的返回類型,就可以令const 和 non-const TextBlocks獲得不同的處理:

cout<<tb[0];         //沒問題——讀一個non-const
tb[0] = 'x';         //沒問題——寫一個non-const
cout<<ctb[0];        //沒問題——讀一個const
ctb[0] = 'x';        //錯誤!——寫一個const

注意其中的聲明:

TextBlock tb("Hello");

const TextBlocks("World");

同樣需要注意的是,non-const operator[] 的返回類型是一個 char&,而不是char。如果是char,那麼tb[0] = 'x' 就無法通過編譯。

class CTextBlock {
public:
    ...
    char& operator[](size_t position) const
    {    return pText[position];    }
private:
    char* pText;
};

這個class不適當地將其 operator[] 聲明爲const成員函數,而該函數卻返回了一個reference指向對象內部值。假設暫時不管這個事實,請注意,operator[]實現代碼並不更改 pText。於是編譯器很開心地位operator[] 產出目標代碼。但是看看它允許發生什麼事情:

const CTextBlock cctb("Hello");    //聲明一個常量對象
char* pc = &cctb[0];               //調用const operator[] 取得一個指針,指向cctb 的數據
*pc = 'J';                         //cctb現在有了"Jello"這樣的內容

這其中當然不該有任何錯誤:你創建一個常量並且設以某值,而且只對它調用const成員函數。但你終究還是改變了它的值。


有關const_cast

class A  
{  
public:  
     A()  
     {  
      m_iNum = 0;  
     }  
      
public:  
     int m_iNum;  
};  
      
void foo()  
{  
    //1. 指針指向類  
    const A *pca1 = new A;  
    A *pa2 = const_cast<A*>(pca1);  //常量對象轉換爲非常量對象  
    pa2->m_iNum = 200;    //fine  
     
    //轉換後指針指向原來的對象  
    cout<< pca1->m_iNum <<pa2->m_iNum<<endl; //200 200  
      
    //2. 指針指向基本類型  
    const int ica = 100;  
    int * ia = const_cast<int *>(&ica);  
    *ia = 200;  
    cout<< *ia <<ica<<endl;   //200 100  
}

更加詳細請點擊


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


【4】確定對象被使用前已先被初始化 (Make sure that objects are initialized before they're used.)



通常如果你使用C part of C++而且初始化可能招致運行期成本,那麼就不保證發生初始化。一旦進入non-Const parts of C++,規則有些變化。這就很好地解釋了爲什麼array(來自Cpart of C++)不保證其內容被初始化,而vector(來自STL part of C++)卻有此保證。

至於內置類型以外的任何其他東西,初始化責任落在構造函數身上。


基於賦值的構造函數首先調用default構造函數爲成員設初值,然後立刻對它們賦予新值。default構造函數的一切作爲因此浪費了。成員初值列(member initialization list)的做法避免了這一問題,因爲初值列中針對各個成員變量而設置的實參,被拿去作爲各成員變量之構造函數的實參。


對大多數類型而言,比起先調用default構造函數然後調用copy assignment 操作符,單隻調用一次copy構造函數是比較高效的,有時甚至高效得多。對於內置型對象如numTimesConsulted,其初始化和賦值的成本相同,但爲了一致性最好也通過成員初值列來初始化。


ABEntry::ABEntry (const string& name, const string& address, const list<PhoneNumber>& phones)
    :theName(name),
     theAddress(address),
     thePhones(phones),
     numTimesConsulted(0)
{    }

non-local static 變量初始化順序不確定,帶來的問題


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

構造函數最好使用成員初值列(member initialization list),而不要在構造函數中使用賦值操作(assignment),其排列次序應該和它們在class中的聲明次序相同。

爲避免“跨編譯單元之初始化序”問題,請以local static對象代替non-local static 對象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章