1、操作符重载定义
具有特殊名称的函数,保留字operator后接需定义的操作符符号。
2、操作符重载需要注意的地方
不能创建任何新的操作符
重载的操作符必须具有一个类类型或枚举类型的操作数
重载的操作符不能改变原有操作符的优先级和结合性
重载的操作符不具备短路求值特性
3、重载操作符的设计原则
必须设置为成员函数的操作符有:赋值(=)、下标([])、调用(())、成员访问箭头(->)
通常设定为成员函数的操作符有:复合操作符(+= ,-=)、自增(++)、自减(--)、解引用(*)
通常设定为普通非成员函数的有:算数操作符、相等操作符、关系操作符、位操作符(一般是对称的操作符)
4、操作符重载
(1)输入和输出操作符
输出操作符的返回值一般为引用,因为输出流的拷贝构造函数是私有的,无法完成输出流的拷贝操作。它的参数一般为输出流的引用和const类型对象,之所以采用const类型,是为了防止对象在输出操作中被修改状态,另外输出操作符通常要尽可能少地格式化,尤其是不要输出换行符,要让用户自己控制输入细节。IO操作符必须为非成员函数,如果是成员函数,那么我们必须在使用地时候用:item << cout形式,这违背我们的使用原则。另外在重载输出符时,一般要加friend关键字,这样就可以对对象的私有成员进行访问了。重载示例如下:
ostream& operator<<(ostream& os, const ClassType& object)
{
os << // ...
return os;
{
输入操作符的返回值也是一个引用,它指向它要读的流,第一个参数是一个输入流引用,,因为输入操作符要改写对象的状态,所以第二个参数是对要读入的对象的非const引用。输入操作符与输出操作符的区别是它必须对错误情况或文件结束情况有所考虑,如当读取数据出现错误时,我们可以new一个新对象作为返回值,并且指出错误,如设置failbit等。示例代码如下:
istream& operator>>(istream& in, Sales_item& s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
if(in)
s.revenue = s.units_sold * price;
else
s = Sales_item();
return in;
}
(2)算数操作符和关系操作符
算数操作符不改变操作数的状态,所以返回值是类类型而非引用,两个操作数分别是const类型的对象,一般既定义了算数操作符又定义了相关的复合赋值操作符的类,使用复合赋值实现算数操作符。如果使用算数操作符来实现复合操作符,我们就需要创建和撤销一个临时对象,没有前一种实现效率高,示例代码如下:
Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs);
ret += rhs;
return ret;
}
一般定义了相等操作符,也要定义不等操作符,而不等操作符的实现大多数是在相等操作符的基础上实现的,相等操作符和不等操作符的重载模板如下:
inline bool operator==(const Sales_item &lhs, const Sales_item &rhs)
{
}
inline bool operator!=(const Sales_item &lhs, const Sales_item& rhs)
{
return !((lhs == rhs)
}
(3)赋值操作符
赋值操作符必须返回对*this的引用,并且必须定义为成员函数,而复合赋值操作符不一定要定义为成员函数,赋值操作符的参数为const类型的引用。
(4)下标操作符
定义下标操作符比较复杂的地方在于,它的左右操作数都应表现正常,下标操作符出现在左边,必须生成左值,返回引用类型。可以对const和非const对象使用下标,应用const对象时,返回值是const引用,不能作为赋值目标,并且只能调用const函数来实现操作,示例代码如下:
int& operator[](const size_t);
const int &operator[](const size_t) const;
(5)成员访问操作符
成员访问操作符包括解引用操作符(*)和箭头操作符(->),箭头操作符必须定义为类成员函数,解引用操作符不要求定义为成员函数,两个都要定义const和非const版本,并且箭头操作符必须返回指向类类型的指针或定义了自己的箭头操作符的类类型对象。如果返回类型是类类型的其他的对象,则讲递归应用该操作符。示例代码如下:
Screen& operator*();
Screen* operator->();
const Screen& operator*() const;
const Screen* operator->() const;
(6)自增和自减操作符
自增和自减操作符的前缀形式要求返回(*this)的引用,没有参数。使用时要区分好前缀与后缀的形式,后缀操作符接受一个额外的int形参,编译器提供0作为这个参数的实参。自增自减操作符的示例代码如下:
CheckedPtr& operator++();
CheckedPtr& operator--();
CheckedPtr operator--(int); //后缀形式,在前缀的基础上定义的
5、函数对象
(1)函数对象
定义了调用操作符的类,其对象称为函数对象。如:
struct absInt
{
int operator()(int val)
{
return val < 0 ? -val :val;
}
};
absInt absObj;
unsigned int ui = absObj(i);
函数对象可以比函数更灵活,比如:
bool GT6(const string &s)
{
return s.size() >= 6;
}
这个函数在用于count_if函数中时,已将数字6固化到GT6函数中,如果想使用更灵活,那么函数对象就派上用场了。
class GT_cls
{
public:
GT_cls(size_t val = 0):bound(val)
{
}
bool operator()(const string &s)
{
return s.size() >= bound;
}
private:
string::size_type bound;
};
count_if(words.begin(), words.end(), GT_cls(6)); //这里的6不会固化可以更换任何数字
标准库定义的函数对象,有三种类型:算数、关系和逻辑函数对象,它们都定义在functional头文件中,如:
plus<int> intAdd;
int sum = intAdd(10, 20);
(2)函数适配器
函数适配器用于特化和扩展一元和二元函数对象,主要分为两类:绑定器和求反器。绑定器,是通过将一个操作数绑定到给定值而将二元函数对象转化为一元函数对象。求反器,是将谓词函数对象的真值求反。标准库定义了两个绑定器,bind1st和bind2nd,bind1将定值绑定到二元函数对象的第一个实参,bind2nd将给定值绑定到二元函数对象的第二个实参。如:
cout_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));
标准库还定义了两个求反器not1和not2,not1将一元函数对象的真值求反,not2将二元函数对象的真值求反。如:
count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int(), 10)));
6、转换与类类型
(1)转换操作符
转换操作符是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换,转换操作符在类定义体内声明,在保留字operator之后跟着转换的目标类型,一般而言,不允许转化为数组或函数类型,可以转化为指针或引用类型。转换函数不指定返回类型,但是必须显示地返回一个指定类型的值。转换分为类类型转换和标准转换,标准转换要优先于类类型转换,连续两个标准转换是可以的,但是类类型转换不可以紧接着另一个类类型转换。示例代码如下:
class SmallInt
{
public:
operator int() const
{
return val;
}
private:
size_t val;
};