4.1:謹慎定義類型轉換函數
<1>容易的方法是利用一個最新的編譯器特性:explicit關鍵字
<2>C++編譯器把">>"作為一個符號來解釋,在兩個">"間沒有空格,語句會產生語法錯誤。
<3>隱式類型轉換函數
示例代碼如下:
1 #include<iostream> 2 using namespace std; 3 4 template<class T> 5 class Array 6 { 7 public: 8 class ArraySize 9 { 10 public: 11 ArraySize(int numElements):theSize(numElements) 12 { 13 cout<<"flag ArraySize(int numElements)"<<endl; 14 } 15 int size() const 16 { 17 cout<<"flag size()"<<endl; 18 return theSize; 19 } 20 private: 21 int theSize; 22 }; 23 24 Array(int lowBound,int highBound) 25 {}; 26 27 Array(ArraySize arsize) 28 { 29 cout<<"flag Array"<<endl; 30 }; 31 }; 32 void main() 33 { 34 Array<int> a(10); 35 } 36 37 /* 38 flag ArraySize(int numElements) 39 flag Array 40 */
4.2:自增,自減操作符前綴形式和后綴形式
<1>重載函數間的區別決定于它們的參數類型上的差異,但是不論increament或者decrement的前綴還是后綴都只有一個參數。
為了解決這個語言問題,C++規定后綴形式有一個int類型參數,當函數被調用時,編譯器傳遞一個0作為int參數的值給該函數。
<2>前綴自增:增加然后取回;后綴自增:取回然后增加
<3>前綴形式
示例代碼如下:
1 UPInt & UPInt::operator++() 2 { 3 *this += 1; //增加 4 return *this; //取回值 5 } 6 const UPInt UPInt::operator++(int) 7 { 8 UPInt oldValue = *this; //保留原值 9 ++(*this); //增加 10 return oldValue; //返回原值 11 }
<4>后綴操作符函數沒有使用它的參數。這個參數僅僅只是為了區別前綴函數調用。
<5>如果在函數內部沒有使用參數,許多編譯器會顯示警告信息。為了避免這些信息,最常用的方式就是省略掉參數名稱
<6>很明顯后綴函數必須返回一個對象,但是為什么是const對象呢?假設不是const:
假設不是 const 對象,下面的代碼就是正確的:
這組代碼與下面的代碼相同:?
i.operator++(0).operator++(0); ?
很明顯, 第一個調用的operator++函數返回的對象調用了第二個operator++函數。 有兩個理由導致我們應該厭惡上述這種做法:
第一,是與內置類型行為不一致。當設計一個類遇到問題時,一個好的準則是使該類的行為與int 類型一致。而int 類型不允許連續進行兩次后綴 increment:?
int i;?
i++++; // 錯誤!?
第二個原因,是使用兩次后綴 increment 所產生的結果與調用者期望的不一致。
如上所示,第二次調用operator++改變的值是第一次調用返回對象的值,而不是原始對象的值。因此如果:
i++++; 是合法的,i 將僅僅增加了一次。這與人的直覺相違背,使人迷惑(對于int類型和 UPInt 都是一樣),所以最好禁止這么做。
C++禁止int 類型這么做,同時你也必須禁止你自己寫的類有這樣的行為。
最容易的方法是讓后綴 increment 返回 const 對象。當編譯器遇到這樣的代碼: ?
i++++; // same as ?
i.operator++(0).operator++(0);
它發現從第一個 operator++函數返回的 const 對象又調用 operator++函數,然而這個函數是一個 non-const 成員函數, 所以 const 對象不能調用這個函數。
如果你原來想過讓一個函數返回 const 對象沒有任何意義,現在你就知道有時還是有用的,后綴 increment和 decrement 就是例子。 (更多的例子參見 Effective C++ 條款 21)?
如果你很關心效率問題, 當你第一次看到后綴 increment函數時,你可能覺得有些問題。
這個函數必須建立一個臨時對象以做為它的返回值, (參見條款M19) ,上述實現代碼建立了一個顯式的臨時對象(oldValue) ,這個臨時對象必須被構造并在最后被析構。前綴 increment 函數沒有這樣的臨時對象。
由此得出一個令人驚訝的結論,如果僅為了提高代碼效率,UPInt 的調用者應該盡量使用前綴 increment,少用后綴 increment,除非確實需要使用后綴 increment。
讓我們明確一下,當處理用戶定義的類型時,盡可能地使用前綴 increment,因為它的效率較高。
我們再觀察一下后綴與前綴 increment 操作符。它們除了返回值不同外,所完成的功能是一樣的,即值加一。
簡而言之,它們被認為功能一樣。那么你如何確保后綴increment和前綴increment的行為一致呢?
當不同的程序員去維護和升級代碼時,有什么能保證它們不會產生差異?除非你遵守上述代碼里的原則,這才能得到確保。
這個原則是后綴 increment和 decrement 應該根據它們的前綴形式來實現。你僅僅需要維護前綴版本,因為后綴形式自動與前綴形式的行為一致。
正如你所看到的,掌握前綴和后綴 increment和decrement 是容易的。
一旦了解了他們正確的返回值類型以及后綴操作符應該以前綴操作符為基礎來實現的規則,就足夠了。
4.3:不要重載“&&”,“||”,或“,”
<1>char *p; if((p!=0)&&(strlen(p)>10))......
注意這個if條件的寫法順序
<2>運算符重載要求:
4.4:理解各種不同含義的new和delete
<1>new operator 稱為new操作符與operator new稱為new操作
string *ps = new string("Memory Management");
使用的是new operator ,即就是new操作符,這種用法是不可改變的,功能總是相同的。
完成兩部分任務:第一,分配足夠的內存以便容納所需類型的對象;第二,它調用構造函數初始化內存中的對象。
而我們所能改變的是如何為對象分配內存。
new操作符調用一個函數來完成必需的內存分配,你能夠重寫或重載這個函數來改變它的行為。
new操作符為分配內存所調用函數的名字是 operator new。
原型如下:
void * operator new(size_t size);
調用形式與其他函數一樣:
void *rawMemory=operator new(sizeof(string));
placement new旨在完成一種情況,那就是假如我們已經申請到了內存,想要在該內存上構建對象,就選擇placement new
三種情況代碼如下:
1 #include<iostream> 2 using namespace std; 3 4 class Test 5 { 6 int a; 7 public: 8 Test(int data = 10):a(data) 9 { 10 cout<<"Construction :"<<this<<endl; 11 } 12 ~Test() 13 { 14 cout<<"Destroy :"<<this<<endl; 15 }; 16 }; 17 void main() 18 { 19 //new操作符(new operator) 20 Test *pa = new Test[2]; //完成兩部分任務 21 delete []pa; 22 pa = NULL; 23 24 //new函數(operator new) 25 void *pb = operator new(sizeof(Test)); //僅僅只是分配一塊內存,類似于malloc 26 operator delete(pb); 27 pb = NULL; 28 29 //placement new 30 void *suffer = malloc(sizeof(Test)); 31 Test *pc = new(suffer) Test(100); //在已經申請的內存上建立自己的對象 32 pc->~Test(); 33 free(suffer); 34 suffer = NULL; 35 }
總結:
如果想在堆上創建一個對象,應該用new操作符,它分配內存,同時又為對象調用構造函數。
如果僅僅想分配內存,就用operator new函數,它不會調用構造函數
如果你想定制自己的在堆對象被建立時的內存分配過程,應該重載寫你自己的operator new函數,new操作符會調用你定制的operator new
如果想在一塊已經分配好的內存里建立一個對象,使用placement new
<2>delete操作符與operator delete的關系與new操作符與operator new的關系一樣
delete p;
導致編譯器生成類似的代碼:
p->~Class();
operator delete(p);
?