运算符重载-operator函数与五个不能重载的符号

运算符重载:operator 函数、五个不能重载的符号,与 << 的真相

我的github:(https://github.com/xcx55/ubuntu-linux-project)

感谢各位大佬参观我的github!!

类和对象"中"的下一段:运算符重载。它是 C++ 里"语法糖浓度"最高的机制------q1 == q2、cout << d1、q[i] 这些写法背后全是函数调用。这篇从"为什么需要它"讲到"哪些不能重载",最后拆掉 cout 的神秘感。

一、为什么内置类型不需要它,自定义类型离不开它

内置类型的比较,硬件直接支持------汇编里有现成的 cmp 指令 比较数据大小。所以 3 < 5 天经地义。

自定义类型呢?写个日期类,q1 < q2 直接编译报错:

复制代码
error C2676: 二进制"<": "date"不定义该运算符...
E0349: 没有与这些操作数匹配的 "<" 运算符

编译器不知道两个 date 怎么比------按年比?按月比?这个"语义"只有类的作者知道。运算符重载就是让类的作者把运算符的语义写出来:当运算符被用于类类型对象时,C++ 规定必须转换成调用对应的运算符重载函数,没有对应重载就编译报错。

形式上它就是个函数:名字 = operator + 运算符,有返回类型、参数列表、函数体:

cpp 复制代码
bool operator==(const Date& x1, const Date& x2)
{
    return x1._year == x2._year
        && x1._month == x2._month
        && x1._day == x2._day;
}

二、参数个数的两条规则

  1. 参数个数 = 运算对象的数量:一元运算符一个参数,二元运算符两个------左操作数传第一个参数,右操作数传第二个;
  2. 成员函数版本参数少一个:作为成员函数时,第一个运算对象默认传给隐式的 this 指针,所以参数从左边少一个。

两种调用方式完全等价:

cpp 复制代码
bool a = q1 == q2;             // 隐式调用,编译器转成下面的
bool b = operator==(q1, q2);   // 全局函数版:显式调用
bool c = q1.operator==(q2);    // 成员函数版:显式调用

q1 == q2 不是什么魔法,就是一次函数调用------重载之后运算符的优先级和结合性跟内置版本保持一致,所以写起来跟原生类型没差别。

private 成员怎么访问?

全局的 operator 函数要访问对象的私有成员,绕不开这个问题。三种思路:

  1. 提供 Getxxx 函数(把成员读出来);
  2. 友元(friend,后面细讲);
  3. 直接把运算符重载函数写进类里------最常用。

写进类里还有个附带的好处:成员函数自带 this 指针,this == a.year 的比较直接写在函数体里;参数也顺势少一个。参数建议写成 const Date&:引用避免传值传参触发拷贝构造,const 保证不改被比较的对象。

三、重载的边界:五个不能碰的符号

自由是有边界的,选择题常考:

  • 不能创造语言里没有的运算符 :operator@ 不行,运算符得是语法中存在的;
  • 五个运算符不能重载 :.* :: sizeof ?: .;
  • 至少要有一个类类型参数 :不能改内置类型的含义------int operator+(int x, int y) 编译器直接拒绝,3 + 5 的语义谁都别想动;
  • 重载要重载得有意义 :看运算符对这类对象有没有自然语义------Date 类重载 operator-(算两个日期差几天)有意义,重载 operator+ 就没意义(两个日期相加是什么?)。一个类需要哪些运算符,是看重载后有意义的那些。

四、cout 的真面目:一堆 operator<< 的重载

之前入门篇说过 cout 能"自动识别类型",现在能拆穿了:cout 是 ostream 类创建的对象 ,"自动识别"就是函数重载------ostream 里把 operator<< 按所有内置类型重载了个遍(bool、short、int、unsigned、long、float、double、void*......),cout << i 就是 cout.operator<<(i),一次输出就是一次函数调用,连续输出就是多次链式调用。

那给自定义类型重载 << 呢?这里有个反直觉的规定:重载 << 和 >> 必须写成全局函数,不能是成员函数。

原因是 this 指针的位置:成员函数版第一个参数永远是 this(左操作数),写成成员函数的话,调用 d1 << cout 才能匹配------变成"对象 << cout",反人类 。想要 cout << d1 这个自然顺序,就必须把 ostream 放在第一个参数位置,只能全局函数:

cpp 复制代码
ostream& operator<<(ostream& out, const Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;   // 返回流对象本身,支持连续输出
}

返回 ostream& 是为了 cout << d1 << d2 这种链式写法------第一次调用返回 cout,接着当第二个调用的左操作数。

五、operator\[\]:让自定义类型像数组一样用

再补一个实用的:operator[]。给 Stack 加上它:

cpp 复制代码
int& operator[](int i)
{
    return _a[i];
}

stack q;
for (int i = 0; i < 10; i++)
    q[i] = i;   // 像 C 数组一样读写

q[i] 就是 q.operator[](i),返回引用才能既读又写。C 语言里"数组下标"是内置类型的特权,重载之后自定义类型也享受同款语法------STL 的 vector、string 能用 [],全是这套机制的产物。


串一下:内置类型有 cmp 硬件支持 → 自定义类型没有 → 运算符重载把语义写成函数(operator+运算符)→ 成员函数版 this 占掉第一个参数 → 全局版要解决 private 访问(三种思路)→ 五个符号不能重载、内置类型含义不能改 → cout 的"自动识别"就是 ostream 里的一排 operator<< 重载 。运算符重载讲完,下一篇写其中最特殊的那个------赋值运算符 =,六大默认成员函数的最后一块拼图。

相关推荐
傲世仙尊2 小时前
赋值运算符重载-从无到有与从有到有六大函数收官
c++
晴天的雨.9922 小时前
【C++】模板进阶
开发语言·c++·算法
Shadow(⊙o⊙)3 小时前
仿muduo实现高并发服务器——时间轮timewheel.cpp
c语言·c++
WL学习笔记15 小时前
C & C++ 内存管理
c++·c++内存管理
宛 禾17 小时前
Qt 控件入门:QLabel 从拖拽到对象树,一篇讲透
c++·qt·学习
hetao173383717 小时前
联盟场-提高组-R7 & ZYZSC-S-Round 11
c++·算法
程序喵大人17 小时前
【C++入门】编译链接模型 - 07 链接器怎样把符号对上号
开发语言·c++·编译器·编译链接·链接器
局部有解18 小时前
Hot100 - 三数之和
数据结构·c++·算法
longlongzihan18 小时前
C++ 常用数据结构速查:vector、stack、queue、list、树、图、哈希表等声明与核心操作
数据结构·c++·list