章节目录

运算符重载简介

本节阅读量:

我们之前学习过函数重载。它提供了一种机制:只要每个函数都有唯一的函数原型,就可以创建多个同名函数,并根据调用方式选择对应版本。这让您可以为不同的数据类型创建函数变体,而不必为每个变体单独起名。

在C++中,运算符被实现为函数。通过对运算符函数使用函数重载,您可以为不同的数据类型(包括您编写的类)定义自己的运算符版本。使用函数重载来重载运算符,就称为运算符重载。

在本章中,我们将研究与运算符重载相关的主题。


运算符作为函数

考虑以下示例:

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

编译器提供了用于整数操作数的加号运算符(+)内置版本。该函数会将整数x和y相加,并返回整数结果。当您看到表达式x+y时,可以在脑中把它理解为函数调用operator+(x,y)(其中operator+是函数名)。

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

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

编译器也提供了用于double的加号运算符(+)内置版本。表达式w+z会被视为函数调用operator+(w,z),函数重载则用于确定编译器应调用double版本,而不是整数版本。

现在考虑一下如果我们尝试添加程序定义类的两个对象会发生什么:

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

在这种情况下,您希望发生什么?直观的预期结果是屏幕上打印字符串“Hello,World!”。然而,由于Mystring是程序定义的类型,编译器没有可用于Mystring操作数的加号运算符内置版本,因此会产生编译错误。为了让它按预期工作,我们需要编写一个重载函数,告诉编译器“operator+”应该如何处理两个Mystring类型的操作数。下一课我们会看看如何做到这一点。


重载运算符决议

在计算包含运算符的表达式时,编译器使用以下规则:

  1. 如果所有操作数都是基本数据类型,则编译器将调用内置函数(如果存在)。如果不存在,编译器将产生编译器错误。
  2. 如果任一操作数是程序定义的类型(例如类或枚举类型),编译器将使用函数重载决议算法,尝试找到明确的最佳匹配重载运算符。这可能涉及对一个或多个操作数进行隐式转换,以匹配重载运算符的参数类型。它也可能涉及通过重载类型转换(将在本章后面介绍),将程序定义的类型隐式转换为基本类型,从而匹配内置运算符。如果找不到匹配项(或匹配结果不明确),编译器将报错。

运算符重载的限制是什么?

首先,C++中几乎所有现有运算符都可以重载。例外包括:条件运算符(?:)、sizeof、作用域运算符(::)、成员选择运算符(.)、指针成员选择运算符(.*)、typeid和cast运算符。

其次,只能重载已经存在的运算符。不能创建新运算符,也不能重命名现有运算符。例如,不能创建“operator**”来执行指数运算。

第三,重载运算符中至少有一个操作数必须是用户定义的类型。这意味着您可以重载operator+(int,Mystring),但不能重载operater+(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-呢?你希望它做什么?

最后,重载运算符应以与原始运算符一致的方式返回值。不修改其操作数的运算符(例如算术运算符)通常应按值返回结果。修改其最左侧操作数的运算符(例如,++、任何赋值运算符)通常应通过引用返回最左侧的操作数。

在这些限制内,您仍然可以为自定义类重载许多有用的功能!您可以重载+运算符来连接程序定义的字符串类,或者将两个分数类对象相加。您可以重载«运算符,以便将类输出到屏幕(或文件)。也可以重载相等运算符(==)来比较两个类对象。这使得运算符重载成为C++中非常有用的功能之一,因为它允许您以更直观的方式处理类。

在接下来的课程中,我们将更深入地研究重载不同类型的运算符。


不要使用operator作为变量名

在C++中,「operator」是用来定义运算符重载的关键字。需要避免使用任何 C++ 关键字作为变量名、函数名或类名。

编译器在解析代码时会将其视为语法的一部分。如果你这样写:

1
int operator = 10;  // 错误:编译失败

编译器会报错,因为它会尝试将「operator=」解析为一个赋值运算符重载函数,但这里的语法并不正确,因此编译失败。

如果你原本想起名叫 operator,可以用一些语义相近、但不会冲突的名字,例如「op」。


20.7 第20章总结

上一节

21.1 使用友元函数重载算术运算符

下一节