小 C 上一讲刚学会写类,很快就用上了。为了做一道"矩阵快速幂"的题,他写了一个 Matrix 类,把矩阵乘法实现成成员函数。
逻辑完全正确,但写出来是这样的:
r = Matrix::mul(r, a); a = Matrix::mul(a, a);
而他心里想的数学表达式明明只有两行:
r = r * a; a = a * a;
接着他做了一道"维护不重复元素集合"的题。他写了 Point 结构,还专门写了一个比较函数 bool cmp(Point a, Point b),用 sort(v.begin(), v.end(), cmp) 一切正常。
可是当他写下 set<Point> s; 时,编译器毫不客气地吐了一屏错误:
error: no match for 'operator<' (operand types are 'const Point' and 'const Point')
他觉得很委屈:我明明写了比较函数啊,为什么 set 不认?
小 D 看完说了一句话:你写的 cmp 是你自己的函数,set 不知道它的存在。set 只认 operator<。接着小 D 在 Point 里加了四行:
friend bool operator<(const Point& a, const Point& b) { return a.x != b.x ? a.x < b.x : a.y < b.y; }
加完之后,sort 不用给比较函数了,set 能用了,priority_queue 也能用了。小 C 这才明白:运算符重载不是"炫技的语法糖",而是让自己的类型能和标准库说上话的唯一通道。
标准库只认运算符,不认你的函数名。
💡 运算符重载到底解决了什么
问题一 · 表达不自然。数学里写 a + b、a * b、a < b,代码里却要写 add(a,b)、mul(a,b)、cmp(a,b)。符号是通用的,函数名是私有的——重载让自定义类型获得与其他类型一致的"表达方式"。
问题二 · 无法接入标准库。这是最关键的一点。标准库的大量组件是按运算符约定编写的:sort 默认用 <、set/map 用 < 维护有序性、priority_queue 用 < 比较优先级、cout 用 << 输出、cin 用 >> 读入。你的类型想被它们接受,就必须提供对应的运算符。
问题三 · 代码重复与不一致。如果每次排序都要传一个比较函数,那"什么算更小"这个规则就散落在各处;某处改了、某处忘了改,就会出现"同一个集合,两处排序结果不同"的诡异现象。把规则写成 operator<,全项目只有一份定义。
结论:运算符重载是"让自定义类型融入 C++ 生态"的手段。它不改变运算符本身的行为,不改变优先级,也不改变结合性——只是告诉编译器:当操作数是你自己的类型时,这个符号该做什么。