【C++】运算符重载实现分数类的四则运算

题目:定义一个分数类(Fraction)。该类具有分子、分母两个成员属性。

编写程序完成以下功能:

  1. 定义合适的构造函数
  2. 定义前自增、后自增运算符重载,完成分子+1操作
  3. 定义分数加减乘除四则运算的运算符重载函数。

Fraction.h头文件代码:

#pragma once			
class Fraction {
private:
	int p;
	int q;
public:
	Fraction(int, int);		//构造函数声明
	int gcd(int, int)const;	//求最大公约数
	void Output();			//打印

	Fraction& operator++();		
		//前自增运算符重载,返回Fraction类引用
	Fraction operator++(int);	
		//后自增运算符重载,返回一个Fraction类对象。规定参数里必须加上int型。实际上并不起作用
	friend Fraction operator+(const Fraction& , const Fraction&);	
			//全局友元加法运算符重载函数。																
			//常引用(const Fraction&)是因为只需要对对象进行计算,不需要改变原对象的数据
	friend Fraction operator-(const Fraction&, const Fraction&);	
			//全局友元减法运算符重载函数
	friend Fraction operator*(const Fraction&, const Fraction&);	
			//全局友元乘法运算符重载函数
	friend Fraction operator/(const Fraction&, const Fraction&);	
			//全局友元除法运算符重载函数
};


下面是类的实现:(均放在main.cpp里,即声明和定义分开)

 gcd(greatest common divisor)函数实现:

用到了辗转相除法。可以参考辗转相除法 - unique_ptr - 博客园 (cnblogs.com)

int Fraction::gcd(int a, int b)const
{	//辗转相除法,返回最大公约数
	int r;
	while (b) {
		r = b;
		b = a % b;
		a = r;
	}
	return (a == 0 ? 1 : a);	
}
  • while(b)即while(b!=0)
  • 这种写法不限定a和b的大小关系。以gcd(12,16)为例,第一次循环,r=16,b=12(注意12%16等于12),a=16,我们神奇的发现,当a<b的时候,在第一次循环会自动交换a和b。故而gcd(12,16)和gcd(16,12)等价。
  • 由于后面我会用到int型/gcd(),故gcd不能取到0。所以在return的时候写成(a==0?1:a)

 构造函数实现

Fraction::Fraction(int m, int n) {
	int g = gcd(m, n);
	p = m / g;			//同时除以最大公约数
	q = n / g;
}
  • 为了使得分数在初始化时合理,我们同时除最大公约数,进行化简

 后自增(x++)运算符重载:

Fraction Fraction::operator++(int) {	//后自增
	Fraction temp(*this);	//用默认拷贝构造函数,把当前对象保存在temp里
	p++;
	int g = gcd(p, q);
	p /= g;
	q /= g;
	return temp;
}

注意:

  1. 运算符重载的两种方法:①类内定义非静态的成员函数②类外定义全局函数、类内声明成友元函数。关于友元函数,可以参考C++友元函数和友元类(C++ friend关键字) (biancheng.net)                                                 
  2. 注意后自增,x++返回的是一个临时对象,这个对象是自增之前的值。故,我们需要利用拷贝构造函数先保存一下这个对象。里面用到了this指针,可以参考C++ this指针详解(精辟) (biancheng.net)

前自增(++x)运算符重载:

Fraction& Fraction::operator++() {		//前自增
	p++;
	int g = gcd(p, q);
	p /= g;
	q /= g;
	return *this;			//返回当前对象的引用
}

注意:

  • 前自增返回值是Fraction&类型;后自增返回的是Fraction类型。这是因为:++x返回的是完成自增后、对象的引用,我们仍然可以对x进行修改。后自增只会返回自增前的临时对象,并且这个对象是不能作为左值出现的。为了讲清楚这一点,举一个例子:

    ++(++x)和(++x)++都是正确的。

    ++(x++)和(x++)++都是错误的。

    原因就在于第一个里++x表示自增后的引用,x++表示自增前的一个临时对象,我们无法对这个临时对象进行操作。

最重要的一点:前自增和后自增都是以“①类内定义非静态的成员函数”形式出现,而且参数列表为空,是因为成员函数自带一个this指针,这是隐性存在的。相当于我们有一个隐性的参数。

