Boolan第一週筆記(一)複合賦值運算符和算術運算符究竟該重載成成員函數仍是非成員函數

第一週侯捷老師以complex類爲例講解了不含指針的類的寫法。討論羣裏有一位同窗提出了一個問題,大意是:對於侯老師給出的complex類(下面是部分代碼),爲何+=設計爲成員函數就是合理的,+設計爲成員函數就是不合理的。我經過翻書和寫代碼獲得的結論是:對於左數是complex類的對象的狀況,+= 和 +做爲成員函數或非成員函數都不影響對它們的調用,程序均可以正確運行,可是+=設計成成員函數,+設計成非成員函數更合理。緣由是:在爲自定義的類定義運算符時要儘可能模仿內置類型的寫法,這樣作符合使用咱們自定義類型的用戶長期以來的習慣(用戶怎樣使用內置類型的運算符,如今就能夠以一樣的方式使用咱們自定義的運算符)。這一點侯捷老師還有C++ Primer都提到過。ios

本文的討論過程和結論並不保證徹底正確(但代碼確定對),它只是我對這個問題作的探討和嘗試,歡迎有更好想法的朋友與我交流。面試

如下是跟這位同窗的疑問有關的代碼,來自侯捷老師 & Boolan C++工程師微專業。函數

#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__

class complex; complex& __doapl(complex* ths, const complex& r); class complex { public: complex(double r = 0, double i = 0) : re(r), im(i) { } complex& operator += (const complex&); double real() const { return re; } double imag() const { return im; } private: double re, im; friend complex& __doapl(complex *, const complex&); }; inline complex& __doapl(complex* ths, const complex& r) { ths->re += r.re; ths->im += r.im; return *ths; } inline complex& complex::operator += (const complex& r) { return __doapl(this, r); } inline double imag(const complex& x) { return x.imag(); } inline double real(const complex& x) { return x.real(); } 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)); } #endif // !__MYCOMPLEX__

先回答+=的設計。this

首先,+=並非非得寫成成員函數。C++ Primer(中文第五版,第500頁)有提到「複合賦值運算符並不非得是類的成員,不過咱們仍是傾向於把包括符複合賦值運算符在內的全部賦值運算都定義在類的內部「。就是說,把複合賦值運算符定義在類內是推薦使用的作法,可是並不嚴格要求必須這樣作。這一點經過如下的程序能夠獲得證實。spa

1.複合賦值運算符做爲類的成員函數:設計

//complex.h
#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__ #include<iostream>
using namespace std; class complex; complex& __doapl(complex* ths, const complex& r); class complex { public: complex(double r = 0, double i = 0) : re(r), im(i) { } complex& operator += (const complex&); double real() const { return re; } double imag() const { return im; } private: double re, im; friend complex& __doapl(complex *, const complex&); }; inline complex& __doapl(complex* ths, const complex& r) { ths->re += r.re; ths->im += r.im; return *ths; } inline complex& complex::operator += (const complex& r) { cout << "complex& complex::operator += (const complex& r) " << endl; return __doapl(this, r); } #endif   //__MYCOMPLEX__
//complex_test.cpp
#include "complex.h" ostream& operator << (ostream& os, const complex& x) { return os << '(' << x.real() << ',' << x.imag() << ')'; } int main() { complex c1(2, 1); complex c2(4, 0); cout << "c1 + c2 =" << (c1 += c2) << endl; system("pause"); return 0; }

運行結果指針

 

2.符合賦值運算符不是類的成員
code

//complex.h
#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__ #include <iostream>
using namespace std; class complex; complex& __doapl(complex& ths, const complex& r); class complex { public: complex(double r = 0, double i = 0) : re(r), im(i) { } double real() const { return re; } double imag() const { return im; } private: double re, im; friend complex& __doapl(complex&, const complex&); }; inline complex& __doapl(complex& l, const complex& r) { l.re += r.re; l.im += r.im; return l; } inline complex&
operator += (complex& l, const complex& r) { cout << "complex& operator += (complex& l, const complex& r)"; return __doapl(l, r); } #endif   //__MYCOMPLEX__
//complex_test.cpp
#include "complex.h" ostream& operator << (ostream& os, const complex& x) { return os << '(' << x.real() << ',' << x.imag() << ')'; } int main() { complex c1(1,2),c2(2, 1); cout << (c1+=c2) << endl; system("pause"); return 0; }

運行結果對象

觀察運行結果可知,+=是否做爲類的成員函數都不影響對+=的調用。可是+=做爲一種賦值運算符,最好使跟內置的賦值運算符同樣寫成成員函數。
blog

接下來討論對於開頭的第一段代碼,+運算符什麼狀況下須要寫成成員函數,什麼狀況下須要寫成非成員函數。

這段代碼重載了三個+運算符,首先討論下面這個。

