重载运算和类型转换¶
约 1027 个字 108 行代码 4 张图片 预计阅读时间 6 分钟
基本概念¶
-
重载的运算符是成员函数时,this绑定到左侧运算对象;成员运算符函数的显式参数数量比运算对象的数量少一个
-
对于一个运算符函数来说,它或者是类的成员或者至少含有一个类类型的参数
C++
// 调用了非成员函数
data + data2;
operator+(data, data2);
// 调用了成员函数
data1 += data2;
data1.operator+=(data2);
- 使用与内置类型一致的含义
- IO类型保持一致
- 如果定义了operator==,应该定义operator!=
- 包含一个内在的单序比较,定义operator <, operator>...
- 逻辑运算符返回bool;算术运算符返回一个类类型的值;赋值运算符返回左侧运算对象的一个引用
选择作为成员或非成员¶
输入和输出运算符¶
- 尽量减少格式化操作
-
输入输出运算符必须是非成员函数;一般声明为类的友元
-
读取操作发生错误时,输入运算符负责从错误中恢复
- IO标准库标识错误
- failbit
- eofbit表示文件耗尽
- badbit表示流被破坏
算术和关系运算符¶
赋值运算符¶
- 可以定义其他赋值运算符使用别的类型作为右侧运算符对象
C++
StrVec& StrVec::operator=(initializer_list<string> il)
{
auto data = alloc_n_copy(il.begin(), il.end());
free();
elements = data.first;
first_free = data.second;
return *this;
}
- 复合赋值运算符:可以不定义在类内;但最好定义在类内,返回类型与赋值运算符相同
下标运算符¶
- 必须为成员函数
- 以所访问元素的引用作为返回值
递增和递减运算符¶
成员访问运算符¶
C++
class StrBlobPtr {
public:
std::string& operator*() const
{
auto p = check(curr, "end");
return (*p)[curr];
}
std::string* operator->() const
{
return &this->operator*();
}
};
- 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象
函数调用运算符¶
- 必须是成员函数;该类的对象乘坐函数对象
- 函数对象常常作为泛型算法的实参
C++
class PrintString {
public:
PrintString(ostream& o = cout, char c = ' '):os(o),sep(c) {}
void operator()(const string& s) const {os << s << sep;}
private:
ostream &os;
char sep;
};
// 先初始化临时对象;调用operator()
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
lambda是函数对象¶
C++
[](const string & a, const string &b){return a.size() < b.size();}
// is equal to
class ShorterString {
public:
bool operator()(const string& a, const string& b) const{
return a.size() < b.size();
}
};
- 通过引用捕获变量,程序保证对象存在;编译器可以直接使用该引用
- 通过值捕获,需要建立捕获变量的数据成员,同时创建构造函数
C++
[sz](const string &a){return a.size() < sz;}
// is equal to
class SizeComp {
private:
size_t sz;
public:
SizeComp(size_t n):sz(n){}
bool operator()(const string &a) const }{return a.size() < sz;}
};
### 标准库函数对象
C++
// bind1st, bind2nd将一个二元函数变为一元函数
count_if(vec.begin(), vec.end(), bind2nd(greater<int>(), 1024));
可调用函数与function¶
- 不同类型可能具有相同的调用形式
- 但每个lambda有自己的类类型,与函数指针类型不相同
标准库function类型¶
C++
function<int(int,int)> f1 = add; // 函数指针
function<int(int,int)> f2 = divide(); // 函数对象类的对象
function<int(int,int)> f3 = [](int i, int j){return i*j;} // lambda
- 不能将重载函数名字存储在function类型对象中;使用函数指针
重载类型转换与运算符¶
-
operator type() const;
-
类型转换符时隐式执行的,无法给函数传递实参;不能在定义中使用任何形参;但会返回一个对应类型的值
- 不指定返回类型
显示的类型转换运算符¶
- 隐式转换可能因为整型提升造成意料之外的结果
C++
class SmallInt {
public:
explicit operator int() const {return val;}
};
static_cast<int>(si) + 3; // 必须显示地请求类型转换
- IO标准库定义一个向bool的显示转换(
while (std::cin >> value)
)
避免二义性的类型转换¶
- 不要为类定义相同的类型转换;不要定义两个及以上转换目标是算术类型的转换
- 不要在两个类之间构建相同的类型转换
- 如果类定义了一组类型转换,他们的转换源或者转换目标类型本身可以通过其他类型转换联系在一起会造成二义性问题
- 使用两个用户定义的类型转换时,如果转换函数之前或之后存在标准类型转换,标准类型转换决定最佳匹配
- 如果两个类型转换提供了同一种可行匹配,类型转换一样好
C++
struct C {C(int);};
struct D {D(int);};
void manip(const C& c);
void manip(const D& e);
manip(10); // 二义性错误
- 如果两个用户定义的类型转换都提供了可行匹配;不考虑标准类型转换级别
C++
struct C {C(int);};
struct E {E(double);};
void manip(const C& c);
void manip(const E& e);
manip(10); // 二义性错误
函数匹配与重载运算符¶
- 不能通过调用的形式来区分成员函数或非成员函数
- 同一名字的成员函数和非成员函数不会彼此重载,运算符的候选函数集需要包含二者