C++有理數類設計

weixin_43912687發表於2020-12-22


有理數類主要功能設計

之前用C語言設計了有理數資料型別C語言實現有理數資料型別(有理數庫),這裡再用C++做個有理數類。有理數類的功能就比C語言的有理數資料型別功能強多了,主要就是運算子過載。

  1. 有理數類,能實現以m/n形式的輸入輸出,支援加減乘除運算;
  2. 支援加賦,減賦,乘賦,除賦,判等,大於,小於運算;
  3. 也支援和普通double型別混合進行以上這些運算;
  4. 運算結果均已化簡;
  5. 注意由於內部分子分母均是int型,乘除法容易溢位。
  6. 設計除法的除數為0時,會報錯退出。

以下是有理數類標頭檔案rational_oop.h

#ifndef RATIONAL_H
#define RATIONAL_H
#include <iostream>

using namespace std;

class Rational 
{
private:
   int fenzi, fenmu;
   void simply();//化簡
   bool negative() const;//判斷是否是負數
public:
   Rational(int numerator = 0, int denominator = 1);//建構函式
   Rational(const double n);//型別轉換建構函式
   Rational(const Rational & ra);//複製建構函式
   //~Rational();沒有動態分配,預設析構就行

   Rational & operator =(const Rational & ra);
   Rational & operator =(const double & n);//使有理數類物件能被double型別賦值

   //以下20個函式使有理數類物件能和double型別混合加減乘除和賦值
   Rational operator +(const Rational & ra);
   Rational operator -(const Rational & ra);
   Rational operator *(const Rational & ra);
   Rational operator /(const Rational & ra);
   Rational operator +(const double & n);
   Rational operator -(const double & n);
   Rational operator *(const double & n);
   Rational operator /(const double & n);
   friend Rational operator +(const double & n, const Rational & ra);
   friend Rational operator -(const double & n, const Rational & ra);
   friend Rational operator *(const double & n, const Rational & ra);
   friend Rational operator /(const double & n, const Rational & ra);
   Rational & operator +=(const Rational & ra);
   Rational & operator -=(const Rational & ra);
   Rational & operator *=(const Rational & ra);
   Rational & operator /=(const Rational & ra);
   Rational & operator +=(const double & n);
   Rational & operator -=(const double & n);
   Rational & operator *=(const double & n);
   Rational & operator /=(const double & n);

   int GetFenzi() { return fenzi;  }
   int GetFenmu() { return fenmu;  }
   void Set(int numerator, int denominator) { fenzi = numerator; fenmu = denominator; }

   //過載強制型別轉換運算子,使得凡是應該出現double型的地方,均可以由有理數類物件替代。比如,可以使得
   //有理數類物件和普通double型比較大小成為可能
   operator double();

   //支援以m/n形式的有理數類物件輸入輸出,負號會顯示在前面。
    friend ostream & operator <<(ostream & os, const Rational & ra);
    friend istream & operator >>(istream & is, Rational & ra);
};

#endif

rational_oop.cpp

#include <iostream>
#include <string>
#include <cstdlib>
#include "rational_oop.h"

using namespace std;

void Rational::simply()//輾轉相除法求最大公約數化簡
{
    int a = fenzi, b = fenmu;

    while (b){
        int mod = a % b;
        a = b;
        b = mod;
    }
    
    fenzi /= a;
    fenmu /= a;
}

bool Rational::negative() const
{
    return (fenzi < 0 || fenmu < 0);
}

Rational::Rational(int numerator, int denominator)
{
    fenzi = numerator;
    fenmu = denominator;
    simply();//確保任意路徑得到的有理數類物件均是最簡形式
}

Rational::Rational(const double n)
{
    double numerator = n, denominator = 1;

    while ((int)numerator != numerator) {
        numerator *= 10;
        denominator *= 10;
    }

    fenzi = numerator, fenmu = denominator;
    simply();//確保任意路徑得到的有理數類物件均是最簡形式
}

Rational::Rational(const Rational & ra): fenzi(ra.fenzi), fenmu(ra.fenmu) {}

Rational & Rational::operator =(const Rational & ra)
{
    fenzi = ra.fenzi, fenmu = ra.fenmu;
    return *this;
}

Rational & Rational::operator =(const double & n)
{
    Rational tmp(n);
    *this = tmp;
    return *this;
}

Rational Rational::operator +(const Rational & ra)
{
    int numerator  = fenzi * ra.fenmu + ra.fenzi * fenmu;
    int denominator = fenmu * ra.fenmu;

    return Rational(numerator, denominator);
}

Rational Rational::operator -(const Rational & ra)
{
    int numerator  = fenzi * ra.fenmu - ra.fenzi * fenmu;
    int denominator = fenmu * ra.fenmu;

    return Rational(numerator, denominator);
}

