1.儘可能使用初始化列表而不要再構造函數裏賦值,初始化順序和聲明的順序一致,一些類型如const,引用等,必須使用初始化。對於非內部數據類型成員對象應當採用初始化表,以獲取更高的效率。
example:
B::B(const A& a):m_a(a){}只調用了類A的拷貝構造函數
2.基類都使用虛析構函數,這樣才能在使用多態時,準確的析構派生類
3.operator>>和operator<<決不能是成員函數。若是f是operator>>或operator<<,讓f成爲非成員函數。若是f還須要訪問c的非公有成員,讓f成爲c的友元函數。在類的內部,它能夠用於靜態和非靜態成員。
4.儘量使用const.const關鍵字實在是神通廣大。在類的外面,它能夠用於全局或名字空間常量,以及靜態對象(某一文件或程序塊範圍內的局部對象)。在類的內部,它能夠用於靜態和非靜態成員.
對指針來講,能夠指定指針自己爲const,也能夠指定指針所指的數據爲const,或兩者同時指定爲const,還有,二者都不指定爲const:
char *p = "hello"; // 非const指針,
// 非const數據
const char *p = "hello"; // 非const指針,
// const數據
char * const p = "hello"; // const指針,
// 非const數據
const char * const p = "hello"; // const指針,
// const數據
5.儘可能用「傳引用」而不用「傳值」
「經過值來傳遞一個對象」的具體含義是由這個對象的類的拷貝構造函數定義的。這使得傳值成爲一種很是昂貴的操做。
爲避免這種潛在的昂貴的開銷,就不要經過值來傳遞對象,而要經過引用:
const student& returnstudent(const student& s)
{ return s; }
這會很是高效:沒有構造函數或析構函數被調用,由於沒有新的對象被建立。函數
6.返回值:對於賦值函數,應當用「引用傳遞」的方式返回對象,對於相加函數應當用值傳遞,由於引用對象在函數結束時被銷燬。ui
例如:this
class String {… // 賦值函數 String & operate=(const String &other); // 相加函數,若是沒有friend修飾則只許有一個右側參數 friend String operate+( const String &s1, const String &s2); private: char *m_data; } //String的賦值函數operate = 的實現以下: String & String::operate=(const String &other) { if (this == &other) return *this; delete m_data; m_data = new char[strlen(other.data)+1]; strcpy(m_data, other.data); return *this; // 返回的是 *this的引用,無需拷貝過程 }
對於賦值函數,應當用「引用傳遞」的方式返回String對象。若是用「值傳遞」的方式,雖然功能仍然正確,但因爲return語句要把 *this拷貝到保存返回值的外部存儲單元之中,增長了沒必要要的開銷,下降了賦值函數的效率。例如:spa
String a,b,c;
…
a = b; // 若是用「值傳遞」,將產生一次 *this 拷貝
a = b= c; // 若是用「值傳遞」,將產生兩次 *this 拷貝
對於相加函數,應當用「值傳遞」的方式返回String對象。若是改用「引用傳遞」,那麼函數返回值是一個指向局部對象temp的「引用」。因爲temp是在棧上申請的變量,函數執行完畢後被銷燬,將致使返回的「引用」無效。指針
//String的相加函數operate + 的實現以下: String operate+(const String &s1, const String &s2) { String temp; delete temp.data; // temp.data是僅含‘\0’的字符串 temp.data = new char[strlen(s1.data) + strlen(s2.data) +1]; strcpy(temp.data, s1.data); strcat(temp.data, s2.data); return temp; }
若是函數返回值是一個對象,要考慮return語句的效率。例如
return String(s1 + s2);
這是臨時對象的語法,表示「建立一個臨時對象並返回它」。不要覺得它與「先建立一個局部對象temp並返回它的結果」是等價的,如
String temp(s1 + s2);
return temp;
實質否則,上述代碼將發生三件事。首先,temp對象被建立,同時完成初始化;而後拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最後,temp在函數結束時被銷燬(調用析構函數)。然而「建立一個臨時對象並返回它」的過程是不一樣的,編譯器直接把臨時對象建立並初始化在外部存儲單元中,省去了拷貝和析構的化費,提升了效率。code
7.劃分全局名字空間的好處
例如,假設library1.h定義了一些常量,其中包括:對象
const double lib_version = 1.204;blog
相似的,library2.h也定義了:接口
const int lib_version = 3;內存
很顯然,若是某個程序想同時包含library1.h和library2.h就會有問題。
要這麼作:
namespace sdm { const double book_version = 2.0; class handle { ... }; handle& gethandle(); }
用戶因而能夠經過三種方法來訪問這一名字空間裏的符號:將名字空間中的全部符號所有引入到某一用戶空間;將部分符號引入到某一用戶空間;或經過修飾符顯式地一次性使用某個符號:
void f1() { using namespace sdm; // 使得sdm中的全部符號不用加 // 修飾符就可使用 cout << book_version; // 解釋爲sdm::book_version ... handle h = gethandle(); // handle解釋爲sdm::handle, // gethandle解釋爲sdm::gethandle ... } void f2() { using sdm::book_version; // 使得僅book_version不用加 // 修飾符就可使用 cout << book_version; // 解釋爲 // sdm::book_version ... handle h = gethandle(); // 錯誤! handle和gethandle // 都沒有引入到本空間 ... } void f3() { cout << sdm::book_version; // 使得book_version // 在本語句有效 ... double d = book_version; // 錯誤! book_version // 不在本空間 handle h = gethandle(); // 錯誤! handle和gethandle // 都沒有引入到本空間 ... }
8.將文件間的編譯依賴性降至最低
假設某一天你打開本身的C++程序代碼,而後對某個類的實現作了小小的改動。提醒你,改動的不是接口,而是類的實現,也就是說,只是細節部分。而後你準備從新生成程序,心想,編譯和連接應該只會花幾秒種。畢竟,只是改動了一個類嘛!因而你點擊了一下"Rebuild",或輸入make(或其它相似命令)。然而,等待你的是驚愕,接着是痛苦。由於你發現,整個世界都在被從新編譯、從新連接!
在name.h中定義
class name{};
在person.h中:
#include"name.h" class person{ public: person(const name& _pname); virtual ~Person(); private: name _pname;//實現細節 string namestr() const; };
在其餘文件中:
class man:public person{
...
};
class woman:public person{
...
};
這時候person文件和name.h之間創建了編譯依賴關係,若是name改變了它的實現,或者name依賴的類改變了實現,包含person類的文件以及任何使用了person類的文件就必須從新編譯。這時person,man,和woman的文件都要從新編譯。
爲了實現定義與實現細節分開,咱們能夠這樣定義person:
class name;//提早聲明 class person{ public: person(const name& _pname); virtual ~Person(); private: string namestr() const; };
若是這樣作可行,person的用戶就不須要從新編譯,只惋惜用起來才知道:
int main() { int x;//定義一個int person p(new name);//定義一個person }
當看到x的定義時,編譯器知道必須爲它分配一個int大小的內存。這沒問題,每一個編譯器都知道一個int有多大。然而,當看到p的定義時,編譯器雖然知道必須爲它分配一個person大小的內存,但怎麼知道一個Person對象有多大呢?
對應於上面的代碼,或許能夠這樣作:
int main() { int x; // 定義一個int Person *p; // 定義一個Person指針,編譯器知道是4字節 ... }
下面具體介紹怎麼採用這一技術來實現person接口和實現的分離:
class name;//提早聲明 class man; class woman; class person{ public: person(const name& _pname); virtual ~Person(); man *personimpl1;//指向具體實現類 woman *personimpl2; string namestr() const; }; //在person.cpp中: #include"person.h" #include"man.h" person.cpp::person(const name& _pname) { personimpl1 = new man(_pname); } string person::namestr()const { return personimpl1->name(); }
這樣,person做爲接口與實現徹底分離。編譯時不對name的改變產生依賴