首页 > 代码库 > C++学习笔记8-操作符重载
C++学习笔记8-操作符重载
1. 重载操作符必须具有一个类类型操作数
用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:
// error: cannotredefine built-in operator for ints
int operator+(int, int);
也不能为内置数据类型重定义加号操作符。例如,不能定义接受两个数组类型操作数的operator+。
重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。
2. 优先级和结合性是固定的
操作符的优先级、结合性或操作数目不能改变。不管操作数的类型和操作符的功能定义如何,表达式
x == y +z;
总是将实参y 和z 绑定到operator+,并且将结果用作operator== 右操作数。 有四个符号(+,-, * 和&)既可作一元操作符又可作二元操作符,这些操作符有的在其中一种情况下可以重载,有的两种都可以,定义的是哪个操作符由操作数数目控制。除了函数调用操作符operator() 之外,重载操作符时使用默认实参是非法的。
3. 类成员与非成员
重载一元操作符如果作为成员函数就没有(显式)形参,如果作为非成员函数就有一个形参。类似地,重载二元操作符定义为成员时有一个形参,定义为非成员函数时有两个形参。
类Sales_item 中给出了成员和非成员二元操作符的良好例子。我们知道该类有一个加号操作符。因为它有一个加号操作符,所以也应该定义一个复合赋值(+=)操作符,该操作符将一个Sales_item 对象的值加至另一个Sales_item对象。一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员:
// member binaryoperator: left-hand operand bound to implicit this pointer
Sales_item&Sales_item::operator+=(const Sales_item&);
// nonmember binaryoperator: must declare a parameter for each operand
Sales_itemoperator+(const Sales_item&, const Sales_item&);
4. 不要重载具有内置含义的操作符
赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就自己定义以下这些操作符。
? 合成赋值操作符(第13.2 节)进行逐个成员赋值:使用成员自己的赋值:使用成员自己的赋值操作依次对每个成员进行赋值。
? 默认情况下,取地址操作符(&)和逗号操作符(,)在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。
? 内置逻辑与(&&)和逻辑或(||)操作符使用短路求值。如果重新定义该操作符,将失去操作符的短路求值特征。
通过为给定类类型的操作数重定义操作符,可以改变这些操作符的含义。
重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。
有时我们需要定义自己的赋值运算。这样做时,它应表现得类似于合成操作符:赋值之后,左右操作数的值应是相同的,并且操作符应返回对左操作数的引用。重载的赋值运算应在赋值的内置含义基础上进行定制,而不是完全绕开。
5. 审慎使用操作符重载
每个操作符用于内置类型都有关联的含义。例如,二元+ 与加法是完全相同的。将二元+ 对应到一个类类型的类似操作可提供方便的简写方法。例如,标准库的类型string,遵循许多程序设计语言的通用规范,使用+ 表示连接——将一个串“加”至另一个串。
当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用。使用重载操作符而不是创造命名操作,可以令程序更自然、更直观,而滥用操作符重载使得我们的类难以理解。
在实践中很少发生明显的操作符重载滥用。例如,不负责任的程序员可能会定义operator+ 来执行减法。更常见但仍不可取的是,改变操作符的“正常”含义以强行适应给定类型。操作符应该只用于对用户而言无二义的操作。在这里所谓有二义的操作符,就是指具有多个不同解释的操作符。
当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。如果不是普通操作,没有必要为简洁而使用操作符。
6. 选择成员或非成员实现
为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,程序员没有选择,操作符必须是成员;在另一些情况下,有些经验原则可指导我们做出决定。下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数:
? 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。
? 像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。
? 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。
? 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
7. 赋值必须返回对*this 的引用
string 赋值操作符返回string 引用,这与内置类型的赋值一致。而且,因为赋值返回一个引用,就不需要创建和撤销结果的临时副本。返回值通常是左操作数的引用,例如,这是Sales_item 复合赋值操作符的定义:
// assumes that bothobjects refer to the same isbn
Sales_item&Sales_item::operator+=(const Sales_item& rhs)
{
units_sold +=rhs.units_sold;
revenue +=rhs.revenue;
return *this;
}
一般而言,赋值操作符与复合赋值操作符应返回操作符的引用。
8. 原型下标操作符
类定义下标操作符时,一般需要定义两个版本:一个为非const 成员并返回引用,另一个为const 成员并返回const 引用。
下面的类定义了下标操作符。为简单起见,假定Foo 所保存的数据存储在一个vector<int>: 中:
class Foo {
public:
int &operator[](const size_t);
const int&operator[] (const size_t) const;
// other interfacemembers
private:
vector<int>data;
// other member dataand private utility functions
};
下标操作符本身可能看起来像这样:
int&Foo::operator[] (const size_t index)
{
return data[index];// no range checking on index
}
const int&Foo::operator[] (const size_t index) const
{
return data[index];// no range checking on index
}
9. 转换可能引起内置操作符的二义性我们再次扩展SmallInt 类。这一次,除了到int 的转换操作符和接受int 参数的构造函数之外,将增加一个重载的加操作符:
class SmallInt {
public:
SmallInt(int = 0); //convert from int to SmallInt
// conversion to intfrom SmallInt
operator int() const{ return val; }
// arithmetic operators
friend SmallInt
operator+(constSmallInt&, const SmallInt&);
private:
std::size_t val;
};
现在,可以用这个类将两个SmallInts 对象相加,但是,如果试图进行混合模式运算,将会遇到二义性问题:
SmallInt s1, s2;
SmallInt s3 = s1 +s2; // ok: uses overloaded operator+
int i = s3 + 0; //error: ambiguous
第一个加使用接受两个SmallInt 值的+ 的重载版本。第二个加有二义性,问题在于,可以将0 转换为SmallInt 并使用+ 的SmallInt 版本,也可以将 s3 转换为int 值并使用int 值上的内置加操作符。
既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性。
10. 可行的操作符函数和转换
通过为每个调用列出可行函数,可以理解这两个调用的行为。在第一个调用中,有两个可行的加操作符:
?operator+(const SmallInt&, const SmallInt&)
?The built-in operator+(int, int)
内置的operator+(int, int)。
第一个加不需要实参转换——s1和s2 与形参的类型完全匹配。使用内置加操作符对两个实参都需要转换,因此,重载操作符与两个实参匹配得较好,所以将调用它。对于第二个加运算:
int i = s3 + 0; //error: ambiguous
两个函数同样可行。在这种情况下,重载的+ 版本与第一个实参完全匹配,而内置版本与第二个实参完全匹配。第一个可行函数对左操作数而言较好,而第二个可行函数对右操作数而言较好。因为找不到最佳可行函数,所以将该调用标记为有二义性的。