18.爭取使類的接口完整并且最小。
類的用戶接口是指使用這個類的程序員所能訪問得到的接口,典型的接口里只有函數存在,封裝好類的數據成員。
完整是指接口中包含所有 合理的操作的函數。最小是指函數盡可能少且功能不重復。
接口中的函數要少的原因:接口中函數越多,越讓其他人難以理解,函數多了會讓人混淆。函數多了難以維護,更難維護與升級。長的類定義會導致長的頭文件,浪費大量編譯時間。
19.分清成員函數,非成員函數和友元函數
成員函數可以是虛函數,即可以實現動態綁定,而非成員函數不行。
關于類一些操作符重載的思考?
如 operator+ 進行加法的重載,假設在有理數 Rational 類中有成員函數 +法的操作,則對于一下三個運算:
~~~
result = R1 + R2;
result = R1 + 2;
result = 2 + R1;
~~~
先補充一個前提,Rational的構造函數的聲明為Rational(int ?numerator = 0,int denominator = 1)。則對于第一行的運算,就是為 R1.operator+(R2),即調用R1中的成員函數,而對于第二行的運算也是正確的,這里將2隱式轉換稱了Rational類(編譯器會對每個函數的每個參數執行隱式類型轉換),在沒有聲明explicit的情況下,即這行編譯器是如此解釋的 R1.operator+( Rational(2))。而對于第三行的運算是出錯的,因為編譯器是如此理解這行的操作 2.operator+(R1),在對于整數2找不到對應的operator+函數后,這里并不會隱式將2轉換為有理數。編譯器又去尋找非成員的全局的operator+( 2,R1),結果也沒有找到,最后搜索失敗。
而當將operator+ 作為友元函數時,對于第三行,編譯器就如此理解 operator+( 2,R1),2 作為函數的參數,編譯器也會嘗試隱式轉換為有理數類。
但是盡量不要使用友元函數,而使用全局函數,最好能夠調用類中的共有接口來實現操作符運算。因為使用友元函數會帶來很多麻煩,簡單點來說,類是為了封裝的,友元會降低類的封裝性。
20.避免public接口出現數據成員。
簡單來說,public中只有函數,對數據成員的讀寫只通過函數來實現,這樣,可以通過設置函數來實現數據成員的 不可讀寫,只讀,只寫,可讀寫。
復雜一點,實現功能分離 functional abstraction ,如果使用函數來實現對數據成員的訪問,當更改對數據成員的設定時,只要在這些函數中進行一些修改,添加一些代碼,就可以實現效果,且對用戶隱藏這些細節。
21.盡可能使用const。
const除了聲明常量外,在類中常用于使實參為常量即聲明參數為const,以及使函數不改變類內成員的值即聲明函數為const,以及讓函數的返回值為常量。
讓函數的返回值為常量,減少用戶出錯的幾率。如對operator+的重載,如果返回值不是一個常量,對于這個式子 (a+b) = c,就容易阻止這種無聊的錯誤。
聲明函數為const,其實是指函數重載,為const對象調用,而若一個函數只有const的版本,就會發生將普通對象轉換為 const對象,而對于const函數中對const的對象的操作,不能改變const對象中一般數據成員的值 即const函數不能改變對象的成員的值。 但是const函數中對數據成員不進行改變又不是絕對的,如果有指針的數據成員,在函數中不改變指針,但可以去改變指針指向的內容的值, 這樣的函數也可以通過編譯器的檢測,但這和我們的為const的定義相違背。另一種conceptual constness 的觀點認為const成員函數可以修改它所在對象的一些數據,在用戶不發覺的情況下,使用關鍵字mutable。或則可以強制一點,使用const_cast聲明一個局部的this指針,然后強制修改這個指針指向的內容的值,這樣是可以通過的。
22.盡量傳引用,不要傳值。
對于傳值,函數的形參是通過實參的拷貝來初始化,函數的調用者是函數返回值的拷貝。通過值來傳遞一個對象,具體含義是由這個對象的類的拷貝構造函數定義的。而這樣既浪費空間,又浪費時間。
使用傳引用,沒有新的對象創建,傳遞的一直都是引用,則沒有構造函數和析構函數的調用。
傳引用的另一個優點是 避免了 切割問題 slicing problem。 當一個派生類的對象被當作基類的對象進行傳遞時,派生類對象會由 基類的構造函數使用 派生類對象轉換得到的基類對象 作為參數 賦值一個新的 完整的基類對象,其派生類中所具有的行為特性會被切割掉,只是一個簡單的基類對象。而如果使用了傳引用,傳給函數中使用的依然是這個對象本身,而這個對象有多態,有派生類的功能。
對于傳引用。一般都是使用指針來實現的,而對于一些較小的對象,如int,傳值其實會比傳引用更加高效。
23.必須返回一個對象時,不要試圖返回一個引用。
傳引用的一個嚴重的錯誤,傳遞一個并不存在的對象的引用。舉例,對于操作符重載中的 operator= , 其返回值為 const T,返回對象的原因:
如果返回為引用,則這個別名的原名是誰,在哪里?實現對于+ ,其返回的對象不是兩個參數,而是在函數中新建的對象,而這個對象不能在棧中,因為其在函數結束后就會被釋放,而這個別名指向一塊已經釋放的內存這是不正確的。這個對象也不能在堆中,對象不能是動態建立的,因為在調用+后,并沒有儲存這個別名并釋放這個別名所在的內存,這是內存泄漏。所以,不能返回一個引用,所以必須返回一個對象,即使這個對象返回到調用處要 先在函數中進行一次構造和析構,再在調用出進行一次構造,在使用后又要調用一次析構函數,即使花費不低,但只能使用返回對象。