Rational Rational::operator *(const Rational & ra)
{
    int numerator  = fenzi * ra.fenzi;
    int denominator = fenmu * ra.fenmu;

    return Rational(numerator, denominator);
}

Rational Rational::operator /(const Rational & ra)
{
    if (ra.fenzi == 0) {
        cout << "除數為0了!" << endl;
        exit(1);
    }

    int numerator  = fenzi * ra.fenmu;
    int denominator = fenmu * ra.fenzi;

    return Rational(numerator, denominator);
}

Rational Rational::operator +(const double & n)
{
    Rational tmp(n);
    return *this + tmp;
}

Rational Rational::operator -(const double & n)
{
    Rational tmp(n);
    return *this - tmp;
}

Rational Rational::operator *(const double & n)
{
    Rational tmp(n);
    return *this * tmp;
}

Rational Rational::operator /(const double & n)
{
    Rational tmp(n);
    return *this / tmp;
}

Rational operator +(const double & n, const Rational & ra)
{
    Rational tmp(n);
    return tmp + ra;
}

Rational operator -(const double & n, const Rational & ra)
{
    Rational tmp(n);
    return tmp - ra;
}

Rational operator *(const double & n, const Rational & ra)
{
    Rational tmp(n);
    return tmp * ra;
}

Rational operator /(const double & n, const Rational & ra)
{
    Rational tmp(n);
    return tmp / ra;
}

Rational & Rational::operator +=(const Rational & ra)
{
    Rational tmp =  *this + ra;
    *this = tmp;
    return *this;
}

Rational & Rational::operator -=(const Rational & ra)
{
    Rational tmp =  *this - ra;
    *this = tmp;
    return *this;
}

Rational & Rational::operator *=(const Rational & ra)
{
    Rational tmp = *this * ra;
    *this = tmp;
    return *this;
}

Rational & Rational::operator /=(const Rational & ra)
{
    Rational tmp =  *this / ra;
    *this = tmp;
    return *this;
}

Rational & Rational::operator +=(const double & ra)
{
    Rational tmp(ra);
    *this += tmp;
    return *this;
}

Rational & Rational::operator -=(const double & ra)
{
    Rational tmp(ra);
    *this -= tmp;
    return *this;
}

Rational & Rational::operator *=(const double & ra)
{
    Rational tmp(ra);
    *this *= tmp;
    return *this;
}

Rational & Rational::operator /=(const double & ra)
{
    Rational tmp(ra);
    *this /= tmp;
    return *this;
}

Rational::operator double()
{
    return (double) fenzi / fenmu;
}

ostream & operator <<(ostream & os, const Rational & ra)
{
    if (ra.fenzi == 0)
        os << 0;
    else if (ra.negative())
        os << "-" << abs(ra.fenzi) << "/" << abs(ra.fenmu); //如果是負數,符號寫在前面。
    else
        os << ra.fenzi << "/" << ra.fenmu;
    return os;
}

istream & operator >>(istream & is, Rational & ra)
{
    string s;
    is >> s;

    int pos = s.find("/", 0);

    string sTmp = s.substr(0, pos);
    ra.fenzi = atoi(sTmp.c_str());//讀入分子
    sTmp = s.substr(pos+1, s.length() - pos - 1);
    ra.fenmu = atoi(sTmp.c_str());//讀入分母

    return is;
}

測試程式和程式執行結果

GCC編譯的。
main.cpp

#include <iostream>
#include "rational_oop.h"

using namespace std;

