【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++的哪一部分。
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 對象。