[分享] 建立一個分數類別 ( class Rational )

看板C_and_CPP (C/C++)作者 (小乖)時間15年前 (2010/12/30 13:32), 編輯推噓3(3029)
留言32則, 4人參與, 最新討論串1/1
目標: 建立一個分數類別,運作方式跟內建的 + 運算子相同。 設計: 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

12/30 13:35, , 1F
12/30 13:35, 1F

12/30 19:54, , 2F
認真的態度感天動地,只好給推
12/30 19:54, 2F

12/30 19:56, , 3F
另補充現成的boost::rational http://tinyurl.com/29khprb
12/30 19:56, 3F

12/30 20:08, , 4F
不是很推给預設值的建構子
12/30 20:08, 4F

12/30 20:10, , 5F
隱式轉換也太多 ~"~
12/30 20:10, 5F

12/30 20:41, , 6F
這個預設建構子是必要的耶..而且他不是只有一個預設轉型
12/30 20:41, 6F

12/30 20:42, , 7F
為什麼一個還會太多 @@?
12/30 20:42, 7F

12/30 20:43, , 8F
還是你的意思是說應該分成好幾個建構子寫?
12/30 20:43, 8F

12/30 21:02, , 9F
寫三個版本的建構子, 嫌重複部份太多再Extract Method
12/30 21:02, 9F

12/30 21:03, , 10F
就好, 預設值這種東西只要宣告的地方亂改, 行為就會很
12/30 21:03, 10F

12/30 21:04, , 11F
不一樣, 要預設值改用 private helper function 來做
12/30 21:04, 11F

12/30 21:06, , 12F
gcc 支援 Delegating Constructor 的話就會方便多了
12/30 21:06, 12F

12/30 21:08, , 13F
在二元運算子的地方都是接受 Rational const&, 搭配其
12/30 21:08, 13F

12/30 21:08, , 14F
他型態作運算時轉換將會很頻繁, 有問題也不好偵錯
12/30 21:08, 14F

12/30 21:21, , 15F
了改
12/30 21:21, 15F

12/30 23:51, , 16F
推,可以收起來當範例嗎?感謝><
12/30 23:51, 16F

12/30 23:52, , 17F
看了EffectiveC++後的確會想寫一個這樣的class..XD
12/30 23:52, 17F

12/30 23:52, , 18F
另外小弟覺得在rvalue ref出來後,二元op的常數性其實
12/30 23:52, 18F

12/30 23:52, , 19F
可以再討論,因為這樣就不能拿來當move ctor的參數
12/30 23:52, 19F

12/30 23:53, , 20F
另外love大提到的預設值參數影響,可否給些例子或網頁
12/30 23:53, 20F

12/30 23:53, , 21F
參考,有點沒有頭緒XD
12/30 23:53, 21F

12/30 23:57, , 22F
哪本書忘了, 主要是測試的問題, 是否建構子責任太多,
12/30 23:57, 22F

12/30 23:58, , 23F
caller 會被混淆, 還有路徑的測試, 我看完也會想寫這
12/30 23:58, 23F

12/30 23:59, , 24F
樣的類別, 不過一想到重載帶來的責任, 還有語意上是
12/30 23:59, 24F

12/31 00:00, , 25F
不是能做得明確...就懶了
12/31 00:00, 25F

12/31 00:22, , 26F
當預設建構子時沒什麼問題, 不加explicit給一個引數時
12/31 00:22, 26F

12/31 00:23, , 27F
兼當轉型使用, 所以 Rational(3) 跟 Ratinal(3,1) 意
12/31 00:23, 27F

12/31 00:24, , 28F
義是: 轉型 vs 建構指定分數, 之所以預設分母是1只是
12/31 00:24, 28F

12/31 00:25, , 29F
為了讓結果合理, 這兩個case測試的時候也是要分開考慮
12/31 00:25, 29F

12/31 00:31, , 30F
範例只是範例, 不同地方能解釋不同概念, 但是並不代表
12/31 00:31, 30F

12/31 00:31, , 31F
全部的概念都用上, 就是好物
12/31 00:31, 31F

12/31 08:48, , 32F
謝謝 love 大大指教
12/31 08:48, 32F
文章代碼(AID): #1D71brKT (C_and_CPP)
文章代碼(AID): #1D71brKT (C_and_CPP)