作者spider391 (小乖)
看板C_and_CPP
標題[分享] 建立一個分數類別 ( class Rational )
時間Thu Dec 30 13:32:35 2010
目標:
建立一個分數類別,運作方式跟內建的 + 運算子相同。
設計:
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
推 yoco315:認真的態度感天動地,只好給推 12/30 19:54
→ 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