在同一作用域中的某个函数和运算符,指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当调用一个重载函数或重载运算符时,编译器通过把 ”所使用的参数类型“ 与 ”定义中的参数类型“ 进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但它们的形式参数(参数的个数、类型或顺序)必须不同。不能仅通过返回类型的不同来重载函数。
下面的实例中,同名函数 print() 被用于输出不同的数据类型:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i)
{
cout << "整数为: " << i << endl;
}
/* 重载声明,具有相同名称print,但参数列表和定义(实现)不相同 */
void print(double f)
{
cout << "浮点数为: " << f << endl;
}
void print(char* c)
{
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
pd.print(5); // 输出整数
pd.print(500.263); // 输出浮点数
char c[] = "Hello C++";
pd.print(c); // 输出字符串
return 0;
}
编译和执行上面的代码:
整数为: 5
浮点数为: 500.263
字符串为: Hello C++
运算符重载
重定义或重载大部分 C++ 内置的运算符,便能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名由关键字 operator 和其后要重载的运算符符号构成。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator+(const Box&);
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或类成员函数。
如果定义上面的函数为类的非成员函数,那么需要为每次操作传递两个参数,这两个参数分别代表运算符的左操作数(第一个参数)和右操作数(第二个参数)。如下所示:
Box operator+(const Box&,const Box&);
下面的实例使用普通的非成员函数重载运算符(friend Box operator*(const Box& b1, const Box& b2))和成员函数(Box operator+(const Box& b))演示了运算符重载的概念。在这里,对象作为参数进行传递,对象的属性使用 this 运算符进行访问,如下所示:
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength(double len)
{
length = len;
}
void setBreadth(double bre)
{
breadth = bre;
}
void setHeight(double hei)
{
height = hei;
}
// 成员函数重载 + 运算符:把两个 Box 对象的长宽高分别相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
// 友元函数声明:非成员函数重载 * 运算符
friend Box operator*(const Box& b1, const Box& b2);
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 友元函数定义:在类外定义普通非成员函数重载 * 运算符
// 通过友元函数访问 Box 的私有成员
// 因为它是非成员函数,而不是 Box 的成员函数。所以不需要 Box:: 前缀和作用域Box::
Box operator*(const Box& b1, const Box& b2)
{
Box box;
// 将 Box1 和 Box2 的长宽高分别相乘
box.length = b1.length * b2.length;
box.breadth = b1.breadth * b2.breadth;
box.height = b1.height * b2.height;
return box;
}
int main()
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 用来接收返回的 Box
Box Box4; // 用来接收返回的 Box
double volume = 0.0; // 存储体积变量
// Box1
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2
Box2.setLength(2.0);
Box2.setBreadth(3.0);
Box2.setHeight(4.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume << endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume << endl;
// 成员函数重载 + 运算符
Box3 = Box1 + Box2;
volume = Box3.getVolume();
cout << "Volume of Box3 (Box1 + Box2) : " << volume << endl;
// 非成员函数重载 * 运算符
Box4 = Box1 * Box2;
volume = Box4.getVolume();
cout << "Volume of Box4 (Box1 * Box2) : " << volume << endl;
return 0;
}
编译和执行上面的代码:
Volume of Box1 : 210
Volume of Box2 : 24
Volume of Box3 (Box1 + Box2) : 720
Volume of Box4 (Box1 * Box2) : 5040
可重载运算符&不可重载运算符
可重载运算符:
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),--(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
不可重载运算符:
成员访问运算符 | . |
成员指针访问运算符 | .* , ->* |
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
运算符重载实例
各种运算符重载的实例:
一元运算符重载
一元运算符只对一个操作数进行操作,下面是一元运算符的实例:
- 递增运算符( ++ )和递减运算符( -- )
- 一元减运算符( - )
- 逻辑非运算符( ! )
一元运算符通常出现在所操作的对象的左边,比如 !obj、-obj 和 ++obj,有时也作后缀,比如 obj++ 或 obj--。
演示如何重载一元减运算符( - ):
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到无穷
int inches; // 0 到 12
public:
Distance() // 所需的构造函数
{
feet = 0;
inches = 0;
}
Distance(int f, int i)
{
feet = f;
inches = i;
}
void displayDistance() // 显示距离的方法
{
cout << "FEET: " << feet << " INCHES:" << inches <<endl;
}
Distance operator- () // 重载负运算符( - )
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
};
int main()
{
Distance D1(11, 10), D2(-5, 11);
-D1; // 取相反数
D1.displayDistance(); // 距离 D1
-D2; // 取相反数
D2.displayDistance(); // 距离 D2
return 0;
}
演示如何重载递增运算符( ++ ),包括前缀和后缀两种用法(后缀形式中的 int 只是用来区分前缀和后缀):
- 前缀形式: operator ++ ( )
- 后缀形式: operator ++ ( int )
#include <iostream>
using namespace std;
// 定义 Time 类:用于表示时间(小时:分钟)
class Time
{
private:
int hours; // 小时,范围 0 到 23
int minutes; // 分钟,范围 0 到 59
public:
// 无参构造函数,初始化时间为 0:0
Time()
{
hours = 0;
minutes = 0;
}
// 带参构造函数,初始化为指定的小时和分钟
Time(int h, int m)
{
hours = h;
minutes = m;
}
// 成员函数:显示当前时间
void displayTime()
{
cout << "H: " << hours << " M: " << minutes << endl;
}
// 重载前缀递增运算符:++T1
Time operator++ ()
{
++minutes; // 先让分钟加 1
// 检查分钟是否溢出
if(minutes >= 60)
{
++hours; // 小时加 1
minutes -= 60; // 分钟减去 60,回到合法范围
}
// 返回新的时间对象(这里返回值是新的对象)
return Time(hours, minutes);
}
// 重载后缀递增运算符:T1++
// int 参数是哑元参数,用于区分前缀和后缀
Time operator++(int)
{
// 保存原始值
Time T(hours, minutes);
// 分钟加 1
++minutes;
if(minutes >= 60)
{
++hours;
minutes -= 60;
}
// 返回原始值(后缀递增返回的是递增前的旧值)
return T;
}
};
int main()
{
// 初始化两个时间对象
Time T1(11, 59); // 11:59
Time T2(10, 40); // 10:40
// 测试前缀递增运算符 ++T1
++T1; // T1:11:59 -> 12:00
T1.displayTime(); // 显示 T1:12:0
++T1; // T1:12:00 -> 12:01
T1.displayTime(); // 显示 T1:12:1
// 测试后缀递增运算符 T2++
T2++; // T2:10:40 -> 10:41,返回旧值 10:40
T2.displayTime(); // 显示 T2:10:41
T2++; // T2:10:41 -> 10:42,返回旧值 10:41
T2.displayTime(); // 显示 T2:10:42
return 0;
}
执行输出结果为:
H: 12 M:0
H: 12 M:1
H: 10 M:41
H: 10 M:42
注意:int 在括号内是为了向编译器说明这是一个后缀形式,这个形参是0,作为前置和后置的区分,而不是表示整数。递增和递减一般是改变对象的状态,所以一般是重载为成员函数。且重载递增递减,一定要和指针的递增递减区分开。因为这里的重载操作的是对象,而不是指针(由于指针是内置类型,指针的递增递减是无法重载的),所以一般情况的递增递减是操作对象内部的成员变量。
若想要实现 ++(++a) 这种连续自加,需返回其对象的引用,这样才能保证操作的是同一块内存空间或对象,否则就只是单纯的赋值操作,原来的对象并未被修改,如下所示:
Time& operator++ () // 重载前缀递增运算符( ++ )
{
++minutes; // 对象加 1
if(minutes >= 60)
{
++hours;
minutes -= 60;
}
return *this;
}
二元运算符重载
二元运算符需要两个参数,加运算符( + )、减运算符( - )、乘运算符( * )和除运算符( / )都属于二元运算符。
这里演示重载加运算符( + ),其他的类似:
#include<iostream>
using namespace std;
// 定义一个 Box 类,演示多种 + 运算符重载
class Box
{
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
public:
// 构造函数:无参构造
Box();
// 构造函数:带参构造
Box(double l,double b,double h);
// 成员函数重载 + 运算符:Box + Box
Box operator+(const Box & box1);
// 成员函数重载 + 运算符:Box + int
Box operator+(const int b);
// 友元函数重载 + 运算符:int + Box
friend Box operator+(const int b, Box box1);
// 显示 Box 的属性
void display();
};
// 无参构造函数
Box::Box()
{
length = 0;
breadth = 0;
height = 0;
}
// 带参构造函数
Box::Box(double l, double b, double h)
{
length = l;
breadth = b;
height = h;
}
// 成员函数重载 + 运算符:用于对象相加 (Box1 + Box2)
Box Box::operator+(const Box& box1)
{
Box box;
// 对应的长、宽、高分别相加
box.length = this->length + box1.length;
box.breadth = this->breadth + box1.breadth;
box.height = this->height + box1.height;
return box;
}
// 成员函数重载 + 运算符:用于对象与整数相加 (Box1 + 6)
Box Box::operator+(const int b)
{
Box box;
// 将整数 b 分别加到长、宽、高
box.length = this->length + b;
box.breadth = this->breadth + b;
box.height = this->height + b;
return box;
}
// 友元函数重载 + 运算符:用于整数与对象相加 (6 + Box1)
// 不能写成成员函数,因为左操作数是 int
Box operator+(const int b, Box box1)
{
// 这里调用 Box 类中定义的成员函数重载 (Box1 + b)
return box1 + b;
}
// 显示 Box 的长、宽、高
void Box::display()
{
cout << "length: " << length << endl;
cout << "breadth: " << breadth << endl;
cout << "height: " << height << endl;
}
int main ()
{
int m = 1;
// 创建两个 Box 对象
Box box1(1.4, 2.7, 3.9);
Box box2(2.1, 3.5, 7.3);
// 定义接收结果的 Box 对象
Box box3, box4, box5;
// 显示 box1 和 box2 的属性
box1.display();
box2.display();
// 这里调用成员函数重载的 + 运算符 (Box + Box)
// 等价于 box3 = box1.operator+(box2);
box3 = box1 + box2;
box3.display();
// 调用成员函数重载的 + 运算符 (Box + int)
// 这里 m 是 int,box1 是 Box,调用 Box::operator+(int)
box4 = box1 + m;
box4.display();
// 调用友元函数重载的 + 运算符 (int + Box)
// 因为左操作数是 int,只有非成员友元函数才能实现
box5 = m + box1;
box5.display();
return 0;
}
执行输出结果为:
length: 1.4
breadth: 2.7
height: 3.9length: 2.1
breadth: 3.5
height: 7.3length: 3.5
breadth: 6.2
height: 11.2length: 2.4
breadth: 3.7
height: 4.9length: 2.4
breadth: 3.7
height: 4.9
关系运算符重载
C++ 语言支持各种关系运算符( < 、 > 、 <= 、 >= 、 == 等等),它们可用于比较 C++ 内置的数据类型。可以重载任何一个关系运算符,重载后的关系运算符可用于比较类的对象。
下面演示了重载 < 运算符,其他的关系运算符类似:
#include <iostream>
using namespace std;
// 定义 Distance 类,表示距离(英尺:英寸)
class Distance
{
private:
int feet; // 英尺数(0 ~ ∞)
int inches; // 英寸数(0 ~ 12)
public:
// 无参构造函数,初始化距离为 0 英尺 0 英寸
Distance()
{
feet = 0;
inches = 0;
}
// 带参构造函数,初始化为指定的英尺和英寸
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 显示距离
void displayDistance()
{
cout << "F: " << feet << " I: " << inches << endl;
}
// 重载小于运算符:用于比较两个 Distance 对象的大小
bool operator <(const Distance& d)
{
// 首先比较英尺
if(feet < d.feet)
{
return true; // 如果当前对象英尺更小,返回 true
}
// 如果英尺相等,则比较英寸
if(feet == d.feet && inches < d.inches)
{
return true; // 如果英尺相等,且当前对象英寸更小,返回 true
}
// 其他情况都返回 false(当前对象不比 d 小)
return false;
}
};
int main()
{
// 定义两个距离对象
Distance D1(11, 10); // 11 英尺 10 英寸
Distance D2(5, 11); // 5 英尺 11 英寸
// 使用重载的小于运算符比较 D1 和 D2
if( D1 < D2 )
{
cout << "D1 is less than D2" << endl;
}
else
{
cout << "D2 is less than D1" << endl;
}
return 0;
}
执行输出结果为:
D2 is less than D1
输入/输出运算符重载
C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型,也可重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型。
注意:需要把运算符重载函数声明为类的友元函数,这样就能不用创建对象而直接调用函数。
演示重载提取运算符 >> 和插入运算符 <<:
#include <iostream>
using namespace std;
// 定义 Distance 类,表示距离(英尺:英寸)
class Distance
{
private:
int feet; // 英尺
int inches; // 英寸
public:
// 无参构造函数,初始化为 0 英尺 0 英寸
Distance()
{
feet = 0;
inches = 0;
}
// 带参构造函数,初始化为指定的英尺和英寸
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 友元函数:重载 << 运算符,用于输出 Distance 对象
friend ostream& operator<<(ostream& output, const Distance& D)
{
output << "FEET : " << D.feet << " INCHES : " << D.inches;
return output;
}
// 友元函数:重载 >> 运算符,用于输入 Distance 对象
friend istream& operator>>(istream& input, Distance& D)
{
input >> D.feet >> D.inches;
return input;
}
};
int main()
{
// 定义三个距离对象
Distance D1(11, 10); // 第一个对象:11 英尺 10 英寸
Distance D2(5, 11); // 第二个对象:5 英尺 11 英寸
Distance D3; // 第三个对象:用于接收用户输入
cout << "Enter the value of object (feet inches): " << endl;
// 从标准输入读取 D3 对象
cin >> D3;
// 输出三个对象
cout << "First Distance : " << D1 << endl;
cout << "Second Distance : " << D2 << endl;
cout << "Third Distance : " << D3 << endl;
return 0;
}
执行输出结果为:
Enter the value of object (feet inches):
5 13
First Distance : FEET : 11 INCHES : 10
Second Distance : FEET : 5 INCHES : 11
Third Distance : FEET : 5 INCHES : 13
赋值运算符重载
可以重载赋值运算符( = ),用于创建一个对象,比如拷贝构造函数。
演示了如何重载赋值运算符:
#include <iostream>
using namespace std;
// 定义一个 Distance 类
class Distance
{
private:
int feet; // 英尺部分
int inches; // 英寸部分
public:
// 无参构造函数,将 feet 和 inches 初始化为 0
Distance()
{
feet = 0;
inches = 0;
}
// 有参构造函数,使用传入的参数初始化
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 重载赋值运算符(operator=),用于对象之间的赋值
void operator=(const Distance& D)
{
feet = D.feet;
inches = D.inches;
}
// 成员函数:显示距离信息
void displayDistance()
{
cout << "FEET: " << feet << " INCHES: " << inches << endl;
}
};
int main()
{
// 创建两个 Distance 对象,分别初始化
Distance D1(5, 6);
Distance D2(4, 3);
cout << "First Distance : ";
D1.displayDistance();
cout << "Second Distance : ";
D2.displayDistance();
// 使用重载的赋值运算符将 D2 的值赋给 D1
D1 = D2;
cout << "After assignment, First Distance : ";
D1.displayDistance();
return 0;
}
执行输出结果为:
First Distance : FEET: 5 INCHES:6
Second Distance :FEET: 4 INCHES:3
First Distance :FEET: 4 INCHES:3
函数调用运算符 () 重载
函数调用运算符 () 可以被重载用于类的对象。重载 () 并非是创造了一种新的调用函数的方式,相反,这是创建一个可以传递任意数目参数的运算符函数。
演示了如何重载函数调用运算符 ():
#include <iostream>
using namespace std;
// 定义一个 Distance 类
class Distance
{
private:
int feet; // 英尺部分
int inches; // 英寸部分
public:
// 无参构造函数:将 feet 和 inches 初始化为 0
Distance()
{
feet = 0;
inches = 0;
}
// 有参构造函数:使用传入的参数初始化
Distance(int f, int i)
{
feet = f;
inches = i;
}
// 重载函数调用运算符 operator(),可让对象像“函数”一样使用
Distance operator()(int a, int b, int c)
{
Distance D;
// 这里进行一些演示性的计算
D.feet = a + c + 10;
D.inches = b + c + 100;
return D;
}
// 显示距离信息
void displayDistance()
{
cout << "FEET: " << feet << " INCHES: " << inches << endl;
}
};
int main()
{
// 创建 Distance 对象
Distance D1(11, 10), D2;
cout << "First Distance: ";
D1.displayDistance();
// 调用重载的 operator(),让 D1 对象表现得像函数一样被调用
D2 = D1(10, 10, 10);
cout << "Second Distance: ";
D2.displayDistance();
return 0;
}
执行输出结果为:
First Distance : FEET: 11 INCHES:10
Second Distance :FEET: 30 INCHES:120
下标运算符 [] 重载
下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。
演示了如何重载下标运算符 []:
#include <iostream>
using namespace std;
// 定义数组大小常量
const int SIZE = 10;
// 定义 safearay 类
class safearay
{
private:
int arr[SIZE]; // 保存数组
public:
// 构造函数:初始化数组 arr[],内容为 0~9
safearay()
{
// register 关键字建议编译器将 i 存放在寄存器(早期用法,现在大多数编译器忽略它)
register int i;
for (i = 0; i < SIZE; i++)
{
arr[i] = i;
}
}
// 重载下标运算符 [],返回一个引用
int& operator[](int i)
{
if (i >= SIZE) // 如果下标越界
{
cout << "索引超过最大值" << endl;
return arr[0]; // 返回第一个元素,避免崩溃
}
return arr[i];
}
};
int main()
{
// 创建 safearay 类对象
safearay A;
// 访问下标为 2 的元素
cout << "A[2] 的值为: " << A[2] << endl;
// 访问下标为 5 的元素
cout << "A[5] 的值为: " << A[5] << endl;
// 访问越界的下标(12),演示安全处理
cout << "A[12] 的值为: " << A[12] << endl;
return 0;
}
执行输出结果为:
A[2] 的值为 : 2
A[5] 的值为 : 5
A[12] 的值为 : 索引超过最大值
0
类成员访问运算符 -> 重载
类成员访问运算符( -> )可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。
注意:运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。
运算符 -> 通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。这些指针是行为与正常指针相似的对象,唯一不同的是,当通过指针访问对象时,它们会执行其他的任务。比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
-> 可被定义为一个一元后缀运算符。也就是说,给出一个类:
class Ptr
{
//...
X * operator->();
};
类 Ptr 的对象可用于访问类 X 的成员,使用方式与指针的用法十分相似。例如:
void f(Ptr p )
{
p->m = 10 ; // (p.operator->())->m = 10
}
语句 p->m 被解释为 (p.operator->())->m。同样地,下面的实例演示了如何重载类成员访问运算符 ->:
#include <iostream>
#include <vector>
using namespace std;
// ========== 定义 Obj 类 ==========
class Obj
{
static int i, j; // 静态成员变量
public:
// const 只约束非静态成员变量不能修改,静态成员变量不受 const 成员函数限制
// 所以可以在 const 成员函数里修改(例如自增)
void f() const // 成员函数 f():输出并自增静态变量 i
{
cout << i++ << endl;
}
void g() const // 成员函数 g():输出并自增静态变量 j
{
cout << j++ << endl;
}
};
// 静态成员初始化
int Obj::i = 10;
int Obj::j = 12;
// ========== 定义 ObjContainer 类(用于存放 Obj* 的容器) ==========
class ObjContainer
{
vector<Obj*> a; // 使用 vector 存放指向 Obj 的指针
public:
// 向容器添加对象指针
void add(Obj* obj)
{
a.push_back(obj);
}
// 将智能指针类声明为友元,方便其访问私有成员
friend class SmartPointer;
};
// ========== 定义智能指针 SmartPointer 类 ==========
class SmartPointer
{
ObjContainer oc; // 内部包含一个 ObjContainer
int index; // 迭代当前索引
public:
// 构造函数,传入 ObjContainer,并初始化索引
SmartPointer(ObjContainer& objc)
{
oc = objc; // 使用拷贝构造
index = 0;
}
// 前缀 ++ 运算符重载:移动到下一个元素
bool operator++() // 前缀版本
{
if (index >= oc.a.size() - 1)
return false; // 到末尾了,返回 false
if (oc.a[++index] == 0)
return false; // 元素为空指针,也返回 false
return true; // 移动成功,返回 true
}
// 后缀 ++ 运算符重载:调用前缀版本
bool operator++(int)
{
return operator++();
}
// 重载 -> 运算符,返回当前指针
Obj* operator->() const
{
if (!oc.a[index])
{
cout << "Zero value" << endl;
return (Obj*)0;
}
return oc.a[index];
}
};
// ========== 主函数 ==========
int main()
{
const int sz = 10; // 创建 10 个 Obj 对象
Obj o[sz];
ObjContainer oc;
// 把 Obj 对象的地址添加到 ObjContainer 中
for (int i = 0; i < sz; i++)
{
oc.add(&o[i]);
}
// 创建智能指针对象,模拟迭代器
SmartPointer sp(oc);
// 使用智能指针访问 Obj 的成员
do {
sp->f(); // 访问 f()
sp->g(); // 访问 g()
} while (sp++); // 后缀 ++ 移动到下一个对象
return 0;
}
执行输出结果为:
10
12
11
13
12
14
13
15
14
16
15
17
16
18
17
19
18
20
19
21