C++ operator overload

什么是运算符重载

  1. 运算符重载有两种实现形式,一种是成员函数,一种是非成员函数的。如果一个运算符函数是成员函数,它的显式参数数量要比运算对象的数量少一个,第一个运算对象绑定到了隐含的this指针。
  2. 一个运算符函数,或者是类的成员,或者至少还有一个类型成员的函数。
  3. 一般不重载逗号运算符和取地址运算符,逻辑与和逻辑或运算符。
  4. 重载的运算符的求值顺序不确定,但是优先级,结合性以及操作数的数目都不变。
  5. 对于输入和输出运算符<<,>>,只能写成非成员函数,因为它们是作用在iostream上的,而不是作用在我们自己的对象上。
  6. 重载的运算符的含义应该和内置类型保持一致。
  7. [],=, ()和->必须是成员函数,否则就会编译出错。

输出和输出运算符

输出运算符

输出运算符的第一个形参是一个非常量ostream对象的引用:向流写入内容会改变其状态,所以形参ostream是非常量,因为无法复制ostream对象所以形参ostream是引用。
第二个形参一般来说是一个常量的引用:引用避免复制实参,常量意味着输出操作不会改变对象的内容。
输出运算符尽量减少格式化操作。

输入运算符

输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入的非常量对象的引用。

算术和关系运算符

可以把算术运算符和关系运算符定义成非成员函数,从而实现对左侧或者右侧对象的转换。

相等运算符

  1. 比较对象的每一个数据成员,只有对应的成员都相等时,才认为两个对象相等。
  2. 一般定义了==操作,也应该定义!=操作。但是,实际上只有一个操作负责实际比较的操作,另一个运算符工作委托给他。

关系运算符

operator==的结果必须和opeartor<的结果一致,即==为真的话,<为假;<为真,==就为假。

赋值运算符

  1. 赋值运算符必须定义为成员函数。
  2. 复合赋值运算符不一定必须定义为类的成员。
  3. 普通赋值和复合赋值都返回左侧运算对象的引用。

下标运算符

  1. 下标运算符必须是成员函数。
  2. 下标运算符通常需要定义两个版本,一个返回普通引用,另一个是类的常量成员,返回常量引用。

递增和递减运算符

  1. 自增自减运算符没有要求是成员函数,但是建议将其设定成成员函数。
  2. 前置版本返回递增或者递减后对象的引用。
  3. 重载无法区分后置和前置,后置版本接收一个额外的不被使用的int形参,进行区分。后置版本返回对象的原值,返回的是值而不是引用。

成员访问运算符

  1. 箭头运算必须是成员函数,而解引用不必。

函数调用运算符

  1. 函数调用运算符必须是成员函数。
  2. 一个类可以定义多个不同版本的调用运算符。
  3. 定义了函数调用运算符的类对象被称为函数对象,因为可以调用这种对象。

lambda是函数对象

标准库定义的函数对象

标准库定义了算术,关系,逻辑类型的函数对象,它们都是模板,定义在functional头文件中:
function_object

可调用对象和function

C++ 中的可调用对象:

  1. 函数
  2. 函数指针
  3. lambda表达式
  4. bind创建的对象
  5. 重载了函数调用运算符的类

可调用对象也有类型。比如每个lambda都有自己唯一的类型,函数和函数指针的类型由返回值类型和实参类型决定。
但是不同的可调用对象类型可能共享同一种调用形式,一种调用形式对应一个函数类型。

重载,类型转换与运算符

转换构造函数和类型转换运算符共同定义了类类型转换。

类型转换运算符

  1. 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型,它的形式如下:
    1
    operator type() const;

这里的type是int, double等。

  1. 类型转换运算符没有显式的返回类型,没有形参,必须声明为类的成员函数,而且不应该改变待转换对象的的内容,因此一般定义为const成员。
  2. 实践中很少定义类型转换运算符,因为用户会感觉很意外。
  3. 通过加上explict关键字,必须使用static_cast显式调用才会进行类型转换。但是,当表达式用作条件时,显式的类型转换会被隐式的执行。

避免类型转换运算符产生的二义性

参考文献

1.《C++ Primer》第五版