Accustoming Youself to C++
條款1.視c++爲一個語言聯邦
C++是個多重範型編程語言,同時支持過程形式、面向對象形式、函數形式、泛型形式、元編程形式。視c++爲一個語言聯邦,包括以下四個部分:
- C. C++說到底還是以C爲基礎。這是面向過程的部分,C與C++相比,少了模板、異常以及重載。
- Object-Oriented. 面向對象部分,包括封裝、繼承、多態以及虛函數綁定等。
- Template C++. 泛型編程部分,template metaprogramming(TMP,模板元編程),其中C++標準模板庫STL在這各部分的基礎上。
-
STL. C++標準模板庫,包括容器、迭代器、算法以及函數對象等。
請記住:
C++高效編程守則視狀況而變化,取決於使用的是這四個部分中的哪一部分。
條款2.儘量以const,enum,inline代替#define
等價描述爲寧可以編譯器代替預處理器。
#define即宏定義在預處理階段進行處理,通常情況下記號名稱會進入記號表(symbol table),也有可能在編譯器處理前就被預處理器移走了,沒有進入記號表,這時就會編譯出錯。解決的辦法使用常量來代替宏。
例子:const double AspecRatio = 1.653;
來代替#define ASPEC_RATIO 1.653;
特別注意兩點:
- 指針常量與常量指針,特別地,使用const string
代替const char* const
;
- class專屬常量:static const來修飾,靜態常量:
例如:class GamePlayer{ static const int NumTurns = 5; //常量聲明式};
或者使用常量類內聲明+類外定義: class GamePlayer{static const int NumTurns;};// 常量聲明式
const int GamePlayer::NumTurns = 5;//常量定義式
另外無法使用宏定義來創建一個class 專屬常量,因爲宏定義並不重視作用域。宏定義只是簡單嵌入,並不做類型檢查,所以在定義宏時,請爲宏的所有實參加上小括號。
例如:#define CALL_MAX(a, b) ((a) > (b) ? (a) : (b))
調用:
int a = 5, b = 0;
CALL_MAX(++a, b); //a被累加1次
CALL_MAX(++a, b + 10); //a被累加2次
- 1
- 2
- 3
這是不可預料的行爲,因此使用內聯函數來代替宏定義函數。
請記住:
對於單純常量,最好使用const對象或enum替換#defines。
對於形似函數的宏,最好改用inline函數替換#defines。
條款3.儘可能使用const
const關鍵字告訴編譯器某值應該保持不變,即爲只讀的。
- 常量指針與指針常量
例如:STL中的迭代器。
const vector<int>::iterator ite1;//類似於常量指針,ite1是隻讀的
vector<int>::const_iterator ite2;//類似於指針常量,*ite2是隻讀的
- 1
- 2
- const可以幫助編譯器甄別錯誤,例如判斷“==”和賦值。
const Rational operator*(const Rational& lhs, const Rational& rhs);//有理數類乘法重載
Rational a, b, c;
if(a * b = c)...//這一步本來是想做比較操作,誤寫成了賦值操作,因爲返回const對象,編譯器就能發現這個錯誤。
- 1
- 2
- 3
- const成員函數
const成員函數代表this指針的類型爲:const className const*
。
改善C++程序效率的一個根本方法是以pass by reference-to-const
方式傳遞對象,這一前提是必須使用const成員函數來處理const對象。
const成員函數的兩個流行概念:
bitwise constness(physical constness):任何成員變量都是隻讀時纔可以說是const。(在Object Model 2.2 Copy Constructor的構造操作中,bitwise copies)
logical constness:一個const成員函數可以修改它處理的對象內的某些bits,但只有在客戶端偵測不出來的情況下纔可以,例如修改指針所指的對象。
如何釋放non-static成員函數的bitwise constness約束:使用mutable關鍵字,即使是在const成員函數中,mutable修飾的成員也是可變的。- 在const和non-cons成員函數中避免重複
利用non-const版本調用const版本,再加上轉型const_cast、static_cast,例如:
- 在const和non-cons成員函數中避免重複
const char& operator[](int pos) const;//const版本
char& operator[](int pos) { //non-const版本
return const_cast<char&> //返回值轉型,移除const
(static_cast<const className&>(*this)[pos]);//*this轉型,增加const
}
- 1
- 2
- 3
- 4
- 5
反過來使用const版本調用non-const版本則是一種錯誤行爲。
請記住:
將某些東西聲明爲const可以幫助編譯器偵測除錯誤;const可以被施加在任何作用域內的對象、函數參數、函數返回類型、成員函數本身。
編譯器強制實施bitwise constness,但是你編寫程序時應該使用“概念上的常量性”(conceptual constness)。
當const和non-const成員函數有着實質等價的實現時,使用non-const版本來調用const版本可以避免代碼重複。
條款4.確定對象使用前已被初始化
永遠在對象使用之前將其初始化,對於內置類型,請手動初始化;對於非內置類型,初始化的任務則落在了構造函數身上。
別混淆賦值和初始化的概念,例如:
class ABEntry {
public:
ABEntry(const string& name, const string& address, int num) {
//這裏都是賦值,而非初始化,實際上是先調用default ctor,再賦值
_name = name;
_address = address;
_num = num;
}
private:
string _name;
string _address;
int _num;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
更好的辦法是使用所謂的成員初值列(member initialization list):
ABEntry::ABEntry(const string& name, const string& address, int num)
: _name(name), _address(address), _num(num) { }
//直接進行copy ctor,就是初始化
- 1
- 2
- 3
特別注意的是:在初值列中列出所有的成員變量,避免遺漏;另外,如果成員變量是const 或者reference,一定需要初值,而不能賦值,最簡單的做法就是使用成員初值列。
C++有着固定的初始化順序:按照聲明順序進行初始化,且基類先於派生類初始化。
class init {
public:
init(int i) : _j(i), _i(_j);
//這裏會出現錯誤,因爲先對_i初始化,再對_j初始化。
private:
int _i;
int _j;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
對於local static object(局部靜態對象),程序結束之後,該對象會被自動銷燬。
基於局部靜態對象的singleton設計模式:
class Singleton
{
public:
static Singleton& getInstance() {
static Singleton s;
return s;
}
private:
Singleton() {}
Singleton(const Singleton&);
void operator=(const Singleton&);
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
請記住:
爲內置類型進行手工初始化,C++不保證初始化它們。
構造函數最好使用成員初值列,不要在構造函數中使用賦值操作,初值列中的成員變量的順序應該和聲明順序一致。
爲免除“跨編譯單元之初始化次序”問題,使用返回local static對象引用的函數來代替non-local static對象。
參考自侯捷老師翻譯的《Effective C++》中文版第三版
轉載自:https://blog.csdn.net/qq_25467397/article/details/80344261