C++_类的简单构造案例

这篇文章主要是介绍用 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++ 中拷贝构造函数的调用时机:

  1. 使用一个已经创建完毕的对象初始化一个新对象。
  2. 值传递的方式给函数参数形参。
  3. 以值方式返回局部对象。

拷贝赋值运算符

在 C++ 中,运算符本质上也是函数,重载运算符就是在重新定义函数,而拷贝赋值运算符就是重载赋值运算符。

1
2
3
4
5
Fraction::Fraction& operator=(const Fraction &fra) {
numerator = fra.numerator;
denominator = fra.denominator;
return *this;
}

注意这里的细节:

  1. 为了实现a = b = c;这样的效果(也就是链式编程法则),需要返回自身的引用。
  2. 形参的类型是常量引用,一方面是避免拷贝和修改形参,另一方面无论形参的类型是常量还是非常量都是可以使用的。
  3. 返回自身是通过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 接收计算结果,如果直接写成 cout << a + b << endl;
会存在与 << 的优先级问题,从而得不到正确的结果。当然,也可以写成 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;
}

Buy me a coffee ? :)
0%