在寫24點(diǎn)的程序時(shí),要處理除法運(yùn)算,不可避免地將引入小數(shù)點(diǎn),但本人對(duì)于計(jì)算機(jī)中的浮點(diǎn)數(shù)實(shí)在沒有太多的好感。權(quán)衡再三,終于決定下定決心寫一個(gè)有理數(shù)類(以下簡(jiǎn)稱為CRational),以解決除法運(yùn)算這個(gè)問題。并且有理數(shù)這個(gè)類也是一個(gè)很好的C++練習(xí)題,可以用來(lái)練練手,以下將看到,它的實(shí)現(xiàn)雖然不難,但也不是很容易。有理數(shù)這個(gè)東西,屬于用戶自定義的數(shù)據(jù)類型,c++對(duì)此的支持,真可謂完美,既不失效率,又具備美觀。c++可以讓程序員做出來(lái)的自定義類型,其行為可以表現(xiàn)得好像是由語(yǔ)言層面實(shí)現(xiàn)的那個(gè)樣子,不管從語(yǔ)法、安全、效率上講。這一點(diǎn),所有的語(yǔ)言都沒法和c++媲美。
不管怎么說(shuō),CRational的需求相當(dāng)明確,它一定有分子、分母、然后支持加減乘除這四種運(yùn)算,于是,一口氣馬上就能寫下它的定義。
class CRational


{
public:
CRational(int nNumberator=0, int nDenominator=1);

int Numberator()const
{ return m_nNum;}

int Denominator()const
{ return m_nDe;}

CRational& operator+=(const CRational& _Right);
CRational& operator-=(const CRational& _Right);
CRational& operator*=(const CRational& _Right);
CRational& operator/=(const CRational& _Right);
CRational operator-()const // 一元操作符,用以取反

{
return CRational(-m_nNum, m_nDe);
}

private:
int m_nNum;
int m_nDe;
}; 嗯,我承認(rèn)代碼很匈牙利,中MFC的毒太深。毋庸置疑,分子分母只能獲取,不能設(shè)置,函數(shù)名中,沒有加Get的前綴,實(shí)在是因?yàn)榇a中它出現(xiàn)的地方太多了,所以能避免就盡量避免。沒有涉及資源分配,析構(gòu)函數(shù)可以忽略,拷貝構(gòu)造函數(shù)和賦值函數(shù)也不用寫了,編譯器將會(huì)提供缺省的實(shí)現(xiàn),足以滿足我們的要求。貌似應(yīng)該還要有一個(gè)返回求取小數(shù)值的操作,但是,這個(gè)類本身就是為了避免操作小數(shù)點(diǎn),而且,計(jì)算小數(shù)點(diǎn)值可通過分子/分母的方法計(jì)算出來(lái)。總之,CRational的終極接口就是這個(gè)樣子了。
每個(gè)有理數(shù)都有一個(gè)標(biāo)準(zhǔn)的等價(jià)類,這個(gè)標(biāo)準(zhǔn)的有理數(shù)的分子、分母都不能再約分了,而且可以暫時(shí)假設(shè)符號(hào)位出現(xiàn)于分子中,分母則為正整數(shù),比如說(shuō),3/6、-4/-8都等價(jià)于1/2,因此,在這個(gè)有理數(shù)類中,必須有一個(gè)標(biāo)準(zhǔn)化的操作,問題是,這個(gè)標(biāo)準(zhǔn)化函數(shù)是返回一個(gè)標(biāo)準(zhǔn)的有理數(shù)還是將有理數(shù)自身直接就標(biāo)準(zhǔn)化了。經(jīng)過多方面的權(quán)衡,特別是為了兼容現(xiàn)有的整型變量(int, char, short等),整型變量可看成分母為1的有理數(shù),我決定讓CRational一直處于標(biāo)準(zhǔn)化的狀態(tài)下,標(biāo)準(zhǔn)化的狀態(tài)由CRational自己來(lái)維持,客戶無(wú)須知道標(biāo)準(zhǔn)化的這個(gè)細(xì)節(jié),因此,將void standarlize()聲明于其private的區(qū)域下,其實(shí)現(xiàn)如下:
void CRational::standarlize()


{
if (m_nDe < 0)

{
m_nDe = -m_nDe;
m_nNum = -m_nNum;
}
int nGcd = gcd(abs(m_nNum), m_nDe);
m_nNum /= nGcd;
m_nDe /= nGcd;
}