int main(void)
{
    cout << "該程式用來測試有理數類:" << endl;
    cout << "由於內部用int型裝分子分母,所以輸入的數不要太大,計算乘除容易溢位:" << endl << endl;

    Rational ra1, ra2(8.8);
    cout << "請以m/n的形式輸入一個有理數ra1:";
    cin >> ra1;
    Rational ra3 = ra1;
    cout << "這裡還用8.8為引數構造了ra2,用ra1為引數初始化ra3.現在:" << endl;
    cout << "ra1 = " << ra1 << " ra2 = " << ra2 << " ra3 = " << ra3 << ";" << endl << endl;

    ra1 = ra2;
    ra2 = 66.6;
    cout << "這裡讓ra1 = ra2, ra2 = 66.6。"<< "現在:" << endl;
    cout << "ra1 = " << ra1 << " ra2 = " << ra2 << " ra3 = " << ra3 << ";" << endl << endl;

    cout << "ra2 + ra3 = " << (ra2 + ra3) << endl;
    cout << "ra2 - ra3 = " << (ra2 - ra3) << endl;
    cout << "ra2 * ra3 = " << (ra2 * ra3) << endl;
    cout << "ra2 / ra3 = " << (ra2 / ra3) << endl << endl;

    cout << "ra2 + 34.55 = " << (ra2 + 34.55) << endl;
    cout << "ra2 - 7.9 = " << (ra2 - 7.9) << endl;
    cout << "ra2 * 3.6 = " << (ra2 * 3.6) << endl;
    cout << "ra2 / 2.5 = " << (ra2 / 2.5) << endl << endl;

    cout << "5.0 + ra2 = " << (5.0 + ra2) << endl;
    cout << "88.88 - ra2 = " << (88.88 - ra2) << endl;
    cout << "5.6 * ra2 = " << (5.6 * ra2) << endl;
    cout << "102.4 / ra2 = " << (102.4 / ra2) << endl << endl;

    cout << "現在" ;
    cout << "ra1 = " << ra1 << " ra2 = " << ra2 << " ra3 = " << ra3 << ";" << endl << endl;
    ra2 += 6.0;
    cout << "ra2 += 6.0, ra2 = " << ra2 << endl;
    ra2 -= 6.0;
    cout << "ra2 -= 6.0, ra2 = " << ra2 << endl;
    ra2 *= 6.0;
    cout << "ra2 *= 6.0, ra2 = " << ra2 << endl;
    ra2 /= 6.0;
    cout << "ra2 /= 6.0, ra2 = " << ra2 << endl<< endl;

    ra2 += ra3;
    cout << "ra2 += ra3, ra2 = " << ra2 << endl;
    ra2 -= ra3;
    cout << "ra2 -= ra3, ra2 = " << ra2 << endl;
    ra2 *= ra3;
    cout << "ra2 *= ra3, ra2 = " << ra2 << endl;
    ra2 /= ra3;
    cout << "ra2 /= ra3, ra2 = " << ra2 << endl << endl;

    cout << "現在" ;
    cout << "ra1 = " << ra1 << " ra2 = " << ra2 << " ra3 = " << ra3 << ";" << endl << endl;
    cout << "我現在double出一個變數n。賦初值為9.1;" << endl;
    double n = 9.1;
    n += ra2;
    cout << "n += ra2, n = " << n << endl;
    n -= ra2;
    cout << "n -= ra2, n = " << n << endl;
    n *= ra2;
    cout << "n *= ra2, n = " << n << endl;
    n /= ra2;
    cout << "n /= ra2, n = " << n << endl << endl;

    cout << "現在" ;
    cout << "ra1 = " << ra1 << " ra2 = " << ra2 << " ra3 = " << ra3 << ";" << endl << endl;

    cout << "ra1 > ra2, 結果為" << (ra1 > ra2) << endl;
    cout << "ra1 != ra2, 結果為" << (ra1 != ra2) << endl;
    cout << "ra1 == ra1, 結果為" << (ra1 == ra1) << endl;
    cout << "ra1 > 8, 結果為" << (ra1 > 8) << endl;
    cout << "88 > ra2, 結果為" << (88 > ra2) << endl;
    cout << "8.8 == ra1, 結果為" << (8.8 == ra1) << endl;

    return 0;
}

執行示例結果:
在這裡插入圖片描述


一點小問題

我也看了網上別人寫的,我覺得我這個才是比較夠大夠完整的。有人把 判等 ,!=, >, <這些全都過載了,其實沒必要,甚至連+ - ×÷我感覺也沒必要。關鍵是過載強制型別轉換運算子(double)。(operator double(); 成員函式)
有了對double運算子的過載,本該出現double型別的變數或常數的地方,如果出現了一個Rational有理數類物件,那麼該物件上的operator double()成員函式就會被呼叫,然後取其返回值使用。比如“<”號兩旁的運算元如果是double型別的變數或常量的話,就能解釋得通,而因為過載了double運算子,有理數類物件就可以替換掉這兩個運算元。按理說+ - ×÷這些運算也應如此。所以可以省略好多運算子過載函式。但我實際測試了一下:
把所有運算子過載全都註釋掉,只留強制型別轉換運算子過載的情況下,同樣在main.cpp裡輸入3/4的資料。會發現可以執行,結果也正確,只是結果都用小數來表示的:
在這裡插入圖片描述我們希望結果是用有理數來表示的,所以過載+ - ×÷,加賦,減賦等還是有必要的,但判等之類的運算結果是真或假,就沒必要過載了。
我突發奇想如果用(Rational)強制型別轉換把小數運算結果轉成有理數分數結果行不行,發現:
cout << "ra2 + ra3 = " << (Rational)(ra2 + ra3) << endl; //執行顯示ra2 + ra3 = 1347/20, 正確
cout << "ra2 * ra3 = " << (Rational)(ra2 * ra3) << endl; //程式卡住了
其他四則運算也有問題,用(Rational)強制型別轉換不一定管用。不知道什麼原因,沒深究。

相關文章