inline complex operator + (double x, const complex& y) { return complex (x + real (y), imag (y)); }

C++ Primer(中文第五版 493頁)提到,「當咱們把運算符定義成成員函數時,它的左側運算對象必須是運算符所屬類的一個對象」。這個+的左側是double,所以不能夠寫成complex類的成員函數。另外,double類型是內置類型,沒有定義一個double 和一個複數的+運算符。因此咱們須要本身寫這個+,並把它寫成complex的非成員函數。

下面的兩個+運算符做爲complex類的成員函數或非成員函數,程序均可以運行。

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)); }

開頭第一段代碼就是把這兩個+做爲非成員函數,程序能夠運行,沒有錯誤。下面試試成員函數版本。

//complex.h
#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__ #include <iostream>
using namespace std; class complex { public: complex(double r = 0, double i = 0) : re(r), im(i) { } double real() const { return re; } double imag() const { return im; } complex operator + (const complex&); complex operator + (double); private: double re, im; }; inline complex complex::operator + (const complex& y) { cout << "complex complex::operator + (const complex& y)" << endl; return complex(real() + y.real(), imag() + y.imag()); } inline complex complex::operator + (double y) { cout << "complex complex::operator + (double y)" << endl; return complex(real() + y, imag()); } #endif   //__MYCOMPLEX__
//complex_test.cpp
#include "complex.h" ostream& operator << (ostream& os, const complex& x) { return os << '(' << x.real() << ',' << x.imag() << ')'; } int main() { complex c1(2, 1); complex c2(4, 0); cout << (c1 + c2) << endl; cout << (c1 + 5) << endl; system("pause"); return 0; }

運行結果

把這兩個+做爲成員函數,程序也能夠運行,沒有錯誤。

那爲何+寫成非成員函數更好呢?

C++ Primer(中文第五版 第493頁)在談到把重載運算符做爲成員函數或非成員函數時,提到「具備對稱性的運算符可能轉換任意一端的運算對象,例如算術,相等性,關係和位運算符等,所以它們一般應該是普通的非成員函數」。接下來書中提到了string的 operator+的例子來講明這一點。string 的+重載成了非成員函數正是因爲對稱型運算符可能轉換任意一端的對象。

string s = "world"; string t = s + "!"; string u = "hi" + s;

string 的+是鏈接兩個string對象的,就是說左側和右側的對象都是string。所以不管+是重載成成員函數仍是非成員函數,第二行的表達式 s+"!"都是對的(由於左側是string, 右側的const char *能夠轉換成string)。可是對於 「hi" + s 就不同了,若是string 的+是成員函數, 就會嚴格要求+的左側對象是string,那麼這個表達式在運行時就找不到匹配的運算符。所以,顯然的,+運算符做爲非成員函數比做爲成員函數更靈活,所以更好。

在本週課程的complex類裏,左數是complex類型的兩個+運算符寫成非成員函數比寫成成員函數更好,我推測正是由於對稱性的運算符可能轉換運算對象(也許是有其餘的緣由,可是我目前不知道,之後發現了再修改)。好比當用戶想要計算c1(1, 0)和 c2 (2, 1)的和,但忘記輸入c1的虛部時,就至關於輸入了一個int和一個c2。 若是complex類把兩個complex對象相加寫成成員函數,int能夠轉換爲double, double則能夠轉換成complex,這時c1 + c2就能夠正常計算,就像上一段中"hi"和s相加發生的轉換過程相似。

下面的代碼能夠驗證當complex類把兩個complex對象相加使用的+運算符寫成非成員函數時,+左側若是輸入int, 該int可轉換爲complex。

//complex.h
#ifndef __MYCOMPLEX__ #define __MYCOMPLEX__ #include <iostream>
using namespace std; class complex { public: complex(double r = 0, double i = 0) : re(r), im(i) { } double real() const { return re; } double imag() const { return im; } private: double re, im; }; inline double imag(const complex& x) { return x.imag(); } inline double real(const complex& x) { return x.real(); } inline complex operator + (const complex& x, const complex& y) { cout << "complex operator + (const complex& x, const complex& y)" << endl; return complex(real(x) + real(y), imag(x) + imag(y)); } #endif   //__MYCOMPLEX__
//complex_test.cpp
#include "complex.h" ostream& operator << (ostream& os, const complex& x) { return os << '(' << x.real() << ',' << x.imag() << ')'; } int main() { int i = 1; complex c2(2, 1); cout << i + c2 << endl; system("pause"); return 0; }

運行結果

計算int + complex調用了complex + complex的函數,說明存在從int到complex的隱式轉換。若是把+寫成complex的成員函數,編譯沒法經過,計算 i+ c2(模仿忘記輸入左數的虛部的狀況)的時候會顯示沒有匹配的運算符。

相關文章
相關標籤/搜索