其中,毫無(wú)疑問,gcd為最大公約數(shù),gcd沒有訪問CRational的任何非靜態(tài)(nonstatic)變量,因此,它必將不可成為CRational的非靜態(tài)函數(shù)。將gcd聲明為靜態(tài)函數(shù),雖然可避免污染全局空間,但也使得外部代碼必須通過CRational來(lái)調(diào)用gcd函數(shù),語(yǔ)法上不方便,而且,gcd本身就是一個(gè)獨(dú)立性很強(qiáng)而且又通用的函數(shù),從意義上,它也不應(yīng)該屬于CRational里面的東西。因此,gcd只能為全局函數(shù)。關(guān)于靜態(tài)函數(shù)和全局函數(shù)的選擇,鑒于“類的接口要盡可能的小”的原則和其他的一些問題,只要函數(shù)不訪問類的靜態(tài)變量,也不通過類的變量來(lái)訪問到其非靜態(tài)成員,它就應(yīng)該是全局函數(shù)。全局函數(shù)是好東西,某些語(yǔ)言為了堅(jiān)持所謂的純粹的面向?qū)ο螅室獠恢С郑瑢?shí)在讓人用起來(lái)很不痛快,它的污染全局空間的問題,完全可以通過命名空間來(lái)解決。總之,只要可能的話,就應(yīng)該將函數(shù)聲明為全局函數(shù),沒什么不好。好了,請(qǐng)看gcd的實(shí)現(xiàn)。
unsigned int gcd(unsigned int x, unsigned int y)


{
unsigned int nTimes=0;
for (; 0 == (x&1) && 0 == (y&1); x>>=1, y>>=1)
++nTimes;

if (x < y)
swap(x, y);

while (y > 0)

{
for (; 0 == (x & 1 );x >>= 1 )
;

if (x < y)
swap(x, y);
x -= y;
if (x < y)
swap(x, y);
}
return x << nTimes;
} 其算法源于《編程之美》,可看成是非遞歸的版本。咦,怎么會(huì)這么長(zhǎng),與日常所見的到似乎不太一樣,高效算法的代碼貌似都會(huì)很長(zhǎng),好比strlen。再仔細(xì)看,里面居然沒有取余的操作。嗯,它的算法核心,用移位和減法這兩種快速的運(yùn)算來(lái)替代取模這種慢速運(yùn)算。
有了standarlize()之后,CRational的幾個(gè)函數(shù)的實(shí)現(xiàn)如下所示:
CRational::CRational(int nNumberator, int nDenominator)
: m_nNum(nNumberator), m_nDe(nDenominator)


{
assert(nDenominator != 0);
standarlize();
}

CRational& CRational::operator+=(const CRational& _Right)


{
m_nNum = m_nNum*_Right.m_nDe + _Right.m_nNum*m_nDe;
m_nDe *= _Right.m_nDe;
standarlize();
return *this;
}……
構(gòu)造函數(shù)中,似乎應(yīng)該檢查分母為0的情況,然后拋出異常。但是,這屬于契約使用的問題,用戶違背的契約,一切后果,必須自己承擔(dān),我們的代碼無(wú)須對(duì)此負(fù)責(zé)。 此外,就是各種+、-、*、/、==、輸出等各種全局運(yùn)算符重載的操作了。得益于C++的缺省類型轉(zhuǎn)換,我們不用再做其他事情,就可以很好地讓我們的CRational很好地與融入到原有的各種整型世界中去。當(dāng)然,為了效率起見,似乎有必要針對(duì)各種整型提供+、-、*、/的各種重載版本(complex就是這樣做的),但在此,確實(shí)沒有必要。缺省類型轉(zhuǎn)換有時(shí)雖然會(huì)帶來(lái)一些問題,但是,當(dāng)確實(shí)需要它的時(shí)候,它就能發(fā)揮重大作用了。C++的各種特性就是這樣,你可以不用,它也不打擾你(你要故意或無(wú)意用錯(cuò),那也沒辦法),當(dāng)真正需要到的時(shí)候,特性的威力就顯示出來(lái)了。很多人之所以愿意沉迷于C++,就在于它不剝奪程序員的任何一點(diǎn)選擇的權(quán)利。
ostream& operator << (ostream& out, const CRational& rat)


{
cout << rat.Numberator();
if (rat.Denominator() != 1)
cout << "/" << rat.Denominator();
return out;
}

CRational operator+(const CRational& _Left, const CRational& _Right)


{
CRational _Tmp(_Left);
return _Tmp += _Right;
}……
嗯,一再寫這些入門級(jí)的文章,本座也很自覺有失身價(jià),也算是對(duì)網(wǎng)絡(luò)世界的一點(diǎn)回報(bào)吧。只要有一個(gè)人看了,能有所啟發(fā),在下就心滿意足了,為免誤人子弟,也歡迎有人批評(píng)指正。