在看《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。
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,於是寫下乘法的方法如下:
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。
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;
};
兩個對比方法及測試代碼:
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多調用兩次構造函數,兩次析構函數,還有對象的數據成員的構造和析構,損失的確是很慘重。