什么是运算符重载
- 运算符重载有两种实现形式,一种是成员函数,一种是非成员函数的。如果一个运算符函数是成员函数,它的显式参数数量要比运算对象的数量少一个,第一个运算对象绑定到了隐含的this指针。
- 一个运算符函数,或者是类的成员,或者至少还有一个类型成员的函数。
- 一般不重载逗号运算符和取地址运算符,逻辑与和逻辑或运算符。
- 重载的运算符的求值顺序不确定,但是优先级,结合性以及操作数的数目都不变。
- 对于输入和输出运算符
<<
,>>
,只能写成非成员函数,因为它们是作用在iostream
上的,而不是作用在我们自己的对象上。 - 重载的运算符的含义应该和内置类型保持一致。
- [],=, ()和->必须是成员函数,否则就会编译出错。
输出和输出运算符
输出运算符
输出运算符的第一个形参是一个非常量ostream对象的引用:向流写入内容会改变其状态,所以形参ostream是非常量,因为无法复制ostream对象所以形参ostream是引用。
第二个形参一般来说是一个常量的引用:引用避免复制实参,常量意味着输出操作不会改变对象的内容。
输出运算符尽量减少格式化操作。
输入运算符
输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入的非常量对象的引用。
算术和关系运算符
可以把算术运算符和关系运算符定义成非成员函数,从而实现对左侧或者右侧对象的转换。
相等运算符
- 比较对象的每一个数据成员,只有对应的成员都相等时,才认为两个对象相等。
- 一般定义了==操作,也应该定义!=操作。但是,实际上只有一个操作负责实际比较的操作,另一个运算符工作委托给他。
关系运算符
operator==的结果必须和opeartor<的结果一致,即==为真的话,<为假;<为真,==就为假。
赋值运算符
- 赋值运算符必须定义为成员函数。
- 复合赋值运算符不一定必须定义为类的成员。
- 普通赋值和复合赋值都返回左侧运算对象的引用。
下标运算符
- 下标运算符必须是成员函数。
- 下标运算符通常需要定义两个版本,一个返回普通引用,另一个是类的常量成员,返回常量引用。
递增和递减运算符
- 自增自减运算符没有要求是成员函数,但是建议将其设定成成员函数。
- 前置版本返回递增或者递减后对象的引用。
- 重载无法区分后置和前置,后置版本接收一个额外的不被使用的int形参,进行区分。后置版本返回对象的原值,返回的是值而不是引用。
成员访问运算符
- 箭头运算必须是成员函数,而解引用不必。
函数调用运算符
- 函数调用运算符必须是成员函数。
- 一个类可以定义多个不同版本的调用运算符。
- 定义了函数调用运算符的类对象被称为函数对象,因为可以调用这种对象。
lambda是函数对象
标准库定义的函数对象
标准库定义了算术,关系,逻辑类型的函数对象,它们都是模板,定义在functional头文件中:
可调用对象和function
C++ 中的可调用对象:
- 函数
- 函数指针
- lambda表达式
- bind创建的对象
- 重载了函数调用运算符的类
可调用对象也有类型。比如每个lambda都有自己唯一的类型,函数和函数指针的类型由返回值类型和实参类型决定。
但是不同的可调用对象类型可能共享同一种调用形式,一种调用形式对应一个函数类型。
重载,类型转换与运算符
转换构造函数和类型转换运算符共同定义了类类型转换。
类型转换运算符
- 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型,它的形式如下:
1
operator type() const;
这里的type是int, double等。
- 类型转换运算符没有显式的返回类型,没有形参,必须声明为类的成员函数,而且不应该改变待转换对象的的内容,因此一般定义为const成员。
- 实践中很少定义类型转换运算符,因为用户会感觉很意外。
- 通过加上explict关键字,必须使用static_cast显式调用才会进行类型转换。但是,当表达式用作条件时,显式的类型转换会被隐式的执行。
避免类型转换运算符产生的二义性
参考文献
1.《C++ Primer》第五版