相必读者您在编程生活中,应该会想过让自己的程序能够使用分数的运算、输出;也许命令行不方便实现像在纸上那样的自然书写显示,但是如同1/2
、3/4
这样的显示,或许也足以使自己感到一点成就感。
现在学习了C++,有了运算符重载等方便的功能,让我们能够方便的实现并使用分数的功能。
之前老师布置过一个【多项式的处理】的作业,奈何其仅支持正整数次数、小数系数;也是出于对它进行一个升级、让他能够支持分数的目的,我便决定开始写这样一个分数类。
这里有几篇博客,虽然我这里可能并没有实现提到的功能,但是它们帮助了我很多,欢迎大家浏览参考:
C++分数类 - 拱垲 - 博客园
C语言将循环小数/有限小数转换为分数 - CSDN
小数转化为分数 - 小白的个人总结 - 博客园
您也可以在搜索引擎中搜索相应的关键字,寻找您喜爱的内容。
我希望这个分数类大概能够满足这些功能:
fraction a, b;
cin >> a >> b;
cout << a + b;
a += 1;
a -= b;
a = a + 5;
if (a > b) cout << a;
else cout << b;
总结一下大概就是:基本四则运算、输入输出、大小判断;那么类的声明大概长这个样子:
class fraction
{
private:
int A; //numerator
int B; //denominator
public:
fraction();
fraction(int );
fraction(int ,int );
fraction(const char* );
void reduce();
void print();
bool if_int() const;
friend istream& operator>> (istream&, fraction&);
friend ostream& operator<< (ostream&, const fraction&);
void add(const fraction& );
void add(int );
void subtract(const fraction& );
void multiply(const fraction& );
void divide(const fraction& );
double value() const;
//使 分数 与 分数 之间的运算成立
fraction operator+ (const fraction& );
fraction operator- (const fraction& );
fraction operator* (const fraction& );
fraction operator/ (const fraction& );
void operator+= (const fraction& );
void operator-= (const fraction& );
void operator*= (const fraction& );
void operator/= (const fraction& );
//使 分数 与 整数 之间的运算成立
fraction operator+ (int );
fraction operator- (int );
fraction operator* (int );
fraction operator/ (int );
void operator+= (int );
void operator-= (int );
void operator*= (int );
void operator/= (int );
friend fraction operator+ (int, const fraction& );
friend fraction operator- (int, const fraction& );
friend fraction operator* (int, const fraction& );
friend fraction operator/ (int, const fraction& );
//使大小判断成立
int compare(const fraction& );
bool operator== (const fraction& );
bool operator!= (const fraction& );
bool operator> (const fraction& );
bool operator< (const fraction& );
bool operator>= (const fraction& );
bool operator<= (const fraction& );
bool operator== (int);
bool operator== (double );
bool operator!= (int );
bool operator> (int );
bool operator< (int );
bool operator>= (int );
bool operator<= (int );
};
笔者用两个整数,分别存储分子和分母,由此得以保存一个分数(存储时,分数将被约分至最简);如果需要,可以使用long long int
型数据。
由于操作过程中,我们可能需要求分子分母的最大公约数、最小公倍数,所以写两个函数:
int gcd(int x,int y) //辗转相除法求最大公因数
{
int t;
while (y!=0) {
t=x%y;
x=y;
y=t;
}
return x;
}
int lcm(int a, int b)
{
//return a*b/gcd(a,b);
//if (a % b == 0) return a; //这两句话可能会有一定的优化效果吧
//if (b % a == 0) return b; //不是很清楚……
int t_gcd = gcd(a,b);
if (a % t_gcd == 0) a /= t_gcd;
else if (b % t_gcd == 0) b /= t_gcd;
return a * b;
}
可以把它们俩声明为成员函数。
对于求a
、b
的最小公倍数,我放弃了直接用a*b/gcd(a,b)
的方法,因为a*b
可能会超出范围;若二者之一能被它们的最大公约数相除,先进行除法操作会好一点点。
好像忘了说明,笔者这个分数类仅保存分数约分过后的结果;另外,分母限定不能为0
,这个在构造函数里进行判断处理;如果分数是一个整数,则分母为存储为1
;分数的正负性仅取决于分子;
为了进行约分时更方便,我们先保存分数的正负性,然后对分子分母取绝对值,在约分过后,统一把正负性加在分子上;
void fraction::reduce()
{
bool if_negative = false;
if ((A < 0 && B > 0) || (A > 0 && B < 0)) //if (A * B < 0)
{
if_negative = true;
}
A = abs(A);
B = abs(B);
int d = gcd(A,B);
A /= d;
B /= d;
if (if_negative) A = -A;
}
和上面一样的思想,单独输出分数的正负;
void fraction::print()
{
int tA = abs(A);
if (A < 0) cout << " - ";
if (tA >= B && tA % B == 0) cout << (tA/B);
else if (tA == 0) cout<<0;
else cout << tA << "/" << B;
}
今天看这段代码,发现好像写得有点繁琐,下边缩减一下:
ostream& operator<< (ostream& os, const fraction& f)
{
int tA = abs(f.A);
if (f.A < 0) os << " - ";
if (tA % f.B == 0) os << (tA / f.B);
else os << tA << "/" << f.B;
return os;
}
bool fraction::if_int() const
{
return abs(A) % B == 0;
}
double fraction::value() const
{
return (double) A / B;
}
fraction::fraction(): A(0), B(1) {}
fraction::fraction(int a): A(a), B(1) {}
fraction::fraction(int a, int b)
{
if (b == 0) {cout<<"No 0 as denominator!\n"; b = 1;}
A = a;
B = b;
reduce();
}
笔者比较得意的是下面这个从字符串初始化的功能,它可以实现从如1
、1.2
、1/2
、2.4/3.2
、1/2.3
、2.3/4.8
等样式的字符串的初始化。
fraction::fraction(const char* str)
{
A = 0; B = 1;
char* sep = (char* )str;
char* dpoint = (char* )str;
while (*sep != '/' && *sep != '\0') sep++;
if (*sep == '\0') //未找到分数线
{
while (*dpoint != '.'&&*dpoint != '\0') dpoint++;
if(*dpoint == '\0') //没有小数点,即为一个整数
{
add( atoi(str) );
}
else
{
int t1 = atoi(str);
dpoint++;
int t2 = atoi(dpoint);
if (t1 < 0) t2 = -t2;
int t3 = 1;
while( *dpoint >= '0' && *dpoint <= '9') {t3*=10; dpoint++;}
A = t2;
//if (t1 < 0) A = -A;
B = t3;
reduce();
add(t1);
}
}
else //存在分数线
{
//处理分数线之前的部分,存入当前类
while (*dpoint != '.' && *dpoint != '/') dpoint++;
if(*dpoint == '/') //没有小数点,即为一个整数
{
add( atoi(str) );
}
else
{
int t1 = atoi(str);
dpoint++;
int t2 = atoi(dpoint);
if (t1 < 0) t2 = -t2;
int t3 = 1;
while( *dpoint >= '0' && *dpoint <= '9') {t3*=10; dpoint++;}
A = t2;
B = t3;
reduce();
add(t1);
}
//处理分数线之后的部分,
fraction div;
dpoint = sep;
while (*dpoint != '.'&&*dpoint != '\0') dpoint++;
if(*dpoint == '\0') //没有小数点,即为一个整数
{
div.add( atoi(sep+1) );
}
else
{
int t1 = atoi(sep+1);
dpoint++;
int t2 = atoi(dpoint);
if (t1 < 0) t2 = -t2;
int t3 = 1; //10^n
while( *dpoint >= '0' && *dpoint <= '9') {t3*=10; dpoint++;}
div.A = t2;
div.B = t3;
div.reduce();
div.add(t1);
}
divide(div);
}
}
istream& operator>> (istream& is, fraction& f)
{
char input[100];
is >> input;
f = fraction(input);
return is;
}
void fraction::add(const fraction& addend)
{
if (B == addend.B) {
A += addend.A;
return;
}
else if (addend.B == 1) {
A += (addend.A * B);
}
else {
int t = lcm(B, addend.B);
int m1 = t / B;
int m2 = t / addend.B;
A *= m1;
B *= m1;
A += (addend.A * m2);
reduce();
}
}
void fraction::add(int n)
{
A += n*B;
}
void fraction::subtract(const fraction& subtrahend)
{
if (B == subtrahend.B) {
A -= subtrahend.A;
return;
}
else if (subtrahend.B == 1) {
A -= (subtrahend.A * B);
}
else {
int t = lcm(B, subtrahend.B);
int m1 = t / B;
int m2 = t / subtrahend.B;
A *= m1;
B *= m1;
A -= (subtrahend.A * m2);
reduce();
}
}
void fraction::multiply(const fraction& multiplier)
{
int d1 = 1, d2 = 1;
if (A != 0) d1 = gcd(A, multiplier.B);
if (multiplier.A != 0) d2 = gcd(multiplier.A, B);
A /= d1;
B /= d2;
A *= (multiplier.A / d2);
B *= (multiplier.B / d1);
//reduce();
}
void fraction::divide(const fraction& divisor)
{
if (divisor.A == 0) {cout << "cannot divided by 0!\n"; return;}\
int d1 = 1, d2 = 1;
d1 = gcd(B,divisor.B);
if (A != 0 && divisor.A != 0) d2 = gcd(A,divisor.A);
A /= d2;
B /= d1;
A *= (divisor.B / d1);
B *= (divisor.A / d2);
//reduce();
}
其实上面这些函数可以返回一个对fraction
类的引用,实现连续调用。
上边的fraction::add(int)
方法其实大概并不需要,因为有了fraction::fraction(int)
这个构造函数之后,即便只有fraction::divide(const fraction &)
,a.divide(5)
也是可以成立的。不过,专门为整数写一个重载,能减少从int
生成类的时间,提高效率。
可以直接调用之前写好的add()
、subtract()
等方法,也可以重新写过;我就直接偷懒了:
fraction fraction::operator+ (const fraction& addend)
{
fraction t(A,B);
t.add(addend);
return t;
}
fraction fraction::operator- (const fraction& subtrahend)
{
fraction t(A,B);
t.subtract(subtrahend);
return t;
}
fraction fraction::operator* (const fraction& multiplier)
{
fraction t(A,B);
t.multiply(multiplier);
return t;
}
fraction fraction::operator/ (const fraction& divisor)
{
fraction t(A,B);
t.divide(divisor);
return t;
}
虽然我为int
型的参数写了重载,但下面这样写还是相当于偷懒了,跟不写一样,读者可以进行一下优化:
fraction fraction::operator+ (int num )
{
fraction addend(num);
fraction t(A,B);
t.add(addend);
return t;
}
fraction fraction::operator- (int num )
{
fraction subtrahend(num);
fraction t(A,B);
t.subtract(subtrahend);
return t;
}
fraction fraction::operator* (int num )
{
fraction multiplier(num);
fraction t(A,B);
t.multiply(multiplier);
return t;
}
fraction fraction::operator/ (int num )
{
fraction divisor(num);
fraction t(A,B);
t.divide(divisor);
return t;
}
当整数在分数类前时,也要能够成立:
fraction operator+ (int num, const fraction& t)
{
fraction p(num);
p.add(t);
return p;
}
fraction operator- (int num, const fraction& t)
{
fraction p(num);
p.subtract(t);
return p;
}
fraction operator* (int num, const fraction& t)
{
fraction p(num);
p.multiply(t);
return p;
}
fraction operator/ (int num, const fraction& t)
{
fraction p(num);
p.divide(t);
return p;
}
这些运算符恰好符合我们之前写好的add()
、subtract()
等方法的逻辑,直接调用即可:
void fraction::operator+= (const fraction& addend)
{
add(addend);
}
void fraction::operator-= (const fraction& subtrahend)
{
subtract(subtrahend);
}
void fraction::operator*= (const fraction& multiplier)
{
multiply(multiplier);
}
void fraction::operator/= (const fraction& divisor)
{
divide(divisor);
}
下面同样是偷懒划水之作,写了等于不写,读者可以自行优化定义:
void fraction::operator+= (int num)
{
fraction t(num);
add(t);
}
void fraction::operator-= (int num)
{
fraction t(num);
subtract(t);
}
void fraction::operator*= (int num)
{
fraction t(num);
multiply(t);
}
void fraction::operator/= (int num)
{
fraction t(num);
divide(t);
}
fraction::compare()
有三种返回值:1
表示当前分数大于参数传入的分数;-1
表示当前分数较小;0
表示两个分数相等:
int fraction::compare(const fraction& t)
{
if (A * t.A < 0)
{
if (A < 0) return -1;
if (t.A < 0) return 1;
}
if (A * t.B < t.A * B) return -1;
else if (A * t.B == t.A * B) return 0;
else return 1;
}
下面有些函数的定义总算是没有偷懒了:
bool fraction::operator== (const fraction& t) {
return A * t.B == t.A * B;
//return (A == t.A && B == t.B);
}
bool fraction::operator!= (const fraction& t) {
return A * t.B != t.A * B;
}
bool fraction::operator> (const fraction& t) {
if (compare(t) == 1) return true;
else return false;
}
bool fraction::operator< (const fraction& t) {
if (compare(t) == -1) return true;
else return false;
}
bool fraction::operator>= (const fraction& t) {
if(compare(t) != -1) return true;
else return false;
}
bool fraction::operator<= (const fraction& t) {
if(compare(t) != 1) return true;
else return false;
}
bool fraction::operator== (int t) {
return A == t * B;
}
bool fraction::operator== (double t) {
return value() == t;
}
bool fraction::operator!= (int num) {
return A != num * B;
}
bool fraction::operator> (int num) {
return A > num * B;
}
bool fraction::operator< (int num) {
return A < num * B;
}
bool fraction::operator>= (int num) {
return A >= num * B;
}
bool fraction::operator<= (int num) {
return A <= num * B;
}
恰好有个作业题要把几个分数加起来,我就试了一下:
int main()
{
fraction a,b;
//fraction z(1); fraction x("10/11.9") ; fraction c("15/17.2") ; fraction v("5/13.7");
fraction z,x,c,v;
cin>>z>>x>>c>>v;
cout<<z<<" "<<x<<" "<<c<<" "<<v<<endl;
a = x + z + c + v;
cout<<a<<endl;
fraction q("1/11.9") ; fraction w("1/17.2") ;fraction e("1/13.7");
cout<<q<<" "<<w<<" "<<e<<" "<<endl;
b = q + w + e;
cout<<b<<endl;
cout<<a/b;
return 0;
}
也正是这么一用使我发现了自己的几个问题:一是通分算法不够优秀,也就是求最小公倍数的函数有些问题,会溢出;二是自己的函数的参数是fraction&
而不是const fraction &
,这样有个问题就是,类似下面的语句不会通过:
fraction a = fraction(1,2) + fraction(1,3);
我猜想的原因应该就是,通过类名加参数生成的临时类,不能被更改,所以只能用常引用。下面这个例子貌似也是这个理儿:
如果函数是这样定义的:
void some_function(string & a, string & b) {
string s = a + b;
cout << s;
}
那么下面这样使用就不行:
some_function("app","le");
解决方法:
要么这样调用:
string a = "app";
string b = "le";
some_function(a,b);
或者将函数改为:
void some_function(string a, string b) { /*...*/ }
但最好还是这样定义函数:
void some_function(const string & a, const string & b) { /*...*/ }
当然,也许这些本来应该都是常识的……