C++ 中最常用的运算符之一便是算术运算符——即加号(+)、减号(−)、乘号(*)与除号(/)。请注意,这四个运算符均为二元运算符,亦即它们均要求两个操作数,分别位于运算符两侧。上述四种运算符的重载方式完全相同。
事实上,运算符重载共有三种实现途径:成员函数方式、友元函数方式以及普通函数方式。本课将讨论友元函数方式(因其对大多数二元运算符而言最为直观)。下一课将介绍普通函数方式,后续课程再讨论成员函数方式,并最终详细总结何时选用何种方式。
利用友元函数重载运算符
考虑以下类:
class Cents
{
private:
int m_cents{};
public:
Cents(int cents) : m_cents{ cents } {}
int getCents() const { return m_cents; }
};
下面的示例演示如何重载加号运算符(+),以将两个 Cents
对象相加:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents) : m_cents{ cents } {}
// 使用友元函数实现 Cents + Cents
friend Cents operator+(const Cents& c1, const Cents& c2);
int getCents() const { return m_cents; }
};
// 注意:此函数并非成员函数!
Cents operator+(const Cents& c1, const Cents& c2)
{
// 借助 Cents 构造函数及内建 operator+(int, int)
// 由于是友元函数,可直接访问 m_cents
return { c1.m_cents + c2.m_cents };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
运行结果:
I have 14 cents.
重载加号运算符(+)只需:
- 声明名为
operator+
的函数; - 指定两个形参,类型为欲相加的操作数类型;
- 选择合适的返回类型;
- 实现函数体。
在本例中:
- 形参类型:两个
Cents
对象; - 返回类型:
Cents
,表示结果; - 实现:将两个对象的
m_cents
相加即可。由于友元函数可直接访问m_cents
,且m_cents
为整型,可直接使用内建加号运算符。
重载减法运算符(-)
同样简单:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
explicit Cents(int cents) : m_cents{ cents } {}
friend Cents operator+(const Cents& c1, const Cents& c2);
friend Cents operator-(const Cents& c1, const Cents& c2);
int getCents() const { return m_cents; }
};
Cents operator+(const Cents& c1, const Cents& c2)
{
return { c1.m_cents + c2.m_cents };
}
Cents operator-(const Cents& c1, const Cents& c2)
{
return { c1.m_cents - c2.m_cents };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 2 };
Cents centsDiff{ cents1 - cents2 };
std::cout << "I have " << centsDiff.getCents() << " cents.\n";
return 0;
}
以此类推,重载乘号(*)与除号(/)只需分别定义 operator*
与 operator/
即可。
友元函数可在类内定义
尽管友元函数并非类成员,但可将其定义置于类内:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
explicit Cents(int cents) : m_cents{ cents } {}
friend Cents operator+(const Cents& c1, const Cents& c2)
{
return { c1.m_cents + c2.m_cents };
}
int getCents() const { return m_cents; }
};
对于实现简单的运算符重载,这样做完全合法。
重载不同操作数类型的运算符
通常,我们希望重载的运算符支持不同类型操作数。例如,Cents(4) + 6
应得到 Cents(10)
。
当 C++ 计算表达式 x + y
时,x
为左操作数,y
为右操作数。若二者类型不同,则 x + y
与 y + x
将调用不同的重载函数。
例如:
Cents(4) + 6
调用operator+(Cents, int)
;6 + Cents(4)
调用operator+(int, Cents)
。
因此,若需支持不同类型,需编写两个版本:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
explicit Cents(int cents) : m_cents{ cents } {}
friend Cents operator+(const Cents& c1, int value);
friend Cents operator+(int value, const Cents& c1);
int getCents() const { return m_cents; }
};
Cents operator+(const Cents& c1, int value)
{
return { c1.m_cents + value };
}
Cents operator+(int value, const Cents& c1)
{
return { value + c1.m_cents }; // 或复用上一版本
}
int main()
{
Cents c1{ Cents{ 4 } + 6 };
Cents c2{ 6 + Cents{ 4 } };
std::cout << "I have " << c1.getCents() << " cents.\n";
std::cout << "I have " << c2.getCents() << " cents.\n";
return 0;
}
注意两函数实现相同,仅参数顺序不同。
再举一例:MinMax 类
#include <iostream>
class MinMax
{
private:
int m_min{}; // 当前最小值
int m_max{}; // 当前最大值
public:
MinMax(int min, int max) : m_min{ min }, m_max{ max } {}
int getMin() const { return m_min; }
int getMax() const { return m_max; }
friend MinMax operator+(const MinMax& m1, const MinMax& m2);
friend MinMax operator+(const MinMax& m, int value);
friend MinMax operator+(int value, const MinMax& m);
};
MinMax operator+(const MinMax& m1, const MinMax& m2)
{
int min{ std::min(m1.m_min, m2.m_min) };
int max{ std::max(m1.m_max, m2.m_max) };
return { min, max };
}
MinMax operator+(const MinMax& m, int value)
{
int min{ std::min(m.m_min, value) };
int max{ std::max(m.m_max, value) };
return { min, max };
}
MinMax operator+(int value, const MinMax& m)
{
return m + value; // 复用已有重载
}
int main()
{
MinMax m1{ 10, 15 };
MinMax m2{ 8, 11 };
MinMax m3{ 3, 12 };
MinMax mFinal{ m1 + m2 + 5 + 8 + m3 + 16 };
std::cout << "Result: (" << mFinal.getMin() << ", " << mFinal.getMax() << ")\n";
return 0;
}
输出:
Result: (3, 16)
表达式 m1 + m2 + 5 + 8 + m3 + 16
自左向右求值,每一步返回的 MinMax
对象作为下一次调用的左操作数,最终得到最小值 3、最大值 16。
利用已有运算符实现新运算符
上例中,operator+(int, MinMax)
直接调用 operator+(MinMax, int)
,减少代码冗余并简化维护。只要这样能使代码更简洁,就应复用已有重载;若实现极为简单(仅一行),是否复用可视情况而定。
小测
问题 1
a) 编写名为 Fraction
的类,含整型分子与分母成员;提供 print()
函数输出分数。
示例代码应能通过编译并输出:
1/4
1/2
b) 使用友元函数方式,重载乘法运算符,支持 Fraction * int
与 Fraction * Fraction
。
提示:
- 两分数相乘:分子相乘,分母相乘;
- 分数与整数相乘:分子乘整数,分母不变。
示例代码应能通过编译并输出:
2/5
3/8
6/40
4/5
6/8
6/24
c) 若将构造函数改为非 explicit
并删除整数乘法运算符,为何仍能正确工作?
d) 若将 operator*(Fraction, Fraction)
的引用形参改为非 const
,则主函数中某语句失效,何故?
e) 附加题:实现 reduce()
成员函数,将分数化为最简形式(C++17 可用 std::gcd
,旧编译器可用示例递归函数)。示例输出见题目要求。