來源:陳太漢
在看《Effective C++》這本書的過程中,我無數次的發出感嘆,寫得太好了,句句一針見血,直接說到點上。所以決定把這本書的內容加上自己的理解寫成5篇部落格,我覺得不管你是否理解這些條款,都值得你先記下來。下面的索引對應的是書中的章節。
18:努力讓介面完美且最小化
19:區分member functions,non-member functions和friend functions三者
20:避免將data member放在公開介面中
21:儘量使用const
22:儘量使用 pass-by-refernece,少用pass-by-value
18:努力讓介面完美且最小化
為了客戶端的方便呼叫,介面中可能會定義很多方法,而其中可能右很多方法是多餘或是重複的,這樣會導致介面中方法太多,讓使用者迷失在一堆的方法中,而且大型介面不易維護,長長的class定義導致標頭檔案很長,會增大編譯的時間。但是也不必太過吝嗇方法的個數,如果加入一個member function會是class更好用,會是增加一個member function能減少客戶端的錯誤,那都是這些方法都是成為介面一份子的理由。
19:區分member functions,non-member functions和friend functions三者
member function可以是虛擬函式而non-member function不可以,如果一個函式必須是動態繫結的那麼他就必須是虛擬函式,就必須是memberfunction,虛擬函式能實現動態繫結是因為子類可以根據自己的需要重寫父類的虛方法實現動態繫結,而non-member function不可能被重寫。Friend function是獨立於class的,他只是可以訪問class的私有成員,如果一個方法不需要訪問一個class的私有成員,就不應該讓這個方法稱為這個類的friend function。
1 2 3 4 5 6 7 8 9 10 |
class Rational { public: Rational(int numerator=0,int denominator=1); int numerator()const; int denominator() const; const Rational operator*(const Rational& rhs)const; private: ... }; |
上面這個類表示一個分數,分數的加減乘除的方法都沒有提供,那我們該以什麼樣的方式實現這些操作呢,是member function還是non-member function還是friend function呢?
第一直覺就是這些操作是屬於Rational的應該是member function,那麼我們就新增一個關於乘法的public member function,就是下面這個樣子:
const Rational operator*(const Rational& rhs)const;
簡單的介紹為什麼是這個樣子。先解釋3個const,第一個const表示方法的返回為const,就是禁止我們對一個乘法賦值,如禁止a*b=3;第二個const表示在這個方法中不能修改rhs中任何成員的值,第三個const表示這個方法是const方法,在這個方法中不能修改呼叫這個方法的物件的資料成員。Const還有其他很多作用將會在下一個條款中介紹。
返回值為什麼是by value?首先我們必須用一個變數來存乘法的結果值,我們不能在方法中構造一個區域性變數,然後返回他的引用,因為這個方法執行完後,區域性變數會被自動回收。
引數為什麼為引用型別?一句話儘量用by reference代替by value,條款22專門講述這個問題。這個返回值返回使用by value是沒有其他辦法了,你必須用一個變數來存放結果值。有了這個方法我們就可以進行乘法操作了。
Rational oneHalf(1,2),twoFive(2,5);
Rational result=oneHalf*twoFive;//沒有問題
Result=oneHalf*3;//沒有問題,在型別不匹配的時候編譯器會一直尋找隱式型別轉換的方法,直到找不到報錯,由於建構函式的兩個引數都有預設值,所以可以發生隱式型別轉換,3相當於Rational(3,1),於是不會出現任何問題。
乘法的交換律告訴我們:a*b=b*a;於是我想oneHalf*3可以寫成3*oneHalf,但是對不起不行,
3*oneHalf相當於3.operator(oneHalf),在這個3是操作物件,不會發生任何型別轉換,而oneHalf是引數,於是編譯器尋找將Rational轉換成int(假設3為int型別)的方法,當然是沒有啦。為了實現Rational和int型別的任意操作用member function是不可能啦,於是用non-member function,於是寫下乘法的方法如下:
1 2 3 4 |
const Rational operator*(const Rational& lhs,const Rational& rhs) { return Rational(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs. denominator()); } |
Non-member function當然不需要用cosnt修飾,將乘法結果存在一個匿名變數中,如果編譯有優化的話,這個結果將直接存在接收這個方法的變數中。有了這個方法,3*oneHalf就沒有任何問題了,現在3被隱式轉換成Rational(3,1)了,不會出現任何問題,如果你不想出現隱式型別轉換,就在建構函式的前面加上explicit。最後要考慮的是是否需要將這個方法稱為Rational的friend function,當然是不需要,因為讓他稱為friend function沒有任何幫助,多一個沒有任何幫助的朋友有這個必要嗎?
20:避免將data member放在公開介面中
將data member設為private,然後用member function實現讀寫操作,對於用過面嚮物件語言的朋友都知道,就不廢話了。如果這些方法只是返回data member的話,可以讓這些方法稱為inline,就可以節省方法呼叫帶來的效能損失。
21:儘量使用const
關於這一條上面也提到一些。
Const修飾方法的返回值表示不可以直接對這個方法進行賦值;
Const修飾方法表示在這個方法中不能修改data member;
Const修飾參數列示這個引數在這個方法中不能修改;
常量性的不同也可以實現方法的過載,常量物件只能呼叫對應的常量方法。非常量物件可以呼叫常量方法。
22:儘量使用 pass-by-refernece,少用pass-by-value
先看一個類,然後看兩個方法的對比,其他的廢話就多說了,因為C#引用型別預設的是pass-by-reference,而C++任何型別預設的都是pass-by-value。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class DataItem { public: DataItem() { cout<<" constructor DataItem"<<endl; } ~DataItem() { cout<<" ~destructor DataItem"<<endl; } DataItem(const DataItem& item) { cout<<" constructor DataItem"<<endl; value=item.value; text=item.text; //*this=item;//這句的作用等同於上面兩句,但是它會呼叫operator= ,就多了一次方法呼叫 } const DataItem& operator=(const DataItem& item) { cout<<" operator= DataItem"<<endl; text=item.text; value=item.value; return *this; } DataItem* operator&() { return this; } const DataItem* operator&()const { return this; } int GetValue() { return value; } void SetValue(int val) { value=val; } string& GetText() { return *text; } void SetText(string* txt) { text=txt; } private : int value; string* text; }; |
兩個對比方法及測試程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
DataItem getDataItemByValue(DataItem item) { return item; } const DataItem& getDataItemByReference(const DataItem& item) { return item; } void TestDataItem() { DataItem item; cout<<"getDataItemByValue start:"<<endl; getDataItemByValue(item); cout<<"getDataItemByValue end"<<endl; cout<<endl; cout<<"getDataItemByReference start:"<<endl; getDataItemByReference(item); cout<<"getDataItemByReference end"<<endl; } |
結果截圖:
從結果中我們看到pass-by-value多呼叫兩次建構函式,兩次解構函式,還有物件的資料成員的構造和析構,損失的確是很慘重。