14.1 基本概念
- 通常情况下,不应该重载逗号,取地址,逻辑与,逻辑或运算符
- 重载运算符至少含有一个类类型的参数。
14.2.1 重载输出运算符<<
ostream& operator<<(ostream& os,const Sales_data& item);
- 通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符
- IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明成友元
14.2.2 重载输入运算符>>
istream& operator>>(istream& is,Sales_data &item);
- 输入运算符必须处理输入可能失败的情况,而输出运算符不需要
std::istream& operator>>(std::istream& is, Sales_data& item)
{
double price = 0.0;
is >> item.bookNo >> item.units_sold >> price;
if (is)
item.revenue = price * item.units_sold;
else
item = Sales_data();
return is;
}
14.3 算术和关系运算符
- 类定义了算术运算符,通常也会定义一个对应的复合赋值运算符。此时,最有效的方式是使用复合赋值运算符来定义算术运算符
Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;
sum += rhs;
return sum;
}
14.3.1 相等运算符
- 类定义了operator==通常也应该定义operator!=
bool operator==(const StrVec& lhs, const StrVec& rhs)
{
return (lhs.size() == rhs.size() &&
std::equal(lhs.begin(), lhs.end(), rhs.begin()));
}
bool operator!=(const StrVec& lhs, const StrVec& rhs)
{
return !(lhs == rhs);
}
14.4 赋值运算符
- 赋值运算符必须定义成类的成员,复合赋值运算符通常情况下也应该这样做。这两类运算符都应该返回左侧运算对象的引用
Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
14.5 下标运算符
- 如果一个类包含下标运算符,则他通常会定义两个版本,一个返回普通引用,另一个是类的常量成员并且返回常量引用
std::string& operator[](size_t n) { return elements[n]; }
const std::string& operator[](size_t n) const { return elements[n]; }
14.6 递增和递减运算符
- 定义递增和递减运算符的类应该同时定义前置版本和后置版本。这些运算符通常应该被定义成类的成员
StrBlobPtr& StrBlobPtr++();//前置版本
StrBlobPtr StrBlobPtr++(int);//后置版本
p.operator++(0);//调用后置版本++,传入的值会被运算符函数忽略。
14.7 成员访问运算符
- 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象
14.8 函数调用运算符
- 函数对象(定义了函数调用运算符的类的对象)常常作为泛型算法的实参
absInt absObj;
int ui=absObj(j);//注意调用方式,另一个absInt对象最用于一个实参列表
14.8.1 lambda 是函数对象
- 在lambda表达式产生的类中含有一个重载的函数调用运算符
- 若lambda采用值捕获方式,这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,另其使用捕获的变量的值来初始化数据成员
14.8.2 标准库定义的函数对象
- 标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类(类模板)
14.8.3 可调用对象与function
- 可调用对象也有类型,两个不同类型的可调用对象却可能共享同一种调用形式。
function<T> f;//f是一个用来存储可调用对象的空function,这些可调用对象的调用形式应该与函数类型T相同。
map<string,function<int(int,int)>> binops;
- 我们不能直接将重载函数的名字存入function类型的对象中,会产生二义性。通过存储函数指针而非函数名字可以解决此问题:
int (*fp)(int,int)=add;
binops.insert({"+",fp});
或者使用lambda来消除二义性
binops.insert({"+",[](int a,int b){return add(a,b);}});
14.9.1 类型转换运算符
- 转换构造函数和类型转换运算符共同定义了类类型转换
- 类型转换运算符是类的一种特殊成员函数
operator type() const
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
SmallInt si;
si=4;//
首先将4隐式转换成SmallInt,然后调用SmallInt::operator
========================================
si+3;//首先将si隐式地转换成int,然后执行整数的加法
- 编译器一次只能执行一个用户定义的类型转换。
- 尽管类型转换原函数不负责指定返回类型,但实际上每个类型转换函数都会返回一个对应类型的值
- 显示的类型转换运算符,编译器不会自动执行这一类型转换:
explicit operator int() const {return val;}
si+3//error:此处需要隐式类型转换,但类的类型转换运算符是显式的
。static_cast<int>(si)+3;//正确:显示请求类型转换
- 上述规定存在一个例外:当表达式被用作条件是,编译器会将显示的类型转换自动应用于它,即显示的类型转换将被隐式执行
- 向bool的类型转换通常用在条件部分,因此operator bool一般定义成explicit的
14.9.2 避免有二义性的类型转换
一言以蔽之:除了显式地向bool类型的转换之外,我们应该尽量避免定义类型转换函数并尽可能限制那些“显然正确”的非显示构造函数
14.9.3 函数匹配与重载运算符
- 表达式中运算符的候选函数集级应该包括成员函数,也应该包括非成员函数。