“C++ Primer”
可以重载的操作符
1
2
3
4
5
6
7
+` `-` `*` `/` `%` `^
&` `|` `~` `!` `,` `=
<` `>` `<=` `>=` `++` `--
<<` `>>` `==` `!=` `&&` `||
+=` `-=` `/=` `%=` `^=` `&=
|=` `*=` `<<=` `>>=` `[]` `()
->` `->*` `new` `new[]` `delete` `delete[]
不能重载的操作符
1
::` `.*` `.` `?:
选择作为成员或者非成员实现
- 赋值(
=
)、下标([]
)、调用(()
)和成员访问箭头(->
)必须被定义为成员函数; - 复合赋值操作符通常应该(ought)是成员,然而,不像赋值操作符,这不是必须的;
- 改变对象状态的操作符或者与这个类类型关系十分密切(closely tied)的操作符应该(should)被定义为成员,如:自增、自减和解引用操作符;
- 对称操作符——它们可以转换任何一个操作数,比如算术运算、相等性比较、关系比较和按位操作符——通常应该(should)被定义为常规的非成员函数
递增和递减操作符
前置版本:前置操作符应该返回自增后或者自减后的对象的引用
1
2
3
4
5
class StrBlobPtr {
public:
StrBlobPtr operator++(int);
StrBlobPtr operator--(int);
};
后置版本:额外的int
参数用于区分前置后置版本,后置操作符应该返回旧的(未自增或者未自减)的值。这个值将作为值返回而不是引用
1
2
3
4
5
class StrBlobPtr {
public:
StrBlobPtr operator++(int);
StrBlobPtr operator--(int);
};
函数调用运算符
函数调用操作符必须是成员函数。一个类型可以定义多个调用操作符版本,其中每一个必须在参数的个数或类型不一样。
1
2
3
4
5
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
};
lambda 是函数对象
当我们写ambda
时,编译器将其翻译成一个匿名类的匿名对象(unnamed object of an unnamed class)。这个类从ambda
中产生并包含一个函数调用操作符。如:
1
2
3
4
5
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b) {
return a.size() < b.size();
}
);
将被翻译成
1
2
3
4
5
class ShorterString {
public:
bool operator()(const string &s1, const string &s2) const
{ return s1.size() < s2.size(); }
};
默认情况下由 lambda 生成的类的函数调用操作符是一个const
成员函数。如果lambda
被声明为mutable
,那么调用操作符将不是const
的。
std::function
调用形式 两个不同的可调用对象拥有相同的调用形式,如:
1
2
3
4
5
6
7
8
9
10
11
// 函数
int add(int i, int j) { return i + j; }
// lambda
auto mod = [](int i, int j) { return i % j; };
// 函数对象类
struct div {
int operator()(int denominator, int divisor) {
return denominator / divisor;
}
};
// 调用形式都为 int(int, int)
当想通过一个map
存储这些具有相同表达形式的可调用对象时却会失败,如
1
map<string, int(*)(int, int)> binops; // 不成功,每个 lambda 有自己的类类型
通过定义在functional
头文件中的新的标准库类std::function
来解决此问题
定义:
1
2
3
4
5
6
7
8
9
function<int(int, int)>
// function 是模板,当我们创建 function 类型对象时我们必须提供额外的信息,在这里是调用签名
map<string, function<int(int, int)>> binops = {
{"+", add}, // 函数指针
{"-", std::minus<int>()}, // 库函数对象
{"/", div()}, // 用户定义函数对象
{"*", [](int i, int j) { return i * j; }}, // 匿名 lambda
{"%", mod} // 具名 lambda
};
类型转换运算符
转换操作符(conversion operator)是一种特殊的成员函数,可以将一个类类型的值转为一个其它类型的值。
类型转换运算符必须是成员函数,并且不指定返回值类型,其参数列表必须是空的。这个函数通常应该是const
的。转换成数组或者函数类型是被禁止的。转换成指针类型(数据和函数指针)以及引用类型是允许的
形式:operatro type() const;
显式转换操作符
1
2
3
4
class SmallInt {
public:
explicit operator int() const { return val; }
};
与explicit
构造函数一样,编译器不会自动运用explicit
转换操作符进行隐式转换:
1
2
3
SmallInt si = 3;
si + 3; //错误:用到了隐式转换,但是重载的操作符是显式的
static_cast<int>(si) + 3; //ok:显式调用转换
但是编译器会将 explicit 转换用在条件中,如下:
- 如果条件是一个 if, while 或者 do 语句;
- 如果条件表达式在 for 语句的头部;
- 如果作为逻辑非(
!
)、或(|
)或者与(&&
)操作符的操作数; - 条件操作符(
?:
)的条件表达式部分中;