这篇文章主要是介绍用 C++ 实现的一个简单数学类——分数类,其中包含了对应的基本运算规则。
前言 学习完 C++ 关于类的部分后,很想自己动手写一写,所以这里尝试自己用 C++ 的语言写一个简单的类。
类的定义 首先完成类的定义,先考虑分数的组成——分子和分母,这里为了数据处理时方便,简单认为分子和分母都是有符号整数,数据类型就用int。
1 2 3 4 5 class Fraction {private : int numerator; int denominator; };
类的功能 设计好类的定义后,接下来要考虑的就是类的功能函数了。按照三五法则,必须要为这个类提供拷贝构造函数、拷贝复制运算符和析构函数。同时,因为要求包含基本运算规则,所以必须要完成对应四个运算符的重载等功能。
构造函数 这里提供一个有参构造函数,注意在 C++ 中,如果提供了构造函数,编译器就不再提供默认构造函数 了,但可以通过default关键字继续使用由编译器提供的默认构造函数。 此时Fraction.h文件的内容为:
1 2 3 4 5 6 7 8 9 class 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 9 class Fraction {public : Fraction () = default ; Fraction (int up, int down); ~Fraction () = default ; private : int numerator; int denominator; };
拷贝构造函数 顾名思义,拷贝构造函数的功能就是拷贝原来的对象,并按照原来的对象构造一个新对象,所以拷贝构造函数的定义可以写成下面这种形式:
1 2 3 4 Fraction::Fraction (const Fraction &f) { numerator = f.numerator; denominator = f.denominator; }
注意这里使用了const和&,使用const是因为不对原对象做修改,而使用&是因为拷贝构造函数还有定义出来,如果不用&,编译器会默认调用这个对象的拷贝构造函数来复制这个形参。
C++ 中拷贝构造函数的调用时机:
使用一个已经创建完毕的对象初始化一个新对象。 值传递的方式给函数参数形参。 以值方式返回局部对象。
拷贝赋值运算符 在 C++ 中,运算符本质上也是函数,重载运算符就是在重新定义函数,而拷贝赋值运算符就是重载赋值运算符。
1 2 3 4 5 Fraction::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 4 ostream& 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 2 3 4 5 6 7 8 9 10 ostream& operator <<(ostream &out, Fraction &f) { f.reduction (); if (f.denominator == 1 ) cout << f.numerator; else if (abs (f.numerator) > f.denominator) cout << f.numerator / f.denominator << f.numerator % f.denominator << '/' << f.denominator; else cout << f.numerator << '/' << f.denominator; }
重载 >> 运算符 同理<<运算符的思路,假定输入两个整数分别代表分子和分母。
1 friend std::istream& operator >>(std::istream &in, Fraction &f);
1 2 3 4 istream& operator >>(istream &in, Fraction &f) { in >> f.numerator >> f.denominator; return in; }
重载 == 运算符 为了方便比较两个函数相等,顺便把==运算符也重载了,判断逻辑也很简单,默认所有的分数都是最简形式的,只要分子分母同时相等就是相等的分数。
1 2 3 4 5 bool operator ==(const Fraction &fra) { if (numerator == fra.numerator && denominator == fra.denominator) return true ; else return false ; }
==运算符重载完成后,可以直接用来重载!=运算符。
功能函数 在设计分数的运算规则之前,需要先准备好一些功能函数来便于计算。
最小公约数 这个函数定义的声明放在function.h文件中,定义放在对应的function.cpp中,使用辗转相除法 的思路计算最大公约数。
1 2 3 4 long long gcd (int a, int b) { if (b == 0 ) return a; else return gcd (b, a % b); }
绝对值函数 这个函数定义的声明放在function.h文件中,定义放在对应的function.cpp中,功能是返回int型变量的绝对值。
1 2 3 int abs (int a) { return a > 0 ? a : -a; }
化简函数 当分母与分子可以约分时,要化为最简分数。考虑到特殊情况,如果分子为 0,就让分母为 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 void 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 7 Fraction 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 2 3 4 5 6 7 Fraction 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 2 3 4 5 6 7 Fraction Fraction::operator *(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.numerator; res.denominator = denominator * fra.denominator; res.reduction (); return res; }
重载 / 运算符 1 2 3 4 5 6 7 Fraction Fraction::operator /(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.denominator; res.denominator = denominator * fra.numerator; res.reduction (); return res; }
总结 尽管是个简单的案例,但其中涉及了很多 C++ 的基础知识点。 最后贴出所有文件(其实一共也就五个文件😂)的代码:
fraction.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #pragma once #include <iostream> 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 () ; };
fraction.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include "fraction.h" #include "function.h" Fraction::Fraction (int up, int down): numerator (up), denominator (down){} Fraction::Fraction (const Fraction &f) { numerator = f.numerator; denominator = f.denominator; } Fraction Fraction::operator =(const Fraction &fra) { numerator = fra.numerator; denominator = fra.denominator; return *this ; } bool Fraction::operator ==(const Fraction &fra) { if (numerator == fra.numerator && denominator == fra.denominator) return true ; else return false ; } void 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; } } Fraction Fraction::operator +(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.denominator + fra.numerator * denominator; res.denominator = denominator * fra.denominator; res.reduction (); return res; } Fraction Fraction::operator -(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.denominator - fra.numerator * denominator; res.denominator = denominator * fra.denominator; res.reduction (); return res; } Fraction Fraction::operator *(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.numerator; res.denominator = denominator * fra.denominator; res.reduction (); return res; } Fraction Fraction::operator /(const Fraction &fra) { Fraction res; res.numerator = numerator * fra.denominator; res.denominator = denominator * fra.numerator; res.reduction (); return res; }
function.h 1 2 3 4 #pragma once long long gcd (int a, int b) ;int abs (int a) ;
function.cpp 1 2 3 4 5 6 7 8 long long gcd (int a, int b) { if (b == 0 ) return a; else return gcd (b, a % b); } int abs (int a) { return a > 0 ? a : -a; }
test.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> #include "fraction.h" using namespace std;ostream& operator <<(ostream &out, Fraction &f) { f.reduction (); if (f.denominator == 1 ) cout << f.numerator; else if (abs (f.numerator) > f.denominator) cout << f.numerator / f.denominator << ' ' << f.numerator % f.denominator << '/' << f.denominator; else cout << f.numerator << '/' << f.denominator; return out; } istream& operator >>(istream &in, Fraction &f) { in >> f.numerator >> f.denominator; return in; } int main () { Fraction a, b, res; cin >> a >> b; cout << a << endl; cout << b << endl; if (a == b) cout << "a = b" << endl; else cout << "a != b" << endl; res = a + b; cout << res << endl; res = a - b; cout << res << endl; res = a * b; cout << res << endl; res = a / b; cout << res << endl; return 0 ; }