注:答案是按照特定的顺序给出的,但由于许多用户根据投票而不是给出答案的时间对答案进行排序,以下是答案的索引,以最有意义的顺序排列:
C中运算符重载的一般语法++C语言中运算符重载的三个基本规则++成员与非成员之间的决定常见操作员过载赋值运算符输入和输出运算符函数调用运算符比较运算符算术运算符阵列订阅指针类类型的运算符转换运算符重载新建和删除
(注意:这是Stack Overflow的C++FAQ的一个条目。如果你想批评以这种形式提供FAQ的想法,那么在meta上发布这一切的帖子就是这样做的地方。该问题的答案在C++聊天室中进行监控,FAQ想法最初就在那里开始,所以你的答案很可能会被提出该想法的人阅读。)
成员与非成员之间的决定
二进制运算符=(赋值)、[](数组订阅)、->(成员访问)以及n元()(函数调用)运算符必须始终作为成员函数实现,因为语言的语法要求它们实现。
其他运算符可以作为成员或非成员实现。然而,其中一些函数通常必须实现为非成员函数,因为您无法修改它们的左操作数。其中最突出的是输入和输出运算符<<and>>,其左操作数是标准库中的流类,您无法更改。
对于必须选择将其作为成员函数或非成员函数实现的所有运算符,请使用以下经验规则来决定:
如果它是一元运算符,请将其实现为成员函数。如果二进制运算符对两个操作数一视同仁(保持不变),则将此运算符作为非成员函数实现。如果一个二元运算符不能平等对待两个操作数(通常它会改变其左操作数),如果它必须访问操作数的私有部分,则将其设置为左操作数类型的成员函数可能会很有用。
当然,与所有经验法则一样,也有例外。如果你有一个类型
enum Month {Jan, Feb, ..., Nov, Dec}
如果要重载它的递增和递减运算符,则不能将其作为成员函数,因为在C++中,枚举类型不能具有成员函数。所以你必须把它作为一个自由函数重载。嵌套在类模板中的类模板的运算符<()在作为类定义中内联的成员函数时更容易编写和读取。但这些确实是罕见的例外。
(但是,如果发生异常,请不要忘记操作数的常量问题,对于成员函数,该操作数将成为隐式this参数。如果作为非成员函数的运算符将其最左边的参数作为常量引用,则作为成员函数的同一运算符需要在末尾有一个常量,以使*this成为常量引用。)
继续到“常用运算符”以重载。
成员与非成员之间的决定
二进制运算符=(赋值)、[](数组订阅)、->(成员访问)以及n元()(函数调用)运算符必须始终作为成员函数实现,因为语言的语法要求它们实现。
其他运算符可以作为成员或非成员实现。然而,其中一些函数通常必须实现为非成员函数,因为您无法修改它们的左操作数。其中最突出的是输入和输出运算符<<and>>,其左操作数是标准库中的流类,您无法更改。
对于必须选择将其作为成员函数或非成员函数实现的所有运算符,请使用以下经验规则来决定:
如果它是一元运算符,请将其实现为成员函数。如果二进制运算符对两个操作数一视同仁(保持不变),则将此运算符作为非成员函数实现。如果一个二元运算符不能平等对待两个操作数(通常它会改变其左操作数),如果它必须访问操作数的私有部分,则将其设置为左操作数类型的成员函数可能会很有用。
当然,与所有经验法则一样,也有例外。如果你有一个类型
enum Month {Jan, Feb, ..., Nov, Dec}
如果要重载它的递增和递减运算符,则不能将其作为成员函数,因为在C++中,枚举类型不能具有成员函数。所以你必须把它作为一个自由函数重载。嵌套在类模板中的类模板的运算符<()在作为类定义中内联的成员函数时更容易编写和读取。但这些确实是罕见的例外。
(但是,如果发生异常,请不要忘记操作数的常量问题,对于成员函数,该操作数将成为隐式this参数。如果作为非成员函数的运算符将其最左边的参数作为常量引用,则作为成员函数的同一运算符需要在末尾有一个常量,以使*this成为常量引用。)
继续到“常用运算符”以重载。
为什么用于将对象流到std::cout或文件的运算符<<函数不能作为成员函数?
假设你有:
struct Foo
{
int a;
double b;
std::ostream& operator<<(std::ostream& out) const
{
return out << a << " " << b;
}
};
鉴于此,您不能使用:
Foo f = {10, 20.0};
std::cout << f;
由于运算符<<被重载为Foo的成员函数,因此运算符的LHS必须是Foo对象。这意味着,您需要使用:
Foo f = {10, 20.0};
f << std::cout
这是非常不直观的。
如果将其定义为非成员函数,
struct Foo
{
int a;
double b;
};
std::ostream& operator<<(std::ostream& out, Foo const& f)
{
return out << f.a << " " << f.b;
}
您将能够使用:
Foo f = {10, 20.0};
std::cout << f;
这是非常直观的。
C中运算符重载的一般语法++
您不能更改C++中内置类型的运算符的含义,只能为用户定义的类型重载运算符1。也就是说,至少有一个操作数必须是用户定义的类型。与其他重载函数一样,运算符只能为某组参数重载一次。
并非所有的运算符都可以在C++中重载。不能重载的运算符包括:.::sizeof typeid.*和C++中唯一的三元运算符,?:
在C++中可以重载的运算符有:
算术运算符:+-*/%和+=-=*=/=%=(所有二进制中缀);+-(一元前缀);++--(一元前缀和后缀)位操作:&|^<<>>和&=|=^=<<>>=(所有二进制中缀);~(一元前缀)布尔代数:==!=<><>=|&&&(所有二进制中缀)!(一元前缀)内存管理:新建[]删除删除[]隐式转换运算符杂项:=[]->->*,(所有二进制中缀);*&(全一元前缀)()(函数调用,n元中缀)
然而,您可以重载所有这些并不意味着您应该这样做。请参阅运算符重载的基本规则。
在C++中,运算符以具有特殊名称的函数的形式重载。与其他函数一样,重载运算符通常可以实现为其左操作数类型的成员函数或非成员函数。您是否可以自由选择或绑定使用其中一个取决于几个条件。2应用于对象x的一元运算符@3被调用为运算符@(x)或x.operator@()。应用于对象x和y的二进制中缀运算符@被调用为操作符@(x,y)或x.运算符@(y)。4
作为非成员函数实现的运算符有时是其操作数类型的朋友。
1“用户定义”一词可能有点误导。C++区分了内置类型和用户定义类型。前者属于例如int、char和double;所有struct、class、union和enum类型都属于后者,包括标准库中的类型,即使它们本身不是由用户定义的。
2这将在本常见问题解答的后面部分介绍。
3@在C++中不是有效的运算符,这就是为什么我将它用作占位符。
C++中唯一的三元运算符不能重载,唯一的n元运算符必须始终作为成员函数实现。
继续学习C++中运算符重载的三个基本规则。