看板 C_and_CPP 關於我們 聯絡資訊
目標: 建立一個分數類別,運作方式跟內建的 + 運算子相同。 設計: 1. 用兩個泛型整數代表分子分母,分母 > 0 2. 運用 const 修飾詞達成語意 eq. (r1+r2) = r3 //Compile Error 3. 能跟整數一起運作 eq. r1 = 1; r1= r2+1; 4. 自訂 operator << 使得分數可以被 ostream 輸出 使用案例: Rational <long long> r0; Rational <long long> r1 = 1; Rational <long long> r2 = r1 + 1; (r1 + r2) = r0; //compile error!! r1+=r2; r1 + = 1; r1 = 1 + r2; cout << r1 << r2; 參考: 都是參考 Scott Meyers 的 Effective C++(3/e) & More Effective C++ 這個 Rational 案例被分散到各個條款,我整理成一個完整的 class Effective C++ (3/e) (E) 條款 3: Use const whenever possible // constraint (r1+r2)=r0 條款24: Declare non-member function when type conversions should apply to all parameters. // enable r1 = 1 + r2; 條款46: Define non-member functions inside templates when type conversions are desired. // Rational<long long> More Effective C++ (ME) 條款20: Facilitate the return value optimization //Implementation detail 條款22: Consider using op= instead of stand-alone op // impl detail 程式碼 ======================= Rational.h ======================= #pragma once #include <ostream> #include <cassert> template <class T> class Rational { //friend 在 template 的涵義是為了要能具現化 //並不只為了存取 private 參考 E46 friend std::ostream& operator<<(std::ostream& os,const Rational& r) { if(r.d_ == 1) os << r.d_; else os << r.n_ << "/" << r.d_; return os; } friend bool operator==(const Rational& lhs,const Rational& rhs) { return (lhs.d_ == rhs.d_) && (lhs.n_==rhs.n_); } //參考 E3 防止 (r1+r2) 被賦值 friend const Rational operator+(const Rational& lhs,const Rational& rhs) { return Rational(lhs)+=rhs; //RVO ME20 } friend const Rational operator*(const Rational& lhs,const Rational& rhs) { return Rational(lhs)*=rhs; } T n_; //numerator 分子 T d_; //denominator 分母 public: Rational(T n = 0,T d = 1) : n_(n),d_(d){ assert(d_>0); reduce(); } //參考 ME22 Rational& operator+=(const Rational& rhs){ T g = gcd(rhs.d_,d_); T l = d_ * (rhs.d_/g); // lcm 最小公倍數 n_ = n_*(l/d_) + rhs.n_*(l/rhs.d_); d_=l; reduce(); return*this; } Rational& operator*=(const Rational& rhs){ d_*=rhs.d_; n_*=rhs.n_; reduce(); return*this; } private: // 最大公因數 T gcd(T m,T n) { return n?gcd(n,m%n):m; } // 約分 void reduce() { T g = gcd(d_,n_); if(g != 1){ d_/=g; n_/=g; } } }; ==================================================== 備註: 1. 這個類別示範了設計重載運算子該注意的事項 2. 加法和乘法的實作部分我就沒特別考慮效率eq. gcd 用遞迴實作 可以試試看如何把 lazy evaluation 實作進去 3. 減法、除法和比較運算子的介面可以依此類推 4. 正負號部分我目前把是用分子主導 (分母恆正),錯誤處理的部分沒實作上去 "Rational.h" 的實作碼 http://codepad.org/MT6qOUa9 -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 220.134.96.34
spider391:Gtest Code: http://nopaste.info/d4523daa70.html 12/30 13:35
yoco315:認真的態度感天動地,只好給推 12/30 19:54
yoco315:另補充現成的boost::rational http://tinyurl.com/29khprb 12/30 19:56
loveme00835:不是很推给預設值的建構子 12/30 20:08
loveme00835:隱式轉換也太多 ~"~ 12/30 20:10
yoco315:這個預設建構子是必要的耶..而且他不是只有一個預設轉型 12/30 20:41
yoco315:為什麼一個還會太多 @@? 12/30 20:42
yoco315:還是你的意思是說應該分成好幾個建構子寫? 12/30 20:43
loveme00835:寫三個版本的建構子, 嫌重複部份太多再Extract Method 12/30 21:02
loveme00835:就好, 預設值這種東西只要宣告的地方亂改, 行為就會很 12/30 21:03
loveme00835:不一樣, 要預設值改用 private helper function 來做 12/30 21:04
loveme00835:gcc 支援 Delegating Constructor 的話就會方便多了 12/30 21:06
loveme00835:在二元運算子的地方都是接受 Rational const&, 搭配其 12/30 21:08
loveme00835:他型態作運算時轉換將會很頻繁, 有問題也不好偵錯 12/30 21:08
yoco315:了改 12/30 21:21
tomap41017:推,可以收起來當範例嗎?感謝>< 12/30 23:51
tomap41017:看了EffectiveC++後的確會想寫一個這樣的class..XD 12/30 23:52
tomap41017:另外小弟覺得在rvalue ref出來後,二元op的常數性其實 12/30 23:52
tomap41017:可以再討論,因為這樣就不能拿來當move ctor的參數 12/30 23:52
tomap41017:另外love大提到的預設值參數影響,可否給些例子或網頁 12/30 23:53
tomap41017:參考,有點沒有頭緒XD 12/30 23:53
loveme00835:哪本書忘了, 主要是測試的問題, 是否建構子責任太多, 12/30 23:57
loveme00835:caller 會被混淆, 還有路徑的測試, 我看完也會想寫這 12/30 23:58
loveme00835:樣的類別, 不過一想到重載帶來的責任, 還有語意上是 12/30 23:59
loveme00835:不是能做得明確...就懶了 12/31 00:00
loveme00835:當預設建構子時沒什麼問題, 不加explicit給一個引數時 12/31 00:22
loveme00835:兼當轉型使用, 所以 Rational(3) 跟 Ratinal(3,1) 意 12/31 00:23
loveme00835:義是: 轉型 vs 建構指定分數, 之所以預設分母是1只是 12/31 00:24
loveme00835:為了讓結果合理, 這兩個case測試的時候也是要分開考慮 12/31 00:25
loveme00835:範例只是範例, 不同地方能解釋不同概念, 但是並不代表 12/31 00:31
loveme00835:全部的概念都用上, 就是好物 12/31 00:31
spider391:謝謝 love 大大指教 12/31 08:48