【C++ 面试真题】聊聊 C++ 的运算符重载

📅 发布时间:2026/8/18 19:48:04
【C++ 面试真题】聊聊 C++ 的运算符重载 【C 面试真题】聊聊 C 的运算符重载运算符重载是 C 面试里特别能考察功底的一道题。会用operator只是入门真正考你的是哪些能重载、怎么写、为什么有些只能写成成员函数、自增自减的前后置怎么区分。一道题能从语法一路问到设计哲学。本文用问答的方式把运算符重载一次讲透。一、先说结论什么是运算符重载❓ 什么是运算符重载为什么 C 要提供这个特性✅ 运算符重载就是给已有的运算符赋予新的含义让它能作用于自定义类型。比如内置的只能算 int、double。但你想让两个复数对象也能相加structComplex{doubler,i;// 重载 号Complexoperator(constComplexo)const{return{ro.r,io.i};}};Complex a{1,2},b{3,4};Complex cab;// c {4, 6}a b实际调用了a.operator(b)读起来却像内置类型一样自然。设计哲学加分点运算符重载的初衷是让自定义类型用起来像内置类型提高代码可读性。C 之父 Bjarne 的忠告是“运算符重载应该让代码更清晰而不是用来炫技。”如果一个让读者看不出在干嘛那就该老老实实写个函数。一个反面教材有人给字符串类重载/表示拼接看起来很酷但读到s1 / s2时谁也猜不出它是啥意思。运算符的语义要符合直觉——就是合并、就是相等、就是有序。一旦违背常识可读性反而下降。二、哪些运算符能重载哪些不能❓ 是不是所有运算符都能重载✅ 不是。C 里绝大多数运算符可重载但有5 个硬性不能重载外加几个几乎不该重载的。绝对不能重载的 5 个运算符含义为什么不能::作用域解析基础语法无法重定义.成员访问基础语法.*成员指针访问基础语法?:三目条件含 3 个操作数语义难定义sizeof取大小编译期求值无运行期调用⚠️注意C20C20 引入了operator飞船运算符但.仍不能重载——C20 给的是默认比较不是真重载.。能重载但强烈不建议重载的、||重载后会失去短路求值两个操作数都会算几乎总是个坑,逗号重载后求值顺序的保证丢失语义混乱。口诀::..*?:sizeof五个不能动||,三个别去动。三、两种写法成员函数 vs 全局函数❓ 运算符重载写成成员函数和全局函数有什么区别✅ 这是最高频的追问点。同一个运算符有两种写法区别在于左操作数是谁。写法一成员函数——左操作数必须是this对象structComplex{doubler,i;// 成员左操作数是 *thisComplexoperator(constComplexo)const{return{ro.r,io.i};}};写法二全局友元函数——左操作数可以是任意类型structComplex{doubler,i;// 开放访问让全局函数读私有friendComplexoperator(constComplex,constComplex);};// 左右都是 Complex全局实现Complexoperator(constComplexa,constComplexb){return{a.rb.r,a.ib.i};}为什么要全局函数当左操作数不是你的类时成员函数写不出来。最经典的例子——cout objstructPoint{intx,y;};// 左操作数是 ostream不是 Point// 只能用全局函数std::ostreamoperator(std::ostreamos,constPointp){returnos(p.x,p.y);}// 用法Point p{1,2};std::coutp;// 打印 (1,2)cout是标准库的类你没法给它加成员函数所以只能写成全局。加分点有几个运算符必须写成成员函数见下一节其余的二者皆可。经验法则二元运算符-*等优先写全局能保证左右操作数都支持隐式转换尤其当构造函数未加explicit时差别明显。举个具体差别成员函数写a b时a必须是目标类型隐式转换只在b上发生全局函数则左右都能隐式转换。如果你的类能从int隐式构造那1 obj用成员函数写不出来左操作数 1 不是对象全局函数却可以。四、哪些运算符必须写成成员函数❓ 有没有运算符强制要求写成成员函数✅ 有。下面这4 个必须是成员函数全局写法编译直接报错运算符原因赋值与对象身份绑定()函数调用依赖对象[]下标依赖对象-成员访问依赖对象structVec{intdata[4];// [] 必须是成员intoperator[](inti){returndata[i];}};⚠️高频考点赋值运算符若你没自己写编译器会自动生成一个默认的逐成员拷贝。这一点和构造/析构一样是Rule of系列三五法则的起点。五、自增自减前置 vs 后置怎么区分❓a和a的重载函数长得一样怎么区分✅ 这是最经典的设计巧思。C 用一个多余的 int 参数来区分前置a无参返回引用修改后的对象后置a多一个int哑参数返回值修改前的副本。structCounter{intn0;// 前置 n无参返回引用Counteroperator(){n;return*this;}// 后置 nint 哑参返回旧值Counteroperator(int){Counter old*this;// 存旧值n;// 先自增returnold;// 返回旧值}};Counter c;c;// 调 operator()c;// 调 operator(int)那个int参数根本不用纯粹是个标记——调用时编译器自动塞个 0 进去你写c实际是c.operator(0)。性能提示加分点后置要先拷贝一份旧值再自增多一次拷贝。所以对自定义类型优先用前置c。这也是为什么迭代器习惯写it而不是it。--同理。对内置类型int 等前置后置性能没差但养成写前置的习惯到了自定义类型就自动受益——这是前置优先能成为社区共识的原因。六、赋值运算符三句话法则❓ 重载要注意什么✅ 赋值运算符重载有三条铁律面试必背参数常量引用const T避免拷贝不改源返回引用T支持a b c链式赋值处理自赋值a a不能出错尤其涉及动态内存。structBuffer{char*pnullptr;// 规范的赋值重载Bufferoperator(constBuffero){if(thiso)return*this;// 自赋值delete[]p;// 释放旧的pnewchar[strlen(o.p)1];strcpy(p,o.p);return*this;// 返回 *this}};加分点现代 C 推荐用copy-and-swap惯用法一行解决自赋值和异常安全Bufferoperator(Buffer o){// 值传参std::swap(p,o.p);// 交换return*this;// o 析构释放旧}// 异常安全 自动处理自赋值七、核心规则速查表维度规则不能重载::..*?:sizeof不该重载必须成员函数()[]-前后置区分后置多一个 int 哑参赋值重载三铁律const 引用参、返回引用、防自赋值全局 vs 成员左操作数非本类 → 全局八、面试高频追问❓ Q1运算符重载能改变运算符的优先级和结合性吗✅不能。重载只改变对自定义类型时怎么算优先级、结合性、操作数个数全都钉死不动。a b * c永远是先算*再算。❓ Q2重载后的还能算 int 吗✅能。重载是新增不是覆盖——内置类型上的行为不变。重载只对自定义类型生效这叫重载解析。❓ Q3为什么和能用来做输入输出✅ 因为它们在iostream里被重载成了流插入/提取运算符。本质就是全局函数operator(ostream, T)。这是运算符重载最经典的成功案例。❓ Q4operator该返回值还是引用✅ 返回值。a b产生一个新对象它不在任何变量里。若返回引用引用的是局部临时对象函数返回即销毁——未定义行为。算术运算符返回值赋值/复合赋值返回引用这是通用法则。❓ Q5C20 的operator是什么✅ “飞船运算符”三向比较。你只需重载这一个编译器自动生成四个大大减少手写比较的样板代码。九、总结速查表场景推荐写法算术运算符全局返回值赋值/复合赋值成员返回引用比较、流全局()[]-必须成员自增自减成员后置加 int一句话回顾运算符重载的本质是让自定义类型用起来像内置类型。记住五个不能动、三个别去动、四个必须成员、前后置靠 int 区分再配上赋值三铁律这题就稳了。如果您觉得本篇内容对你有帮助欢迎点赞 、收藏 ⭐、转发 。下期我们聊聊 lambda 表达式的本质敬请关注