文章目錄
-
- 一、視C++為一個語言聯邦
-
- 1、C++不同于C的部分
- 2、可以将C++分為4個層次
- 二、盡量以const,enum,inline替換#define
-
- 1、#define存在的問題
- 2、const,enum,inline的好處
- 3、請記住
- 三、盡可能使用const
-
- 1、const修飾變量
- 2、const修飾函數參數
- 3、const成員函數
- 4、函數傳回const
- 5、請記住
- 四、确定對象使用前已被初始化
-
- 1、成員初始化清單
- 2、以local static對象替換non-local static對象
- 3、請記住
一、視C++為一個語言聯邦
C是面向過程的語言,它的側重點在于算法和資料結構。編寫C代碼,側重點在于通過輸入資料,設計一定的算法過程,得到輸出。C++是面向對象的語言,它的側重點在于抽象出對象模型,使這個模型和問題像契合。通過對對象狀态的控制,來解決問題。
1、C++不同于C的部分
class:雖然C語言也有結構體struct,但是它更多的側重于資料機構,側重資料的組織。雖然struct在++中也支援class的個各種特性,但是很少用struct去替代class。它們兩個一個不同在于class預設成員的通路是private,而struct是public。
template:模闆屬于泛型程式設計,泛型程式設計使得代碼獨立于特定的資料類型,可以大大減少代碼量。
overload:重載是C語言中沒有的,在一些代碼中經常看到external “C",這是表示以C語言方式編譯。因為重載是通過編譯時,在函數明後加上函數參數類型來生成函數名實作的,而C語言則不是,是以如果要給C調用,就要加上extern “C”。
2、可以将C++分為4個層次
- C:C++實在C語言的基礎上發展而來的。
- Object-Oriented C++:這是C++中不同于C的部分,這裡主要指面向對象。
- Template C++:C++中的泛型程式設計。
- STL:這是一個标準模闆庫,它用模闆實作了很多容器、疊代器和算法,使用STL往往事半功倍。
二、盡量以const,enum,inline替換#define
1、#define存在的問題
【不容易定位錯誤】:
在預處理時, 所有使用PI的地方都将被替換,之後編譯器在編譯時從未看到過PI。這時如果遇到錯誤,報錯時給出的是3.1415926,而不是PI,因為PI從未進入到符号表,這将導緻錯誤難以了解。一個替換的方法是如下定義:
【class專屬常量】:
專屬于class作用域的常量。專屬于class的常量将這個常量限定在class的作用域内,而#define定義的常量沒有作用域的限制,一旦在某一處有個宏定義,在其後面都有效(除非#undef)。這意味着#define不僅不能夠用來定義class專屬常量,也不能提供任何封裝性。
class GamePlayer
{
static const int NumTurns=5; // 所有執行個體共享一份
int scores[NumTurns];
};
2、const,enum,inline的好處
- const的好處:
- define直接常量替換,出現編譯錯誤不易定位(不知道常量是哪個變量);
- define沒有作用域,const有作用域提供了封裝性。
- enum的好處:
- 提供了封裝性;
- 編譯器肯定不會配置設定額外記憶體空間(其實const也不會)。
- inline的好處:
- define宏函數容易造成誤用。
3、請記住
- 對于單純常量,最好以const對象或者enum替換#define。
- 對于形似函數的宏,最好改用inline函數替換#define。
三、盡可能使用const
1、const修飾變量
// 表示a是一個常量。
const T a;
//表示a是一個指向常量的指針,*a是不能被修改的,但是a可以被修改。
const T *a;
// 表示a是一個指向整數的常指針,a是不能被修改的,但是*a是可以被修改的。
T * const a;
// 表示a是一個指向常量的常指針,*a是不能被修改的,a也是不能被修改的。
const T * const a;
2、const修飾函數參數
傳遞過來的參數在函數内不能改變,與修飾變量性質一樣。
void display(const int a, int b)
{
a=5; // 錯誤。
}
3、const成員函數
const成員函數的目的是确認該函數可以用到const對象上。const成員函數使得:
- class接口更加容易了解,确認哪些接口可以修改class成員。
- 使操作const對象成為可能。
關于第2點,是因為const對象隻能調用const成員函數,但是非const對象既可以調用普通成員函數,也可以調用const成員函數。這是因為this指針可以轉換為const this,但是const this不能轉換為非const this。
【Note】:
(1)一個函數是不是const是可以被重載的。
(2)如果某些成員變量可能總是會被修改,即使是在const成員函數中,可以對這一類變量使用mutable關鍵字。
4、函數傳回const
若函數的傳回值是指針,且用const修飾,則函數傳回值指向的内容是常數,不可被修改,此傳回值僅能指派給const修飾的相同類型的指針。令函數傳回一個常量值,往往可以降低客戶錯誤而造成的意外,又不至于放棄安全性和高效性。
Rational a, b, c;
(a * b) = c; // 避免無意義的指派操作。
5、請記住
- 将某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加于任何作用域内的對象、函數參數、函數傳回類型、成員函數本體。
- 編譯器強制實施bitwise constness,但你編寫程式時應該使用“概念上的常量性”(conceptual constness)。
- 當const 和non-const成員函數有着實質等價的實作時,令non-const版本調用const版本可避免代碼重複。
四、确定對象使用前已被初始化
1、成員初始化清單
在一些語境下會初始化為0,但在另一些語境下可能就不會初始化,例如:
class Point
{
int x, y;
}
在構造函數中完成初始化時,要區分指派和初始化。在構造函數體内的是指派,在初始化清單中的才是初始化。
class Point
{
public:
Point(int x_, int y_):x(x_), y(y_) { }
private:
int x, y;
}
初始化效率往往高于指派。指派是先定義變量,在定義的時候可能已經調用了變量的構造函數,之後指派是調用了指派操作符;而初始化是直接調用了複制構造函數。
【Note】:
(1)初始化順序要和聲明順序一緻。
(2)在一些情況下必須使用初始化方式,有些周遊在定義時就要求初始化,例如const和引用。
2、以local static對象替換non-local static對象
将每個non-local static對象搬到自己的專屬函數内(該對象在此函數内被聲明為static)。這些函數傳回一個reference指向它所含的對象。然後使用者調用這些函數,而不是直接指涉這些對象。換句話說,non-local static對象被local static對象替換了。
class FileSystem{
/..../
std::size_t numDisks() const;
/..../
};
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {
/..../
Directory (params);
/..../
};
Directory::Directory(params)
{
/..../
// 如果是tfs.numDisks()則無法保證tfs在tempDir之前被初始化。
std::size_t disks = tfs().numDisks();
/..../
}
Directory& tempDir()
{
static Directory td;
return td;
}
3、請記住
- 為内置型對象進行手工初始化,因為C++不保證初始化它們(C part of C++)。
- 構造函數最好使用成員初始化清單,而不要在構造函數本體内使用指派操作。初值列列出的成員變量,其排列次序應該和它們在class中的聲明次序相同。
- 為避免“跨編譯單元之初始化次序”問題,請以local static 對象替換 non-local static對象。