重载运算与类型转换
14.1 基本概念
他们的名字有关键字operator和其后要定义的运算符号共同组成 重载的运算符也包含返回类型、参数列表以及函数体 如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,因此其显式参数数量比运算符的运算对象总数少一个
直接调用重载运算符函数
1
2
3
4
5
6
data1 + data2;
operator+ (data1,data2);//等价表示
//成员函数
data1+= data2;
data1.operator+=(data2);
//this绑定到data1的地址
使用与内置类型一致的含义
- 如果类执行IO操作,则定义移位运算符与其内置类型的IO保持一致
- 定义operator==则也应该定义operator!=
- 如果一个类包含内在的单序比较,则定义operator<,以及其他关系操作
- 逻辑与关系运算符应返回bool,算术返回一个类类型,赋值和复合运算符返回左侧运算对象的一个引用
赋值和复合赋值运算符
+= 运算符应该与其内置版本一致,即先执行+在执行=
选择作为成员或者非成员
当我们把运算符定义为成员函数时,其左侧运算对象必须时运算符所属类的一个对象
1
2
3
4
string s = "world";
string t = s + "!" ;//正确,可以吧一个const char* 加到string 对象
string u = "hi" + s;//如果+ 是string的成员函数,则错误
故string将+定义为普通的非成员函数
14.2 输入和输出运算符
14.2.1 重载输出运算符«
通常情况下,«第一个形参是一个 非常量ostream对象的引用,第二个形参一般是一个常量的引用
输出运算符尽量减少格式化操作
也即尽量不要打印 换行符
输入输出运算符必须是非成员函数
14.2.2 重载输入运算符 »
输入运算符必须能处理输入可能失败的情况
当读取操作发生错误时,输入运算符应该负责从错误中恢复
14.3 算术和关系运算符
14.3.1 相等运算符
14.3.2 关系运算符
14.4 赋值运算符
已经介绍过拷贝赋值和移动赋值运算符,为了与内置类型的赋值运算符保持一致,新的赋值运算符将返回其左侧运算对象的引用
复合赋值运算符
14.5 下标运算符
下标运算符必须是成员函数 如果一个类包含下标运算符,则它通常定义两个版本,一个返回普通引用,一个是类的常量成员并且返回常量引用
表示容器的类通常可以通过元素在容器中的位置访问元素
14.6 递增和递减运算符
建议将其定义为类的成员函数 应该同时定义前置版本和后置版本
1
i++;i--;
定义前置递增/递减运算符 返回递增或者递减后对象的引用
区分前置和后置运算符
后置版本接受一个额外(不被使用)int 类型的形参,当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参
1
StrBlobPtr operator++(int);//后置版本
后置版本:递增/递减对象但是返回原值 故递增递减前要记录对象的状态
显式地调用后置运算符
通过函数调用的方式调用后置版本,必须为其整型参数传递一个值
1
2
3
StrBlobPtr p(a1);
p.operator++(0);//后置版本
p.operator--();//前置版本
14.7 成员访问运算符
解引用运算符(*)和箭头运算符(->) 箭头运算符必须是类的成员,解引用运算符通常也是类的成员
14.8 函数调用运算符
例如
1
2
3
4
5
struct absInt{
int operator() (int val) const{
return val <0 ? -val:val;
}
};
函数调用运算符必须是成员函数 如果类定义了调用运算符,则该类的对象称为函数对象
含有状态的函数对象类
函数对象类通常含有一些数据成员,这些成员被用于定制调用运算符中的操作 例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class PrintString{
public:
PrintString(ostream& o = cout,char c = ' '):
os(o),sep(c){}
void operator() (const string& s)const
{os << s << sep;}
private:
ostream & os;
char sep;
};
//使用函数对象
PrintString printer;
printer(s);
PrintString errors(cerr,'\n');
14.8.1 lambda是函数对象
当我们编写了一个lambda后,编译器将该表达式翻译成一个未命名类的未命名对象,在lambda表达式产生的类中含有一个重载的函数调用运算符
表示lambda以相应捕获行为的类
lambda表达式产生的类不含默认贡藕早函数、赋值运算符以及默认析构函数
cpp11标准为什么要增加lambda?什么情况下会使用lambda,什么情况下使用类 lambda是通过匿名的函数对象来实现的,我们可以把lambda看作是函数对象在使用方式上进行的简化。 当代码需要一个简单函数且其并不会在其他地方被使用时,就可以用lambda来实现,如果它被多次使用且需要保存某些状态,则使用函数对象。
14.8.2 标准库定义的函数对象
标准库定义了一组表示算数运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行明明操作的调用运算符。
1
2
3
plus<int> intAdd;//可执行int加法的函数对象
negate<int> intNegate;//可对int取反的函数对象
………………
其类型都在functional头文件中
在算法中使用标准库函数对象
14.9 重载、类型转换与运算符
通过定义类型转换运算符可以做到类类型的类型转换。 转换构造函数和类型转换运算符共同定义了类类型转换,有时候也叫做用户定义的类型转换。
14.9.1 类型转换运算符
类型转换运算符时类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。 一般形式如下:
1
operator type() const;
其中 type表示某种类型
一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常为const
定义含有类型转换运算符的类
举个例子:
1
2
3
4
5
6
7
8
9
10
11
class SmallInt{
public:
SmallInt(int i = 0 ): val(i)
{
if (i< 0 || i > 255)
throw out_of_range("Bad SmallInt Value");
}
operator int() const {return val;}
private:
size_t val;
}
SmallInt类既定义了向类类型的转换,也定义了从类类型向其他类型的转换。
- 构造函数将算数类型的值转换成 SmallInt对象
- 类型转换运算符将SmallInt对象转换成 int;
1 2 3
SmallInt si; si = 4;//首先将4隐式地转换成SmallInt,然后调用SamllInt::operator=; si+3;//首先将si 隐式地转换成int,然后执行整数的加法
显式的类型转换运算符
cpp11新标准引入了显式的类型转换运算符 ```cpp class SmallInt{ public: SmallInt(int i = 0 ): val(i) { if (i< 0 || i > 255) throw out_of_range(“Bad SmallInt Value”); } explicit operator int() const {return val;} private: size_t val; }
static_cast
1
2
3
4
5
#### 转换为bool
cpp11新标准下,IO标准库通过定义一个向bool的显式类型转换实现同样的目的。
例如:
```cpp
while( std::cin>> value)
14.9.2 避免有二义性的类型转换
……………………
14.9.3 函数匹配与重载运算符
重载的运算符也是重载的函数 表达式中的运算符候选函数集既应该包括成员函数,也应该包括非成员函数