學習程序語言根本大法是一回事,學習如何以某種語言設計並實現高效程序則是另外一回事。css
一組明智選擇並精心設計的classes、functions、templates可以使程序編寫容易、直觀、高效、而且遠離錯誤。git
帶着問題去品讀這本經驗著做:程序員
設計上的討論:
「如何在兩個不一樣的作法中擇一完成某項任務?」數組
即便徹底知道該作什麼,徹底進入正軌可能仍是可能有點棘手。函數
榨出這些細節很重要,本書將帶你趨兇避吉,避免那些未可預期、神祕難解的程序行爲。學習
軟件設計和實現是複雜的差事,被硬件、操做系統、應用程序的約束條件塗上五光十色,因此我能作的最好的就是提供指南,讓你得以創造出更棒的程序。ui
準則天生就帶有例外。這就是爲何每一個條款都有解釋與說明。這些解釋與說明是本書最重要的一部分。惟有了解條款背後的基本原理,你纔可以決定是否將它套用於你所開發的軟件,並奉行其所昭示的獨特約束。spa
本書的最佳用途:操作系統
本書的最佳用途就是完全瞭解 C++ 如何行爲、爲何那樣行爲,以及如何運用其行爲造成優點。設計
下面是每一個C++程序員都應該瞭解的一份小小的 C++ 詞彙。
聲明式
所謂聲明式(declaration)是告訴編譯器某個東西的名稱和類型,可是略去細節。
extern int x; //對象聲明式
std::size_t numDigits(int number); //函數聲明式
class Widget; //類聲明式
template<typename T> //模板聲明式
class GraphNode;
簽名式
每一個函數的聲明揭示其簽名式(signature),也就是參數和返回值類型。一個函數的簽名就等同於該函數的類型。numDigits函數的簽名是std::size_t (int),也就是說「這個函數得到一個int 並返回一個 std::size_t」。
定義式(definition)
定義式的任務是提供編譯器一些聲明式所遺漏的細節。對對象而言,定義式是編譯器爲此對象撥發內存的地點。
對function 或者 function template 而言,定義式提供了代碼本體。對 class 或者 class template 而言,定義式列出它的成員:
int x; //對象定義式
std::size_t numDigits(int number) //函數定義式
{
//返回其參數的數字個數
std::size_t digitsSoFar = 1;
while ((number /= 10) != 0)
++ digitsSoFar;
return digitsSoFar;
}
class Widget //class 定義式
{
public:
Widget();
~Widget();
...
};
template<typename T> //template 的定義式
class GraphNode{
public:
GraphNode();
~GraphNode();
...
};
初始化(initialization)
初始化(initialization)是「給予對象初值」的過程。對用戶自定義類型的對象而言,初始化由構造函數執行。
所謂default構造函數是一個可被調用而不帶任何實參者。這樣的構造函數
要不沒有參數,要不就是每一個參數都有缺省值:
class A{
public:
A(); //default構造函數
};
class B{
public:
explicit B(int x = 0, bool b = true); //default構造函數
}
class C{
public:
explicit C(int x); //不是default構造函數
}
上述的class B 和 C的構造函數都被聲明爲 explicit,這可阻止它們被用來執行隱式類型轉換(implicit type conversions),但他們仍可被用來進行顯示類型轉換(explicit type conversions):
void doSomething(B bobject); //此函數接受一個類型爲B的對象
B bObj1; //一個類型爲B的對象
doSomething(bObj1); //沒問題,傳遞一個B給
//doSomething函數
B bObj2(28); //沒問題,根據int 28 創建一個B
//(函數的bool參數缺省爲true)
doSomething(28); //錯誤!doSomething 應該接受
//一個B,不是一個 int ,
//int 和 B 之間沒有隱式轉換
doSomething(B(28)); //沒問題,使用 B 構造函數將 int
//顯式轉換爲一個 B 以促成此調用
被聲明成 explicit 的構造函數禁止編譯器執行非預期的類型轉換。除非你有一個好理由容許構造函數被用於隱式類型轉換,不然你應該把他聲明爲 explicit 。
copy構造函數 和 copy assignment操做符
copy構造函數被用來」以同類型對象初始化自我對象」
copy assignment 操做符被用來「從另外一個同類型對象中拷貝其值到自我對象」:
class Widget{
public:
Widget(); //default構造函數
Widget(const Widget& rhs); //copy構造函數
Widget& operator=(const Widget& rhs); //copy assignment操做符
...
};
Widget w1; //調用default構造函數
Widget w2(w1); //調用copy構造函數
w1 = w2; //調用 copy assignment操做符
當你看到賦值符號時請當心,由於「=」語法也可用來調用copy構造函數:
Widget w3 = w2; //調用copy構造函數
如何區別是」copy構造」仍是」copy賦值」:
若是一個新對象被定義,必定會有一個構造函數被調用,不可能調用賦值操做。若是沒有新對象被定義(例如前面的 「w1 = w2」語句),就不會有構造函數被調用,那麼固然就是賦值操做被調用。
copy構造函數是一個尤爲重要的函數,由於他定義了一個對象如何 pass-by-value(以值傳遞)。
舉個例子:
bool hasAcceptableQuality(Widget w);
...
Widget aWidget;
if (hasAcceptableQuality(aWidget)) ...
參數w是以 by value 方式傳遞給 hasAcceptableQuality,因此在上述調用中 aWidget被複制到 w 體內。這個複製動做由 Widget 的copy構造函數完成。pass-by-value 意味「調用 copy 構造函數」。以 by value 傳遞用戶自定義類型一般是個壞主意, Pass-by-Reference-to-const每每是比較好的選擇。
未定義行爲(Undefined behavior)[不明確行爲]
int* p = 0; //p是一個null指針
std::cout << *p; //對一個null指針取值會致使不明確的行爲
//null指針可讀不可寫
char name[] = "Darla"; //name 是個數組,大小爲6(別忘記最尾端的null)
char c = name[10]; //只涉及一個無效的數組索引