操作符重载简介
本节阅读量:我们之前学习过函数重载,它提供了一种机制,可以创建多个相同名称的不同函数,并分别进行调用,只要每个函数都具有唯一的函数原型。这允许您创建函数的变体以处理不同的数据类型,而不必为每个变体想出唯一的名称。
在C++中,操作符被实现为函数。通过在操作符函数上使用函数重载,您可以定义自己的操作符版本,这些操作符用于不同的数据类型(包括您编写的类)。使用函数重载来重载运算符称为运算符重载。
在本章中,我们将研究与运算符重载相关的主题。
运算符作为函数
考虑以下示例:
|
|
编译器附带了用于整数操作数的加号运算符(+)的内置版本——该函数将整数x和y相加,并返回整数结果。当您看到表达式x+y时,可以在头脑中将其转换为函数调用操作符+(x,y)(其中操作符+是函数的名称)。
现在考虑这个类似的片段:
|
|
编译器还提供了用于double的加号运算符(+)的内置版本。表达式w+z变为函数调用运算符+(w,z),函数重载用于确定编译器应调用此函数的double版本,而不是整数版本。
现在考虑一下如果我们尝试添加程序定义类的两个对象会发生什么:
|
|
在这种情况下,您希望发生什么?直观的预期结果是字符串“Hello,World!”将打印在屏幕上。然而,由于Mystring是程序定义的类型,编译器没有可以用于Mystring操作数的加号运算符的内置版本。所以在这种情况下,它会给我们一个编译错误。为了使它像我们希望的那样工作,我们需要编写一个重载函数来告诉编译器“操作符+”应该如何与Mystring类型的两个操作数一起工作。我们将在下一课中查看如何做到这一点。
重载运算符决议
在计算包含运算符的表达式时,编译器使用以下规则:
- 如果所有操作数都是基本数据类型,则编译器将调用内置函数(如果存在)。如果不存在,编译器将产生编译器错误。
- 如果任以操作数是程序定义的类型(例如,一个类或枚举类型),编译器将使用函数重载决议算法来查看它是否可以找到明确的最佳匹配的重载运算符。这可能涉及隐式转换一个或多个操作数以匹配重载运算符的参数类型。它还可能涉及隐式地将程序定义的类型转换为基本类型(通过重载类型转换,将在本章后面介绍),以便它可以匹配内置运算符。如果找不到匹配项(或找到不明确的匹配项),编译器将出错。
运算符重载的限制是什么?
首先,C++中几乎所有现有的操作符都可以重载。例外情况包括:条件(?:)、sizeof、域操作符(::)、成员选择器(.)、指针成员选择器(.*)、typeid和cast运算符。
其次,只能重载存在的运算符。不能创建新操作符或重命名现有操作符。例如,不能创建“运算符**”来执行指数操作。
第三,重载运算符中至少有一个操作数必须是用户定义的类型。这意味着您可以重载operator+(int,Mystring),但不能重载operater+(int、double)。
由于标准库类被认为是用户定义的,这意味着您可以定义操作符+(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) ),这样才能正常工作,这不是直观的,并且可能容易出错。
由于这个优先级问题,通常最好只以类似于其原始意图的方式使用操作符。
此外,由于运算符没有描述性名称,因此并不总是直观的清楚它们打算做什么。例如,操作符+可能是字符串类连接字符串的合理选择。但操作符-呢?你希望它做什么?
最后,重载运算符应以与原始运算符一致的方式返回值。不修改其操作数的运算符(例如算术运算符)通常应按值返回结果。修改其最左侧操作数的运算符(例如,++、任何赋值运算符)通常应通过引用返回最左侧的操作数。
在这些限制内,您仍然会发现许多有用的功能可以为您的自定义类重载!您可以重载+运算符来连接程序定义的字符串类,或者将两个分数类对象加到一起。您可以重载«运算符,以便将类打印到屏幕(或文件)。可以重载相等运算符(==)来比较两个类对象。这使得操作符重载成为C++中有用的功能之一——因为它允许您以更直观的方式处理类。
在接下来的课程中,我们将更深入地研究重载不同类型的运算符。
最佳实践
- 重载运算符应至少对一个程序定义的类型(作为函数的参数或隐式对象)进行操作。
- 重载操作符时,最好使操作符的功能尽可能接近操作符的原始意图。
- 如果重载运算符的含义不明确和直观,请使用命名函数。
- 不修改其操作数的运算符(例如算术运算符)通常应按值返回结果。修改其最左侧操作数的运算符(例如,++、任何赋值运算符)通常应通过引用返回最左侧的操作数。
