1 讓自己習慣C++
條款 01 視 C++ 為一個語言聯邦
-
C : C++以C為基礎,block、陳述句、前處理器、內置資料型別、陣列、指標都來自于C,當使用C++中的C成分作業時,沒有模板(Template)、沒有例外(Exceptions)、沒有多載(overloading),
-
Object-Oriented C++ : 也就是 C with classes,classes(包括建構式和解構式)、封裝(encapsulation)、繼承(inheritance)、多型(polymorphism)、virtual函式(動態系結)......等等,
-
Template C++ : C++的泛型編程部分,
-
STL(Standard Template Library) : 對容器、迭代器、演算法以及函式物件對的規約有極佳的緊密配合與協調,
*請記住 : *
? 1. 高效編程守則視狀況而變化,取決于你使用C++的哪一部分,
條款 02 盡量以 const,enum,inline 替代 #define
例 : `#define ASPECT_RATIO 1.653`前處理器會將程式中的`ASPET_RATIO`記號全部替換成數值`1.653`,也就是這個記號不會進入記號表內,這樣在程式出錯時獲取的錯誤資訊會很難分析,
? 當我們使用#define 實作宏時,更要小心,必須為宏中的所有實參加上小括號,例 :
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))//一定要加小括號,這里的例子還體現不出什么,注意加括號的位置就行
int a = 5,b = 0;
//當第一個引數(++a)大于第二個引數時,++a會被執行兩次,這顯然很容易出問題
CALL_WITH_MAX(++a,b);
CALL_WITH_MAX(++a,b+10);
?
解決方法 :
-
以一個常量替換上述宏,
const double Aspect_ratio = 1.653;作為一個語言常量,自然會進入記號表,這樣在Debug的時候容易跟蹤,且生成的代碼量比較小,開支較小,因為宏定義是盲目地替換欄位,以 const 代替 #define 時要注意兩點 :
? (1) 定義常量指標時,要注意頂層 const 和底層 const 的區別,頂層 const 是將 const 放在 '*' 左邊,意思是指標指向的常量型別,所以該指標所指向的內容不能被修改;
? 而底層 const 則是將 const 放在 '*' 右邊,意思是我這個指標是一個常量指標,只能指向在初始化時的值,不能指向其他變數,
? (2)定義 class 專屬常量時,也就是類內 static 成員,例如
class GamePlayer{ private: static const int NumTurns = 5; int scores[NumTurns]; };? 注意,上例中的的
static const int NumTurns = 5;是宣告式而不是定義式,C++要求要對使用的任何東西都要提供一個定義式,? 但如果它是個 class 專屬常量,又是 static 且為整數型別(integral type,例如int,char,bool),只要不取指標,則只需要提供宣告式,若有些編譯器不支持,則需要在實作檔案中定義,(注意非整型必須定義,初值可以放在定義式中)
//.h class CostEstimate{ private: static const double FudgeFactor; }; //.c const double CostEstimate::FudgeFactor = 1.35;? 另外,#define 不重視作用域(scope),在定義后編譯程序中都有效,
-
使用
enum hack這種做法比較像
#define,同樣不能取地址,同樣會導致非必要的記憶體分配, -
對于“實作宏“,用
template inline函式,同樣是代碼替換,但這種做法屬于函式操作,一切按函式操作就行了,不用操心引數問題,同時遵守作用域和訪問規則,
請記住:
? 1. 對于單純常量,最好以 const 物件或 enum 替換 #define
2. 對于形似函式的宏(macros),最好改用 inline template 函式替代 #define
條款 03 盡可能使用const
? 頂層 const : 表示 const 修飾的型別為常量,特別地,對于指標型別,const 在 ‘*’ 右邊時表示頂層指標,也就是該指標變數為常量, 與該指標變數指向的物件是否為常量無要求,
int i = 0;
int *const p1 = &i;//const 在‘*’右邊,表示p1只能指向i(頂層指標)
? 底層 const : 與指標和參考型別有關,對于指標型別,當 const 在 ‘*’ 左邊(一般寫在型別左邊)時,表示該指標指向的物件為常量型別, 此時該指標可以指向別的物件,當不能通過指標解參考來更改所指物件的值,
? 而對于參考型別,都是底層 const,也就是所參考的值為常量,但const參考可以系結常量與非常量,這是一個特殊的例子,
int i = 0;
const int j = 1;
const int &ref_i = i;//可以系結非常量
const int &ref_j = j;//可以系結常量型別
const int &ref = 10;//會創建臨時變數,然后系結
? const可以和函式的回傳值、引數、函式自身產生關聯,盡可能地使用它,可以讓編譯器幫你更快的發現錯誤!
const成員函式
? 將 const 作用在成員函式上,可以區分出常量物件和非常量物件所使用的不同版本的成員函式,const成員函式可以保證不修改類的成員變數,
//.h
class TextBlock{
public:
// ...
const char& operator[](std::size_t position) const//const物件呼叫此函式
{ return text[position]; }
char& operator[](std::size_t position)//非const物件呼叫此函式
{ return text[position]; }
private:
std::string text;
};
//.cpp
TextBlock tb("Hello");
std::cout << tb[0];//呼叫 char& operator[](std::size_t position)
const TextBlock ctb("Hello");
std::cout << ctb[0];//呼叫 const char& operator[](std::size_t position) const
? 在C++中,不修改類的成員變數指的是編譯器強制實施的“bitwise constness”,也就是保證每一個bit都不允許修改,但現實中,我們可能會希望一部分成員不被修改,一部分成員被修改,
? 此時,我們可以將能在const成員函式中修改的成員變數宣告為mutable
//.h
class CTextBlock{
public:
//...
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsVaild;
};
//.cpp
std::size_t length() const
{
if(!lengthIsVaild)
{
pText = "Hello";//錯誤,非mutable變數在const成員函式中禁止修改
textLength = std::strlen(pText);//允許修改
lengthIsvaild = true;//允許修改
}
return textLength;
}
在const和non-const成員函式中避免重復
? 很多時候,const和non-const函式知識回傳值不同,函式定義會出現冗余,
class TextBlock{
public;
//...
const char& operator[](std::size_t position) const
{
//... //邊界檢查
//... //日志資料訪問
//... //檢驗資料完整性
return text[position];
}
/*
//冗余版本
char& operator[](std::size_t position)
{
//... //邊界檢查
//... //日志資料訪問
//... //檢驗資料完整性
return text[position];
}
*/
char& operator[](std::size_t position)
{
return const_cast<char&>(//去除回傳值的const屬性
static_cast<const TextBlock>(*this)[position];//static_cast安全轉型為const物件呼叫const版本,回傳值為const char*
);
}
private:
std::string text;
};
解決冗余的原則是,在非常量的版本中對常量版本進行轉型,因為我們永遠不對常量版本的東西作任何修改,只單純呼叫,這完全符合const的設計 : const成員函式保證不修改成員,這避免了不必要的風險,
請記住:
? 1. 將某些宣告為 const 可幫助編譯器偵測出錯誤用法, const 可被施加于任何作用域內的物件、函式引數、函式回傳型別、成員函式 本體,
? 2. 編譯器強制實施 bitwise constness,但你撰寫程式時應該使用“概念上的常量性(conceptul constness),”
? 3. 當 const 和 non-const 成員函式有著實質等價的實作時,領 non-const 版本呼叫 const 版本可避免代碼重復,
條款 04 確定物件被使用前已先被初始化
? 有些型別(stl)保證內容能被默認初始化,而有些卻不行,
? 因此我們強制規定 : 永遠在使用物件之前先將它初始化,
int x = 0;
const char* text = "hello world";
double d;
std::cin >> d;
? 在C++中,除了內置型別外,初始化往往由建構式完成,
? 因此我們強制規定 : 確保每一個建構式都將物件中的每一個成員初始化,
? 注意不要混淆初始化和賦值這兩種概念,
//.h
class PhoneNumber {...};
class ABEntry{
public:
ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
//.cpp
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)
{ }
? 此時對于各個成員,只執行依次建構式即可完成初始化,
? 另外,初始化的次序應該對應宣告的次序,可以避免一些隱晦的錯誤,
現在我們關注“不同編譯單元內定義的non-local static物件”的初始化次序 :
- 編譯單元 : 產出單一目標問價的原始碼,基本上是 單一原始碼檔案 + 其所加入到的頭檔案,注意!編譯器編譯不同單元的次序無法確定!
- static物件 : 壽命從被構造出來直到程式結束,程式結束時自動呼叫其解構式,
- non-local static物件 : 不在函式內的static物件,
- local static物件 : 在函式內的static物件,
我們設想一種這樣的情況 :
? 在一個編譯單元記憶體在一個物件
class FileSystem{
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs;//宣告外部變數,預備給其他檔案(編譯單元)使用
? 在另一個編譯單元記憶體在另一個物件
class Directroy{
public:
Directory( params );
...
};
Directory::Directory( params )
{
...
std::size_t disks = tfs.numDisks();
}
Directory temp( params );
因為不同編譯單元編譯的次序是不確定的,我們無法保證Directory物件定義之前,類FileSystem已經被編譯,這樣會出現錯誤,
解決方法 :
Singleton模式
因為C++保證 : 函式內的local static物件會在“該函式被呼叫期間”“首次遇上該物件的定義式”時被初始化,
//編譯單元,
class FileSystem {...};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
//另一個編譯單元
class Directory {...};
Directory::Directory( params )
{
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir()
{
static Directory td;
return td;
}
因此可以保證呼叫tfs()時,只有FileSystem的定義式被編譯器遇到時才給里面的fs物件初始化并回傳,
但內含static物件在多執行緒系統中執行緒不安全,
*請記住 : *
? 1. 為內置型物件進行手工初始化,因為C++不保證初始化它們,
? 2. 建構式最好使用成員初始化串列,而不要在建構式本體內使用賦值操作,但如果有多個建構式會產生冗余代碼時,可以將具 有確定的默認初始化值的變數賦值封裝進一個函式,其余還是使用串列初始化,同時,串列初始化的次序也應該與宣告次序相同,
? 3. 為免除"跨編譯單元的初始化次序"問題,請以 local static 物件替換 non-local static 物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/502564.html
標籤:其他
上一篇:面試手撕并發演算法題