而我们下面要介绍的加减乘除的运算符重载都是以"②类外定义全局函数、类内声明成友元函数"形式出现,一般我们规定运算具有对称性的运算符往往采取②。实际上,无论是自增还是四则运算,都可以采用另外一种形式写。具体操作方法请读者自行思考,笔者受限于篇幅不赘述。

+运算符重载:

Fraction operator+(const Fraction& F1, const Fraction& F2) {
	Fraction temp(0, 0);
	temp.q = F1.q * F2.q;
	temp.p = F1.q * F2.p + F2.q * F1.p;
	int g = temp.gcd(temp.p, temp.q);
	temp.p /= g;
	temp.q /= g;
	return temp;
}

注意:

参数列表使用了const &,常引用。常引用适用于只需要获取对象的数据成员,而不对成员进行更改时。在这里,我们只需要获得F1、F2的分子分母,把计算结果赋值给temp。故而可以采用常引用。 

-运算符重载:

Fraction operator-(const Fraction& F1, const Fraction& F2) {
	Fraction temp(F2);
	temp.p *= -1;			//temp取F2的负数
	return (F1 + temp);		//复用了对+的重载,减少代码量
}

注意:

我们只需要把temp的分子改一下,就可以复用+的重载。这是个减少代码量的好技巧。 

下面附上main.cpp的完整代码:

#include<iostream>
#include"Fraction.h"
using namespace std;
int main() {
	srand((unsigned)time(NULL));	//随机数生成数据
	Fraction f1(rand() % 3 + 1, rand() %4  + 2);
	Fraction f2(rand() % 3 + 1, rand() %4  + 2);
	Fraction f3=f1+f2;
	Fraction f4 = f1 - f2;
	Fraction f5=  f1 * f2;
	Fraction f6 = f1 / f2;
	f1.Output();
	f2.Output();
	f3.Output();
	f4.Output();
	f5.Output();
	f6.Output();

}

//Fraction类的实现:

Fraction::Fraction(int m, int n) {
	int g = gcd(m, n);
	p = m / g;			//同时除以最大公约数
	q = n / g;
}
int Fraction::gcd(int a, int b)const
{	//辗转相除法,返回最大公约数
	int r;
	while (b) {
		r = b;
		b = a % b;
		a = r;
	}
	return (a == 0 ? 1 : a);	
}
void Fraction::Output() {
	cout << p << "/" << q<<endl;
}
Fraction& Fraction::operator++() {		//前自增
	p++;
	int g = gcd(p, q);
	p /= g;
	q /= g;
	return *this;			//返回当前对象的引用
}
Fraction Fraction::operator++(int) {	//后自增
	Fraction temp(*this);	//用默认拷贝构造函数,把当前对象保存在temp里
	p++;
	int g = gcd(p, q);
	p /= g;
	q /= g;
	return temp;
}

Fraction operator+(const Fraction& F1, const Fraction& F2) {
	Fraction temp(0, 0);
	temp.q = F1.q * F2.q;
	temp.p = F1.q * F2.p + F2.q * F1.p;
	int g = temp.gcd(temp.p, temp.q);
	temp.p /= g;
	temp.q /= g;
	return temp;
}
Fraction operator-(const Fraction& F1, const Fraction& F2) {
	Fraction temp(F2);
	temp.p *= -1;			//temp取F2的负数
	return (F1 + temp);		//复用了对+的重载,减少代码量
}
Fraction operator*(const Fraction& F1, const Fraction& F2) {
	Fraction temp(1, 1);
	temp.p = F1.p * F2.p;
	temp.q = F1.q * F2.q;
	int g = temp.gcd(temp.p, temp.q);
	temp.p /= g;
	temp.q /= g;
	return temp;
}
Fraction operator/(const Fraction& F1, const Fraction& F2) {
	Fraction temp(F2);
	int t = temp.p;
	temp.p = temp.q;
	temp.q = t;		//颠倒F2的分子分母用来
	return F1 * temp;	//复用*运算符重载,减少代码量
}

 文件结构如下:

 

尾声:

介绍一个小技巧!VS编译环境下,Ctrl+ M+O可以一键折叠所有的代码块。Ctrl+M+L可以一键打开所有的代码块。这个快捷键在代码维护的时候非常的贴心。

最后附上运算符重载表:

 其中,=、[ ]、( )、->这四个必须是非静态成员函数方式的重载。

  • 22
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值