这篇文章主要是介绍用 C++ 实现的一个简单数学类——分数类,其中包含了对应的基本运算规则。
前言
学习完 C++ 关于类的部分后,很想自己动手写一写,所以这里尝试自己用 C++ 的语言写一个简单的类。
类的定义
首先完成类的定义,先考虑分数的组成——分子和分母,这里为了数据处理时方便,简单认为分子和分母都是有符号整数,数据类型就用int
。1
2
3
4
5class Fraction {
private:
int numerator;
int denominator;
};
类的功能
设计好类的定义后,接下来要考虑的就是类的功能函数了。按照三五法则,必须要为这个类提供拷贝构造函数、拷贝复制运算符和析构函数。同时,因为要求包含基本运算规则,所以必须要完成对应四个运算符的重载等功能。
构造函数
这里提供一个有参构造函数,注意在 C++ 中,如果提供了构造函数,编译器就不再提供默认构造函数了,但可以通过default
关键字继续使用由编译器提供的默认构造函数。
此时Fraction.h
文件的内容为:1
2
3
4
5
6
7
8
9class Fraction {
public:
Fraction() = default;
Fraction(int up, int down);
~Fraction() = default;
private:
int numerator;
int denominator;
};
类的默认构造函数使用编译器自动生成,有参构造函数的定义放在Fraction.cpp
文件中:1
Fraction::Fraction(int up, int down): numerator(up), denominator(down) {}
有参构造函数直接使用constructor initialize list
来完成定义。
析构函数
因为数据结构很简单,直接使用编译器默认生成的析构函数,直接添加到Fraction.h
文件中即可。1
2
3
4
5
6
7
8
9class Fraction {
public:
Fraction() = default;
Fraction(int up, int down);
~Fraction() = default;
private:
int numerator;
int denominator;
};
拷贝构造函数
顾名思义,拷贝构造函数的功能就是拷贝原来的对象,并按照原来的对象构造一个新对象,所以拷贝构造函数的定义可以写成下面这种形式:1
2
3
4Fraction::Fraction(const Fraction &f) {
numerator = f.numerator;
denominator = f.denominator;
}
注意这里使用了const
和&
,使用const
是因为不对原对象做修改,而使用&
是因为拷贝构造函数还有定义出来,如果不用&
,编译器会默认调用这个对象的拷贝构造函数来复制这个形参。
C++ 中拷贝构造函数的调用时机:
- 使用一个已经创建完毕的对象初始化一个新对象。
- 值传递的方式给函数参数形参。
- 以值方式返回局部对象。
拷贝赋值运算符
在 C++ 中,运算符本质上也是函数,重载运算符就是在重新定义函数,而拷贝赋值运算符就是重载赋值运算符。1
2
3
4
5Fraction::Fraction& operator=(const Fraction &fra) {
numerator = fra.numerator;
denominator = fra.denominator;
return *this;
}
注意这里的细节:
- 为了实现
a = b = c;
这样的效果(也就是链式编程法则),需要返回自身的引用。 - 形参的类型是常量引用,一方面是避免拷贝和修改形参,另一方面无论形参的类型是常量还是非常量都是可以使用的。
- 返回自身是通过
this
指针完成的。
重载 << 运算符
为了便于输出,需要先重载<<
运算符。重载<<
运算符时,可以用成员函数的形式重载,也可以用全局函数的形式重载。利用成员函数重载<<
运算符,最后得到的效果是f << cout;
,但我们想要的效果是cout << f;
,而后一种结果可以通过全局函数的形式重载得到。
由于前面将类的数据成员定义为私有属性,而全局函数为了能访问到类的私有成员,需要将该全局函数声明为类的友元函数,所以需要在fraction.h
头文件中,加入友元函数声明:1
friend std::ostream& operator<<(std::ostream &out, Fraction &f);
将该函数的定义放到test.cpp
中:1
2
3
4ostream& operator<<(ostream &out, Fraction &f) {
out << f.numerator << '/' << f.denominator;
return out;
}
注意,这里假定输出分数的形式类似于1/2
。
定义好后面的计算运算符后,发现可能存在分数为 0、负、假分数和整数的情况,这时输出格式会有一些变化:
- 若为负分数,先输出负号,在输出分数,如
-1/2
- 若为 0,直接输出
0
- 若为假分数,先输出整数,在输出分数,如
3 1/2
表示三又二分之一 - 若为整数,直接输出整数值,如
2/2
,输出1
1 | ostream& operator<<(ostream &out, Fraction &f) { |
重载 >> 运算符
同理<<
运算符的思路,假定输入两个整数分别代表分子和分母。1
friend std::istream& operator>>(std::istream &in, Fraction &f);
1 | istream& operator>>(istream &in, Fraction &f) { |
重载 == 运算符
为了方便比较两个函数相等,顺便把==
运算符也重载了,判断逻辑也很简单,默认所有的分数都是最简形式的,只要分子分母同时相等就是相等的分数。1
2
3
4
5bool operator==(const Fraction &fra) {
if(numerator == fra.numerator && denominator == fra.denominator)
return true;
else return false;
}
==
运算符重载完成后,可以直接用来重载!=
运算符。
功能函数
在设计分数的运算规则之前,需要先准备好一些功能函数来便于计算。
最小公约数
这个函数定义的声明放在function.h
文件中,定义放在对应的function.cpp
中,使用辗转相除法的思路计算最大公约数。1
2
3
4long long gcd(int a, int b) {
if(b == 0) return a;
else return gcd(b, a % b);
}
绝对值函数
这个函数定义的声明放在function.h
文件中,定义放在对应的function.cpp
中,功能是返回int
型变量的绝对值。1
2
3int abs(int a) {
return a > 0 ? a : -a;
}
化简函数
当分母与分子可以约分时,要化为最简分数。考虑到特殊情况,如果分子为 0,就让分母为 1。1
2
3
4
5
6
7
8
9
10
11
12
13void Fraction::reduction() {
if(denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
if(numerator == 0) {
denominator = 1;
} else {
int d = gcd(abs(numerator), abs(denominator));
numerator /= d;
denominator /= d;
}
}
重载 + 运算符
分数的加法有点麻烦,需要先通分,计算后,再约分。不过,我们已经把化简的过程独立出来了,所以直接计算,然后再调用化简函数即可。1
2
3
4
5
6
7Fraction Fraction::operator+(const Fraction &fra) {
Fraction res;
res.numerator = numerator * fra.denominator + fra.numerator * denominator;
res.denominator = denominator * fra.denominator;
res.reduction();
return res;
}
接下来,如法炮制其他运算符。
重载 - 运算符
1 | Fraction Fraction::operator-(const Fraction &fra) { |
重载 * 运算符
1 | Fraction Fraction::operator*(const Fraction &fra) { |
重载 / 运算符
1 | Fraction Fraction::operator/(const Fraction &fra) { |
总结
尽管是个简单的案例,但其中涉及了很多 C++ 的基础知识点。
最后贴出所有文件(其实一共也就五个文件😂)的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Fraction {
public:
friend std::ostream& operator<<(std::ostream &out, Fraction &f);
friend std::istream& operator>>(std::istream &in, Fraction &f);
Fraction() = default;
Fraction(int up, int down);
Fraction(const Fraction &fra);
Fraction operator=(const Fraction &fra);
bool operator==(const Fraction &fra);
Fraction operator+(const Fraction &fra);
Fraction operator-(const Fraction &fra);
Fraction operator*(const Fraction &fra);
Fraction operator/(const Fraction &fra);
~Fraction() = default;
private:
int numerator;
int denominator;
void reduction();
};
1 |
|
1 |
|
1 | long long gcd(int a, int b) { |
1 |
|