21-1 运算符重载介绍

在第11.1节——函数重载简介中,你学习了函数重载的概念。它提供了一种机制,能够创建并解析对多个同名函数的调用,前提是每个函数都具有唯一的函数原型。这使得你可以创建函数的不同变体来处理不同数据类型,而无需为每个变体构思独特的名称。

在C++中,运算符以函数形式实现。通过对运算符函数应用函数重载,你可以定义适用于不同数据类型(包括自定义类)的运算符版本。这种利用函数重载实现运算符的特性称为运算符重载

本章将深入探讨与运算符重载相关的主题。


运算符作为函数

考虑以下示例:

int x { 2 };
int y { 3 };
std::cout << x + y << '\n';

编译器为整数操作数内置了加法运算符(+)——该函数将整数x和y相加,并返回整数结果。当你看到表达式x + y时,可以在脑海中将其转换为函数调用operator+(x, y)(其中operator+是该函数的名称)。

现在考虑这个类似的代码片段:

double z { 2.0 };
double w { 3.0 };
std::cout << w + z << '\n';

编译器还内置了适用于双精度操作数的加法运算符(+)。表达式 w + z 将转化为函数调用 operator+(w, z),此时通过函数重载机制确定编译器应调用该函数的双精度版本而非整数版本。

现在考虑当我们尝试对两个程序定义类的对象进行加法运算时会发生什么:

Mystring string1 { "Hello, " };
Mystring string2 { "World!" };
std::cout << string1 + string2 << '\n';

在此情况下,您预期会发生什么?直观的预期结果是字符串“Hello, World!”将被打印到屏幕上。然而,由于Mystring是程序定义的类型,编译器没有可用于Mystring操作数的内置加号运算符。因此在此情况下,它将报错。要实现预期效果,我们需要编写重载函数来告知编译器如何处理两个Mystring类型的加法运算。具体实现方法将在下一课中讲解。


重载运算符的解析

在评估包含运算符的表达式时,编译器遵循以下规则:

  • 若所有操作数均为基本数据类型,编译器将调用内置函数(若存在)。若不存在,编译器将报错。
  • 若任一操作数为程序定义类型(如自定义类或枚举类型),编译器将采用函数重载解析算法(详见第11.3节——函数重载解析与模糊匹配),尝试寻找无歧义的最佳匹配重载运算符。该过程可能涉及隐式转换一个或多个操作数以匹配重载运算符的参数类型,也可能通过重载类型转换(本章后文将详述)将程序定义类型隐式转换为基本类型,从而匹配内置运算符。若无法找到匹配项(或存在歧义匹配),编译器将报错。

运算符重载有哪些限制?

首先,C++中几乎所有现有的运算符都可以重载。例外情况包括:条件运算符(?:)、sizeof、作用域运算符(::)、成员选择符(.)、指针成员选择符(.*)、typeid 以及强制类型转换运算符。

其次,只能重载已存在的运算符。无法创建新运算符或重命名现有运算符。例如,无法创建用于幂运算的 operator**。

第三,重载运算符的操作数中至少有一个必须是用户定义类型。这意味着可以重载 operator+(int, Mystring),但不能重载 operator+(int, double)。

由于标准库类被视为用户定义类型,因此可以定义 operator+(double, std::string)。但这并非明智之举——未来语言标准可能定义此重载,导致使用该重载的程序失效。因此最佳实践是:重载运算符至少应操作一个程序定义类型,这可确保未来语言标准不会破坏现有程序。

最佳实践:
重载运算符至少应作用于一种程序定义类型(作为函数参数或隐式对象)。

第四,运算符支持的操作数数量不可更改。

最后,所有运算符均保留其默认优先级和结合性(无论用于何种场景),且此特性不可修改。

部分新手程序员试图重载位运算异或符()实现幂运算。但在C++中,运算符的优先级低于基本算术运算符,会导致表达式计算错误。

在基础数学中,幂运算优先于基本算术运算,因此 4 + 3 ^ 2 应解析为 4 + (3 ^ 2) => 4 + 9 => 13。

但在 C++ 中,算术运算符优先级高于 ^ 运算符,故 4 + 3 ^ 2 实际解析为 (4 + 3) ^ 2 => 7 ^ 2 => 49。

要使该表达式正确运行,每次都需显式为幂运算部分加括号(如 4 + (3 ^ 2)),这种操作既不符合直觉又容易出错。

鉴于此优先级问题,通常建议仅按运算符原始设计意图的类比方式使用它们。

最佳实践:
重载运算符时,应尽可能使运算符的功能贴近原始运算符的本意。

此外,由于运算符没有描述性名称,其具体作用往往难以明确。例如,字符串类使用 operator+ 进行字符串连接是合理的选择。但 operator- 呢?你期望它做什么?这并不明确。

最佳实践:
若重载运算符的含义不够清晰直观,请改用命名函数实现。

最后,重载运算符的返回方式应与原始运算符保持一致。不修改操作数的运算符(如算术运算符)通常应按值返回结果。修改最左操作数的运算符(如前置自增、各类赋值运算符)通常应通过引用返回最左操作数。

最佳实践:
不修改操作数的运算符(如算术运算符)通常应按值返回结果。

修改最左操作数的运算符(如前置自增、任何赋值运算符)通常应通过引用返回最左操作数。

在此框架下,您仍可为自定义类重载大量实用功能!可重载+运算符以连接程序定义的字符串类,或对两个Fraction类对象进行加法运算。可重载<<运算符,便于将类对象输出至屏幕(或文件)。可重载等号运算符(==)用于比较两个类对象。正因如此,运算符重载成为C++最实用的特性之一——它让你能以更直观的方式操作类对象。

后续课程中,我们将深入探讨各类运算符的重载机制。

posted @ 2026-01-22 05:34  游翔  阅读(3)  评论(0)    收藏  举报