C++ 中可以重定义或重载大部分内置的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。 与其他函数一样,重载运算符有一个返回类型和一个参数列表。
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。
Box operator+(const Box&);//这种方式定义在类成员位置
Box operator+(const Box&, const Box&);//这种方式可以定义在类外部
+ - * / % ^
& | ~ ! , =
< > <= >= ++ --
<< >> == != && ||
+= -= /= %= ^= &=
|= *= <<= >>= [] ()
-> ->* new new[] delete delete []
.
:成员访问运算符.*, ->*
:成员指针访问运算符::
:域运算符sizeof
:长度运算符?:
:条件运算符#
: 预处理符号
通过连接其他合法符号可以创建新的操作符。例如,定义一个 operator**
以提供求幂运算是合法的
为了与 IO 标准库一致,操作符应接受 ostream& 作为第一个形参,对类类型 const 对象的引用作为第二个形参, 并返回对ostream 形参的引用。另外,当定义符合标准库 iostream 规范的输入或输出操作符的时候,必须使它成为非成员操作符。
//设计
ostream& operator <<(ostream& os, const ClassType &object){
// any special logic to prepare object
// actual output of members
os << // ...
return os;
}
//示例
stream& operator << (ostream& out, const Sales_item& s){
out << s.isbn << "\t" << s.units_sold << "\t"
<< s.revenue << "\t" << s.avg_price();
return out;
}
运算符的重载一般我们会使用以下两种形式:1 成员函数;2 非成员函数,这两种定义不仅在语法上,在语义上也是有差别的。 语法上,定义为成员函数,比如 operator+=,只接受一个参数,而非成员函数接受2个参数
与输出操作符类似,输入操作符的第一个形参是一个引用,指向它要读的流,并且返回的也是对同一个流的引用。 它的第二个形参是对要读入的对象的非const 引用,该形参必须为非const,因为输入操作符的目的是将数据读到这个对象中。 输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性
istream& operator>>(istream& in, Sales_item& s){
double price;
in >> s.isbn >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}
同时定义前缀式操作符和后缀式操作符存在一个问题:它们的形参数目和类型相同,普通重载不能区别所定义的前缀式操作符还是后缀式操作符。
为了解决这一问题,后缀式操作符函数接受一个额外的(即无用的)int 型形参。使用后缀式操作符时,编译器提供 0 作为这个形参的实参。 尽管我们的前缀式操作符函数可以使用这个额外的形参,但通常不应该这样做。那个形参不是后缀式操作符的正常工作所需要的,它的唯一目的是使后缀函数与前缀函数区别开来。
// 定义后缀式操作符
class CheckedPtr {
public:
CheckedPtr& operator++(int);// prefix operators
CheckedPtr& operator--(int);
// other members as before
};
可以为类类型的对象重载函数调用操作符。一般为表示操作的类重载调用操作符。例如,可以定义名为 absInt 的结构,该结构封装将 int 类型的值转换为绝对值的操作:
//定义一个操作:函数调用操作符,该操作符有一个形参并返回形参的绝对值。通过为类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用
struct absInt {
int operator() (int val) {
return val < 0 ? -val :val;
}
};
//使用
int i = -42;
absInt absObj; // object that defines function call operator
unsigned int ui = absObj(i);
标准库定义了一组算术、关系与逻辑函数对象类,标准库还定义了一组函数适配器,
使我们能够特化或者扩展标准库所定义的以及自定义的函数对象类。这些标准库函数对象类型是在 functional
头文件定义的
类型 | 函数对象 | 所应用的操作符 |
---|---|---|
算术函数对象类型 | plus | + |
算术函数对象类型 | minus | - |
算术函数对象类型 | multiplies | * |
算术函数对象类型 | divides | / |
算术函数对象类型 | modulus | % |
算术函数对象类型 | negate | - |
关系函数对象类型 | equal_to | == |
关系函数对象类型 | not_equal_to | != |
关系函数对象类型 | greater | > |
关系函数对象类型 | greater_equal | >= |
关系函数对象类型 | less | < |
关系函数对象类型 | less_equal | <= |
逻辑函数对象类型 | logical_and | && |
逻辑函数对象类型 | logical_or | |
逻辑函数对象类型 | logical_not | ! |
plus<int> intAdd; // function object that can add two int values
negate<int> intNegate; // function object that can negate an int value
// uses intAdd::operator(int, int) to add 10 and 20
int sum = intAdd(10, 20); // sum = 30
// uses intNegate::operator(int) to generate -10 as second parameter
// to intAdd::operator(int, int)
sum = intAdd(10, intNegate(10)); // sum = 0
函数对象常用于覆盖算法使用的默认操作符。例如,sort 默认使用 operator<
按升序对容器进行排序。
为了按降序对容器进行排序,可以传递函数对象 greater。
该类将产生一个调用操作符,调用基础对象的大于操作符。如果 svec
是一个 vector 对象,参考下面代码:
//按降序对 vector 进行排序。像通常那样,传递一对迭代器以指明被排序序列。
//第三个实参用于传递比较元素的谓词函数。该实参greater 类型的临时对象,是一个将 > 操作符应用于两个 string 操作符的函数对象
// passes temporary function object that applies > operator to two
strings
sort(svec.begin(), svec.end(), greater<string>());
标准库提供了一组函数适配器,用于特化和扩展一元和二元函数对象。函数适配器分为如下两类:
- 绑定器,是一种函数适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
- 求反器,是一种函数适配器,它将谓词函数对象的真值求反。
标准库定义了两个绑定器适配器:bind1st 和 bind2nd
。每个绑定器接受一个函数对象和一个值。
bind1st 将给定值绑定到二元函数对象的第一个实参,bind2nd 将给定值绑定到二元函数对象的第二个实参。
例如,为了计算一个容器中所有小于或等于 10 的元素的个数,可以这样给 count_if 传递值:
count_if(vec.begin(), vec.end(),bind2nd(less_equal<int>(),10));
标准库还定义了两个求反器:not1 和 not2
。not1 将一元函数对象的真值求反,not2 将二元函数对象的真值求反。
为了对 less_equal 函数对象的绑定求反,可以编写这样的代码:
//首先将 less_equal 对象的第二个操作数绑定到 10,实际上是将该二元操作转换为一元操作。
//再用 not1 对操作的返回值求反,效果是测试每个元素是否 <=。然后,对结果真值求反。这个 count_if 调用的效果是对不 <= 10 的那些元素进行计数。
count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10)));