1.盡量用類的非成員函數以及友元函數替換類的成員函數
例如一個類來模擬人People
1 class People{ 2 public: 3 ... 4 void Getup( ); 5 void Washing( ); 6 void eating( ); 7 ... 8 }
其實上面三個動作是早上“起床”、“洗簌”、“吃飯”三個常見的動作,如果現在用一個函數來表示使用成員函數即為
1 class People 2 { 3 ... 4 void morningAction( ) 5 { 6 Getup( ); 7 Washing( ); 8 eating( ); 9 } 10 }
如果寫一個非成員函數即為
1 void moringAction(People& p) 2 { 3 p.Getup( ); 4 p.Washing( ); 5 p.eating( ); 6 }
那么是選擇類的成員函數還是類的非成員函數呢?
面向對象則要求是,將操作數據的函數與數據放在一起。但這不意味着要選擇成員函數。從封裝的角度看,成員函數的moringAction封裝性比非成員函數要低。如果某些東西被封裝,它就不再可見。越多東西被封裝,越少人可以看到它。所以使用非成員函數的類,封裝性較低。而越少人看到它,我們就有越大彈性去變化它,因為我們的改變僅僅直接影響看到改變的那些人事物。因此,越多東西被封裝,改變哪些東西能力越大。
在考慮對象內的數據。越少的代碼可以看到數據(訪問它),越多的數據可以被封裝,而我們也就越能自由改變對象數據。現在如果一個成員函數、非成員函數都能提供相同的機能,我們選擇非成員函數。
在說下面內容之前我們先談談C++中的類型轉換分顯示類型轉換和隱式類型轉換。
2.顯示類型轉換
C++有顯示類型轉換操作符分別為:static_cast,const_cast,dynamic_cast和reinterpret_cast
2.1static_cast
轉換功能與C風格類型轉換一樣,含義也一樣。通過使用static_cast可以使沒有繼承關系的類型進行轉換。但是要注意不能將內置類型轉化為自定義類型,或者將自定義類型轉化為內置類型,並且不能將cosnt類型去掉,但能將non-cosnt類型轉換為const類型。例如:
char a='a';
int b=static_cast<int>(a);
兩個類型之間的轉換,其實也是要自己實現支持,原理和內置類型之間轉換相似。
1 #include <iostream> 2 class Car; 3 4 class People 5 { 6 public: 7 People(int a,int h) 8 :age(a),height(h) 9 {} 10 11 inline int getAge() const 12 { 13 return age; 14 } 15 16 inline int getHeight() const 17 { 18 return height; 19 } 20 21 People & operator=(const Car& c); 22 private: 23 int age; 24 int height; 25 }; 26 27 class Car 28 { 29 public: 30 Car(double c, double w) 31 :cost(c),weight(w) 32 {} 33 34 inline double getCost() const 35 { 36 return cost; 37 } 38 39 inline double getWeight() const 40 { 41 return weight; 42 } 43 44 Car & operator=(const People& c); 45 private: 46 double cost; 47 double weight; 48 }; 49 50 People & People::operator=(const Car& c) 51 { 52 age = static_cast<int>(c.getCost()); 53 height = static_cast<int>(c.getWeight()); 54 return *this; 55 } 56 57 Car & Car::operator=(const People& c) 58 { 59 cost = static_cast<double>(c.getAge()); 60 weight = static_cast<double>(c.getHeight()); 61 return *this; 62 } 63 64 int main(int argc,char * argv[]) 65 { 66 Car c(1000.87,287.65); 67 People p(20,66); 68 People p2(0,0); 69 Car c2(0.00,0.00); 70 p2=c; 71 c2=p; 72 std::cout<< "car'info: cost is " << c2.getCost() << ". weight is " << c2.getWeight() <<std::endl; 73 std::cout<< "people'info: age is " << p2.getAge() <<". height is " << p2.getHeight() <<std::endl; 74 return 0; 75 }
運行結果為
car'info: cost is 20. weight is 66 people'info: age is 1000. height is 287
2.2const_cast
主要用來去掉const和volatileness屬性,大多數情況下是用來去掉const限制。
2.3dynamic_cast
它用於安全地沿着繼承關系向下進行類型轉換。一般使用dynamic_cast把指向基類指針或者引用轉換成其派生類的指針或者引用,並且當轉換失敗時候,會返回空指針。
2.4reinterprit_cast
該轉換最普通用途就是在函數指針類型之間進行轉換。
1 typedef void (*fun) ( ) //一個指向空函數的指針 2 fun funArray[10]; //含有10個函數指針的數據。 3 int function( ); //一個返回值為int類型函數 4 funArray[0] = &function( ) //錯誤!類型不匹配 5 funArray[0] = reinterpret_cast<fun>(&function); //ok
3.使用非成員函數可以發生隱式轉換
C++是支持隱式類型轉換的,例如在做運算的時候,或者傳遞參數給函數的時候常常會發生隱式類型轉換。
假設你設計一個class用來表現有理數。其實令類支持隱式類型轉換是一個槽糕的決定。當然在建立數值類型時就是例外。下面定義一個有理數類型:
1 class Rational { 2 public: 3 Rational( int numerator = 0,int denominator =1 ); 4 int numerator( ) const; 5 int denominator ( ) const ; 6 private: 7 ... 8 }
有理數類型想當然支持算數運算,但是不確定是否聲明為成員函數或非成員函數或者是友元函數來實現它們。
首先是考慮成員函數寫法:
1 class Rational { 2 public: 3 ... 4 const Rational operator* (const Rational& rhs) const; 5 ... 6 } 7 Rational testOne(1,4); 8 Rational testTwo(1,1); 9 //做算術運算 10 Rational result = testOne * testTwo; 11 //與常量做運算 12 result = testOne * 2; //ok 13 //乘法滿足交換定律 14 result = 2 * testOne //error!!
那么為什么將常量提前就錯誤了呢?這里我們換一種寫法
1 result = testOne.operator*(2); //ok 2 result =2.operator*(oneHalf); //error
這里發生了什么?其實在第一行式子里發生了所謂隱式類型轉換。哪里發生了隱式類型轉換呢?看上面的代碼operator*函數參數是const Rational類型,而2是一個cosnt int類型,。編譯器發現有non-explicit型的單參數類為int類型的構造函數,可以造出Rational類型。所以編譯器那樣做了,發生了隱式類型轉換。所以第一個式子可以正常運行,但是第二個是沒有將Rational類型轉換為int類型的。
設計出上面兩個式子正常運行才算合理的運行。
1 class Rational{ 2 ... 3 }; 4 const Rational operator*(const Rational & lhs, const Rational & rhs) 5 { 6 return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator() ); 7 } 8 Rational testOne(1, 4); 9 Rational result; 10 result = oneFourth *2; 11 result = 2 * oneFourth; 通過 12 }
按上面代碼設計成非成員函數,那么在調用int類型整數的時候會發生隱式類型轉換。
operaotr* 是否應該稱為Rational class的一個友元函數呢?對本例子而言,完全沒有必要。
如果你需要為某個函數所有參數進行類型轉換,那么這個函數必須是個非成員函數。
4.謹慎使用隱式類型轉換
我們對一些類型隱式轉換無能為力,因為它們是語言本身的特性。不過當編寫自定義類時,我們可以選擇是否提供函數讓編譯器進行隱式類型轉換。
有兩種函數允許編譯器進行隱式類型轉換:單參數構造函數與隱式類型轉換運算符。
1 public Name{ 2 public: 3 Name(const string& s); //轉換string到Name 4 5 ... 6 }; 7 8 class Rational { //有理數類 9 public: 10 //轉換從int到有理數類 11 Rational(int numerator=0,int denominatior =1); 12 ... 13 }
C++是支持隱式類型轉換的,例如在做運算的時候,或者傳遞參數給函數的時候常常會發生隱式類型轉換。有兩種函數允許編譯器進行隱式轉換。單參數構造函數和隱式類型轉換運算符。
也許前面說道了一些隱式類型轉換帶來的方便之處,下面說說一些麻煩之處。
1 template<class T> 2 class Array{ 3 Array(int lowBound,int highBound); 4 Array(int size); 5 T& operator[](int index); 6 ... 7 }; 8 bool oerpator==(const Array<int>& lhs,const Array<int>& rhs); 9 Array<int> a(10); 10 Array<int> b(10); 11 ... 12 for(int i=0;i < 10; ++i) 13 if(a == b[i]) { 14 ... } 15 else 16 { 17 ... 18 }
如果這里不小心將數組a的下標忘記寫了,這里編譯器應該報出警告信息,但是其實是沒有的。因為編譯器將a看成Array<int>類型,b為 int,根據上面的經驗,編譯器看到一個non-explicit單參數構造函數其參數類型為int,而且operator需要一個Array<int>類型,那么編譯器就這樣做了,發生了隱式類型轉換。相信如果真的發生這樣,會很麻煩。
怎么樣避免呢?將構造函數聲明為explicit。避免隱式類型轉換。