背景题目:
定义有理数类(分母不为0的分数,分子分母均为整数)Rational,实现相应操作符的重
载。
(1)定义私有数据成员:分子int iUp; 分母 int iDown。
(2)定义私有成员函数:void Reduce() 和 int Gcd(int l, int r),分别用于有理数的
约简和求两个整数的最大公约数。其中,在约简时需要求取分子与分母的最大公约数。
(3)定义构造函数,在构造函数体内可调用Reduce对有理数进行约简。
(4)将负号-和赋值运算符=重载为公有成员函数,分别用于求有理数的负数和赋值。
(5)将前置++、前置–、后置++、后置–重载为公有成员函数,实现有理数自增1或自减
1。
(6)将+、-、*、/重载为友员函数,实现有理数的加减乘除。
(7)将<、<=、>、>=重载为友员函数,实现有理数的大小关系比较。
(8)重载流插入符<<和流提取符>>,分别用于有理数的输出和输入。其中,输出格式为
“分子/分母”,若为整数,则直接输出整数。
为便于说明,此处只给出部分代码:
#include<iostream>
using namespace std;
class Rational
{
private:
int iUp;
int iDown;
void Reduce()
{
...
}
int Gcd(int l, int r)
{
...
}
public:
Rational(){}
Rational(int u, int d)
{
...
}
Rational& operator=(Rational& r)
{
iUp=r.iUp;
iDown = r.iDown;
Reduce();
return *this;
}
Rational operator-()
{
Rational temp;
temp.iUp = -iUp;
temp.iDown = iDown;
return temp;
}
Rational& operator++()
{
iUp += iDown;
Reduce();
return *this;
}
Rational operator++(int)
{
Rational temp = *this;
iUp += iDown;
Reduce();
return temp;
}
Rational& operator--()
{
iUp -= iDown;
Reduce();
return *this;
}
Rational operator--(int)
{
Rational temp = *this;
iUp -= iDown;
Reduce();
return temp;
}
friend Rational operator+(const Rational& a, const Rational& b);
friend Rational operator-(const Rational& a, const Rational& b);
friend Rational operator*(const Rational& a, const Rational& b);
friend Rational operator/(const Rational& a, const Rational& b);
friend bool operator<(const Rational& a, const Rational& b);
friend bool operator<=(const Rational& a, const Rational& b);
friend bool operator>(const Rational& a, const Rational& b);
friend bool operator>=(const Rational& a, const Rational& b);
friend istream& operator>>(istream& in, Rational& r);
friend ostream& operator<<(ostream& out, const Rational& r);
};
Rational operator+(const Rational& a, const Rational& b)
{
Rational temp;
temp.iUp = a.iUp * b.iDown + b.iUp * a.iDown;
temp.iDown = a.iDown * b.iDown;
temp.Reduce();
return temp;
}
Rational operator-(const Rational& a, const Rational& b)
{
Rational temp;
temp.iUp = a.iUp * b.iDown - b.iUp * a.iDown;
temp.iDown = a.iDown * b.iDown;
temp.Reduce();
return temp;
}
Rational operator*(const Rational& a, const Rational& b)
{
Rational temp;
temp.iUp = a.iUp * b.iUp;
temp.iDown = a.iDown * b.iDown;
temp.Reduce();
return temp;
}
Rational operator/(const Rational& a, const Rational& b)
{
Rational temp;
temp.iUp = a.iUp * b.iDown;
temp.iDown = a.iDown * b.iUp;
temp.Reduce();
return temp;
}
bool operator<(const Rational& a, const Rational& b)
{
...
}
bool operator<=(const Rational& a, const Rational& b)
{
...
}
bool operator>(const Rational& a, const Rational& b)
{
...
}
bool operator>=(const Rational& a, const Rational& b)
{
...
}
istream& operator>>(istream& in, Rational& r)
{
...
}
ostream& operator<<(ostream& out, const Rational& r)
{
...
}
int main()
{
int up, down;
cin >> up >> down;
Rational a(up, down);
cin >> up >> down;
Rational b(up, down);
Rational c;
c = a + b;
cout << "a+b: " << c;
c = a - b;
cout << "a-b: " << c;
c = a * b;
cout << "a*b: " << c;
c = a / b;
cout << "a/b: " << c;
c = -a;
cout << "-a: " << c;
c = ++a;
cout << "++a: " << c;
c = --a;
cout << "--a: " << c;
c = a++;
cout << "a++: " << c;
c = a--;
cout << "a--: " << c;
...
return 0;
}
欲运行代码,结果报错:
可以看到等号与+、-、*、/、后置自增、后置自减连用的地方,都标红,报错信息为“error C2679: 二进制“=”: 没有找到接受’Rational’类型的右操作数的运算符(或没有可接受的转换)”。
可是赋值运算符已经重载了,为什么这里会没有起作用呢?经过查询,我发现,问题出在重载等号传入的参数和其他运算符的返回值类型上。
源码中重载等号的参数类型是Rational&,是一个可变引用
Rational& operator=(Rational& r)
{
iUp=r.iUp;
iDown = r.iDown;
Reduce();
return *this;
}
重载+、-、*、/、后置自增、后置自减的返回参数均为Rational,是一个临时局部变量。
Rational operator+(const Rational& a, const Rational& b)
{
Rational temp;
temp.iUp = a.iUp * b.iDown + b.iUp * a.iDown;
temp.iDown = a.iDown * b.iDown;
temp.Reduce();
return temp;
在C++里,临时局部变量没有合法内存空间,是不可以转为可变引用的,因为可变引用需要有合法的内存空间。但是常量引用可以接收这一类没有有效内存空间的值,即const Rational&。
于是我将重载等号代码修改,
Rational& operator=(const Rational& r)
{
iUp=r.iUp;
iDown = r.iDown;
Reduce();
return *this;
}
问题就解决了。
接下来讲一下常量引用的原理,以整型常量引用为例:
int &a=10;//会报错,10没有申请合法内存空间
const int &a=10;//不会报错
//编译器会自动将代码优化为int temp=10;
//const int &a=temp;
总结:
当不涉及修改数据操作时,传递参数最好用常量引用的形式,即const type&,因为这个方式是适应性最高的,可以接收有合法内存空间的变量或常量,也可以接收没有事先申请合法内存空间的临时(局部)变量或常量,还可以保护数据,防止误操作。