前言
最近在看侯捷的一套課程《C++面向對象開發》,剛看完第一節introduction之后就被瘋狂圈粉。感覺侯捷所提及所重視的部分也正是我一知半解的知識盲區,我之前也寫過一些C++面向對象的程序,不過正如侯捷所說,我還僅僅停留於Object-based層面,寫程序時總是在想如何封裝好一個類,而不是Object-oriented強調類與類之間關系的設計。
這門課程分為兩部分,第一部分講Object-based,第二部分講Object-oriented;第一部分又分為兩部分:帶指針的類的封裝和不帶指針類的封裝。
本文將以模板庫中的complx復數類的部分內容為核心,在分析源代碼的同時,講解一些良好的代碼風格和編程習慣,比如inline內聯函數的使用、friend友元函數的使用、函數參數及返回值何時pass by value何時pass by reference等等。
部分代碼
complex.h
1 #ifndef __COMPLEX__ 2 #define __COMPLEX__ 3 4 class complex 5 { 6 public: 7 complex(double r = 0, double i = 0) 8 : re (r), im (i) 9 { } 10 complex& operator += (const complex&); 11 double real () const { return re; } 12 double imag () const { return im; } 13 private: 14 double re, im; 15 16 friend complex& __doapl (complex*, const complex&); 17 }; 18 19 #endif
complex.cpp
1 #include "complex.h" 2 #include <iostream> 3 4 using namespace std; 5 6 inline complex& __doapl(complex* ths, const complex& r) 7 { 8 ths->re += r.re; 9 ths->im += r.im; 10 return *ths; 11 } 12 13 inline complex& complex::operator += (const complex& r) 14 { 15 return __doapl (this, r); 16 } 17 18 inline double imag (const complex& x) 19 { 20 return x.imag (); 21 } 22 23 inline double real (const complex& x) 24 { 25 return x.real (); 26 } 27 28 inline complex operator + (const complex& x, const complex& y) 29 { 30 return complex (real (x) + real (y), imag (x) + imag (y)); 31 } 32 33 inline complex operator + (const complex& x, double y) 34 { 35 return complex (real (x) + y, imag (x)); 36 } 37 38 inline complex operator + (double x, const complex& y) 39 { 40 return complex (x + real (y), imag (y)); 41 } 42 43 ostream& operator << (ostream& os, const complex& x) 44 { 45 return os << ' (' << real (x) << "," << imag (x) << ')'; 46 }
源碼解析
一、complex.h
1.1 initialization list
//程序1.1 complex(double r = 0, double i = 0) : re (r), im (i) { }
構造函數參數缺省,比較常規。
值得注意的是,變量的初始化盡量放在初始化列表中(initialization list)。當然,完全可以在構造函數的函數體中賦值進行初始化。不過,侯捷指出,一個對象在產生過程中分為初始化和成功產生兩部分,initialization list相當於在初始化過程中對變量賦值,而在函數體中賦值則是放棄了initialization list初始化這一過程,會降低效率。對於“性能榨汁機”的C++語言來講,重視每個細節效率的重要性是毫無疑問的。
1.2參數及返回值傳遞方式
//程序1.2 2 complex& operator += (const complex&);
傳遞參數時,如果能用引用傳遞那么一定不要用值傳遞,因為值傳遞的過程中變量需要copy一份樣本傳入函數中,當參數很多或參數類型復雜時,會導致效率變慢。
其次,如果函數不會改變參數的值,一定要加const限定,在初學時養成良好的變成習慣尤為重要。
關於函數的返回值,同樣是最好按引用傳遞,當然,有些情況無法按引用傳遞,這點將在2.3講解。
其實,參數列表中還隱藏一個this,這點將在2.2講解。
1.3友元函數
//程序1.3 friend complex& __doapl (complex*, const complex&);
我們可以看到,在complex.h文件的末尾定義了一個友元函數,友元函數打破了類的封裝,它不是類的成員函數,卻可以使用點操作符來獲取類的private變量。當然,非友元函數也可以通過get函數來獲取,不過速度會慢一些。
二、complex.cpp
2.1 友元函數及內聯函數
//程序2.1 inline complex& __doapl(complex* ths, const complex& r) { ths->re += r.re; ths->im += r.im; return *ths; }
我們首先來分析一下這個友元函數,這里有兩點值得探討:
第一這個函數將r的實部和虛部加到ths上,r在函數體中值沒用發生改變,所以使用const限定。
第二這個函數被設計成inline內聯函數,我們都知道,內聯函數是把代碼塊直接復制到函數需要調用的地方,通過省略函數調用這一過程來提高效率,那么我們為什么不將所有函數都設計成內聯函數呢?其實我們的inline聲明只是對編譯器的一個建議,對於過於復雜的函數來講,及時我們聲明了inline,編譯器也會調用執行。所以,對於一些“小巧”的函數,我們盡量設計為內聯函數。
2.2 隱藏的“this”
//程序2.2 inline complex& complex::operator += (const complex& r) { return __doapl (this, r); }
操作符重載作為C++的特點之一,有令別的語言羡慕之處,當然也有些難以理解。
實際上,這個函數的參數還有一個隱藏的this,這個this就是函數調用者。
2.3 不能為reference的返回值
//程序2.3 inline complex operator + (const complex& x, const complex& y) { return complex (real (x) + real (y), imag (x) + imag (y)); } inline complex operator + (const complex& x, double y) { return complex (real (x) + y, imag (x)); } inline complex operator + (double x, const complex& y) { return complex (x + real (y), imag (y)); }
注意,這里函數的返回值不能返回reference,這其實是使用臨時對象(typename ()),在函數體內定義變量,然后把這個變量的引用傳遞出去,函數結束后變量本體死亡,傳出去的引用既沒有意義了。
2.4 非成員函數的操作符重載
//程序2.4 ostream& operator << (ostream& os, const complex& x) { return os << ' (' << real (x) << "," << imag (x) << ')'; }
下面講一下為什么有的操作符重載函數定義成非成員函數?
我們知道,操作符重載只作用在左邊的操作數上,試想一下,如果把“<<”定義為成員函數,那每次調用豈不是要這樣c1 << cout
完整代碼
以上就是我在學習過程中特別注意的地方,下面給出complex類完整代碼,只不過多了幾種操作運算,大體思路完全一致。
complex.h
1 #ifndef __MYCOMPLEX__ 2 #define __MYCOMPLEX__ 3 4 class complex; 5 complex& 6 __doapl (complex* ths, const complex& r); 7 complex& 8 __doami (complex* ths, const complex& r); 9 complex& 10 __doaml (complex* ths, const complex& r); 11 12 13 class complex 14 { 15 public: 16 complex (double r = 0, double i = 0): re (r), im (i) { } 17 complex& operator += (const complex&); 18 complex& operator -= (const complex&); 19 complex& operator *= (const complex&); 20 complex& operator /= (const complex&); 21 double real () const { return re; } 22 double imag () const { return im; } 23 private: 24 double re, im; 25 26 friend complex& __doapl (complex *, const complex&); 27 friend complex& __doami (complex *, const complex&); 28 friend complex& __doaml (complex *, const complex&); 29 }; 30 31 32 inline complex& 33 __doapl (complex* ths, const complex& r) 34 { 35 ths->re += r.re; 36 ths->im += r.im; 37 return *ths; 38 } 39 40 inline complex& 41 complex::operator += (const complex& r) 42 { 43 return __doapl (this, r); 44 } 45 46 inline complex& 47 __doami (complex* ths, const complex& r) 48 { 49 ths->re -= r.re; 50 ths->im -= r.im; 51 return *ths; 52 } 53 54 inline complex& 55 complex::operator -= (const complex& r) 56 { 57 return __doami (this, r); 58 } 59 60 inline complex& 61 __doaml (complex* ths, const complex& r) 62 { 63 double f = ths->re * r.re - ths->im * r.im; 64 ths->im = ths->re * r.im + ths->im * r.re; 65 ths->re = f; 66 return *ths; 67 } 68 69 inline complex& 70 complex::operator *= (const complex& r) 71 { 72 return __doaml (this, r); 73 } 74 75 inline double 76 imag (const complex& x) 77 { 78 return x.imag (); 79 } 80 81 inline double 82 real (const complex& x) 83 { 84 return x.real (); 85 } 86 87 inline complex 88 operator + (const complex& x, const complex& y) 89 { 90 return complex (real (x) + real (y), imag (x) + imag (y)); 91 } 92 93 inline complex 94 operator + (const complex& x, double y) 95 { 96 return complex (real (x) + y, imag (x)); 97 } 98 99 inline complex 100 operator + (double x, const complex& y) 101 { 102 return complex (x + real (y), imag (y)); 103 } 104 105 inline complex 106 operator - (const complex& x, const complex& y) 107 { 108 return complex (real (x) - real (y), imag (x) - imag (y)); 109 } 110 111 inline complex 112 operator - (const complex& x, double y) 113 { 114 return complex (real (x) - y, imag (x)); 115 } 116 117 inline complex 118 operator - (double x, const complex& y) 119 { 120 return complex (x - real (y), - imag (y)); 121 } 122 123 inline complex 124 operator * (const complex& x, const complex& y) 125 { 126 return complex (real (x) * real (y) - imag (x) * imag (y), 127 real (x) * imag (y) + imag (x) * real (y)); 128 } 129 130 inline complex 131 operator * (const complex& x, double y) 132 { 133 return complex (real (x) * y, imag (x) * y); 134 } 135 136 inline complex 137 operator * (double x, const complex& y) 138 { 139 return complex (x * real (y), x * imag (y)); 140 } 141 142 complex 143 operator / (const complex& x, double y) 144 { 145 return complex (real (x) / y, imag (x) / y); 146 } 147 148 inline complex 149 operator + (const complex& x) 150 { 151 return x; 152 } 153 154 inline complex 155 operator - (const complex& x) 156 { 157 return complex (-real (x), -imag (x)); 158 } 159 160 inline bool 161 operator == (const complex& x, const complex& y) 162 { 163 return real (x) == real (y) && imag (x) == imag (y); 164 } 165 166 inline bool 167 operator == (const complex& x, double y) 168 { 169 return real (x) == y && imag (x) == 0; 170 } 171 172 inline bool 173 operator == (double x, const complex& y) 174 { 175 return x == real (y) && imag (y) == 0; 176 } 177 178 inline bool 179 operator != (const complex& x, const complex& y) 180 { 181 return real (x) != real (y) || imag (x) != imag (y); 182 } 183 184 inline bool 185 operator != (const complex& x, double y) 186 { 187 return real (x) != y || imag (x) != 0; 188 } 189 190 inline bool 191 operator != (double x, const complex& y) 192 { 193 return x != real (y) || imag (y) != 0; 194 } 195 196 #include <cmath> 197 198 inline complex 199 polar (double r, double t) 200 { 201 return complex (r * cos (t), r * sin (t)); 202 } 203 204 inline complex 205 conj (const complex& x) 206 { 207 return complex (real (x), -imag (x)); 208 } 209 210 inline double 211 norm (const complex& x) 212 { 213 return real (x) * real (x) + imag (x) * imag (x); 214 } 215 216 ostream& 217 operator << (ostream& os, const complex& x) 218 { 219 return os << '(' << real (x) << ',' << imag (x) << ')'; 220 } 221 222 #endif //__MYCOMPLEX__
complex_test.cpp
1 #include <iostream> 2 #include "complex.h" 3 4 using namespace std; 5 6 int main() 7 { 8 complex c1(2, 1); 9 complex c2(4, 0); 10 11 cout << c1 << endl; 12 cout << c2 << endl; 13 14 cout << c1+c2 << endl; 15 cout << c1-c2 << endl; 16 cout << c1*c2 << endl; 17 cout << c1 / 2 << endl; 18 19 cout << conj(c1) << endl; 20 cout << norm(c1) << endl; 21 cout << polar(10,4) << endl; 22 23 cout << (c1 += c2) << endl; 24 25 cout << (c1 == c2) << endl; 26 cout << (c1 != c2) << endl; 27 cout << +c2 << endl; 28 cout << -c2 << endl; 29 30 cout << (c2 - 2) << endl; 31 cout << (5 + c2) << endl; 32 33 return 0; 34 }
總結
作為初學者,一定要養成良好的編程習慣,正如侯捷所說:“一出手就是大家風范”。