C++ 学习笔记(14)重载运算与类型转换、函数对象、function库
参考书籍:《C++ Primer 5th》
API:运算符重载
14.1 基本概念
- 除了重载的函数调用运算符operater( ) 之外,其他重载运算符不能含有默认实参。
- 如果运算符函数是成员函数,左侧运算对象绑定到隐式this指针。
&&、||和,在重载时顺序规则失效。(C++17前)&&、||:同时失去短路求值属性,即两个运算对象都会被求值。

- 定义成员函数或非成员函数选择
- 成员函数:
- 必须:赋值(
=)、下标([ ])、调用(( ))、成员访问箭头(-) - 一般:复合运算符(
+= -=等)、递增递减(++ --)、解引用(&)
- 必须:赋值(
- 非成员函数:
- 算术、相等性、关系、位运算等。
- 成员函数:
string s = "string";
string t = s + "!"; // 正确,等价 s.operator("!")
string u = "hi" + s; // 正确,等价operator+("hi",s)。说明这个操作符重载的是非成员函数。
// 如果是成员函数:const char* 作为左侧对象,却没有这种操作,错误。
14.2 输入和输出运算符
14.2.1 重载输出运算符<<
- 通常输出运算应该只负责打印内容,而非控制格式,输出运算符不应该打印换行符。
- IO运算符一般被声明为友元(以访问类的非公有成员)。
14.2.2 重载输入运算符>>
- 输入运算符必须处理输入可能失败的情况,而输出不用。
14.3 算术和关系运算符
- 通常把算术和关系运算符定义成非成员函数以及允许对左侧或右侧运算对象进行转换。
14.4 赋值运算符
- 除了拷贝和移动,vector还定义了:接受花括号的元素列表(
std::initializer_list<T>)作为参数)。见std::vector::operator=。
14.5 下标运算符
- 一般包含两个版本下标运算符:一个返回普通引用,另一个是类的常量且返回常量引用。
14.6 递增和递减运算符
- 后置版本介绍一个额外(不使用)int类型形参,以区分前置版本。
class A
{
public:
A operator++(); // 前置版本,使用:++a 或 a.operator();
A operator++(int); // 后置版本,使用:a++ 或 a.operator(0);
}
14.7 成员访问运算符
- 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。
14.8 函数调用运算符
- 如果定义了调用运算符,这个类称作函数对象(function object)。
14.8.1 lambda是函数对象
- 编译器将lambda表达式翻译成:未命名类的未命名对象。
- lambda表达式产生的类不含默认构造函数、赋值运算、析构函数;而默认拷贝/移动构造函数通常要看捕获的数据成员类型而定。
14.8.2 标准库定义的函数对象

vector<string *> vs;
sort(s.begin(), s.end(), [](string *a,string *b)) { return a < b; }; // 错误。指针之间没有关系运算。
sort(s.begin(), s.end(), less<string *>); // 正确,按内存地址排序。
14.8.3 可调用对象与function

int add(int i, int j) { return i + j; } // 普通函数
auto mod = [](int i, int j) {return i % j; }; // lambda,函数对象
struct divide { int operator()(int i, int j) { return i / j; } }; // 函数对象
void main()
{
function<int(int, int)> f1 = add; // 函数指针
function<int(int, int)> f2 = mod; // lambda
function<int(int, int)> f3 = divide(); // 函数对象类的对象
cout << f1(7, 2) << endl << f2(7, 2) << endl << f3(7, 2) << endl; // 9 1 3
}
14.9 重载、类型转换与运算符
14.9.1 类型转换运算符
- 转换的类型需要能作为返回值。所以不允许转换成数组或函数类型。
- 类型转换运算符没有显示的返回类型,也没有形参,必须定义为类的成员函数。
class AbsInt
{
public:
AbsInt(int i = 0) { val = abs(i); } // int 转换成 AbsInt
operator int() const { return val; } // AbsInt 转换成 int
int val;
};
void main()
{
AbsInt ai1;
ai1 = -233; // 先把 -233 隐式转换成 AbsInt,然后调用 AbsInt::operator=
AbsInt ai2 = ai1 - 666; // 先把 ai2 隐式转换 int,然后进行整形减法,结果(-433)作为类的隐式构造函数参数。
cout << ai1.val << " " << ai2.val << endl; // 233 433
}
- 使用显示类型转换符(explicit),来标记类型转换必须显示调用。
- 如果表达式作为条件,编译器会将显示类型自动转换,即显示类型却被隐式执行:
- if、for、while和do语句的条件部分
- 逻辑运算符(
!、||、&&)的运算对象 - 条件运算符(
? :)的条件部分
14.9.2 避免有二义性的类型转换
- 不要令两个类执行相同的类型转换,如A类接受B类的构造函数,同时B类接受A类的构造函数。
- 避免转换成内置算术类型,如果已经定义了一个:
- 不要再定义接受算术类型的重载运算符。
- 不要定义转换到多种算术类型的类型转换。
14.9.3 函数匹配与重载运算符
- 一个类同时提供转换目标是算术类型的转换,和重载运算符,会产生二义性。
class AbsInt
{
friend AbsInt operator+(const AbsInt &a, const AbsInt &b) { return A(a.val + b.val); }
...
}
void main()
{
AbsInt a1,a2;
AbsInt a3 = a1 + a2; // 正确。使用重载 operator+
int i = a3 + 666; // 错误。产生二义性,1:a3可以转换int,执行整形加法,2:666转换成AbsInt,执行重载加法
}

本文详细介绍了C++中运算符重载的基本概念及应用,包括不同类型运算符的重载方式选择、输入输出运算符的重载实现、算术与关系运算符的定义技巧等内容,并探讨了类型转换运算符的注意事项。
1003

被折叠的 条评论
为什么被折叠?



