【C++ 从基础到项目实战】C++(八):运算符重载——让你的类用起来像内置类型
阅读时长25分钟 | 关键词C、运算符重载、operator、友元重载、重载、String类引言你有没有想过为什么int可以a b、string可以s1 s2而自己写的MyVector类只能用v.add(w)这种丑陋的语法答案是运算符重载——它允许你为自定义类型定义、-、等运算符的行为让代码像操作内置类型一样自然。一、运算符重载基础1.1 概念与语法运算符重载本质上就是给自定义类写一个特殊的函数函数名是operator 运算符返回类型operator运算符(参数列表){// 实现逻辑}classComplex{public:doublereal,imag;Complex(doubler0,doublei0):real(r),imag(i){}// 重载 运算符成员函数Complexoperator(constComplexother)const{returnComplex(realother.real,imagother.imag);}};intmain(){Complexc1(2,3),c2(4,5);Complex c3c1c2;// 等价于 c1.operator(c2)// c3 (6, 8i)}1.2 可重载 vs 不可重载可重载不可重载-*/%.成员访问!.*成员指针访问-*/::作用域解析--(前后缀)sizeof(流)?:三元条件[]()(下标/函数调用)typeidnewdelete###预处理1.3 两种重载方式方式语法左操作数使用场景成员函数Ret operatorX(Para)必须是本类对象单目运算符、赋值类友元函数friend Ret operatorX(L,R)可以是其他类型双目运算符、流运算符// 成员函数版本Complexoperator(constComplexother)const;// 等价于 c1.operator(c2)// 友元函数版本friendComplexoperator(constComplexa,constComplexb);// 等价于 operator(c1, c2) 流运算符和必须用友元函数因为左操作数是std::ostream而非本类。二、各类运算符重载实例2.1 算术运算符 (, -, *, /)classComplex{public:doublereal,imag;Complex(doubler0,doublei0):real(r),imag(i){}Complexoperator(constComplexo)const{returnComplex(realo.real,imago.imag);}Complexoperator-(constComplexo)const{returnComplex(real-o.real,imag-o.imag);}Complexoperator*(constComplexo)const{returnComplex(real*o.real-imag*o.imag,real*o.imagimag*o.real);}};2.2 关系运算符 (, !, , )classPoint{public:intx,y;Point(intx,inty):x(x),y(y){}booloperator(constPointo)const{returnxo.xyo.y;}booloperator!(constPointo)const{return!(*thiso);// 复用 }};2.3 赋值运算符与复合赋值 (, -)classMyNumber{public:intvalue;MyNumber(intv):value(v){}MyNumberoperator(constMyNumbero){// 返回引用支持链式赋值if(this!o)valueo.value;return*this;}MyNumberoperator(constMyNumbero){valueo.value;return*this;}};// a b c; // 链式赋值依赖返回引用2.4 自增自减 (, --)区分前缀和后缀的秘诀后缀版本多一个不用的 int 参数classCounter{public:intvalue;Counteroperator(){// 前缀 value;return*this;}Counteroperator(int){// 后缀 (int 是标记)Counter temp*this;value;returntemp;// 返回旧值}};Counter c{5};c;// c.value 6, 返回 c 自己c;// c.value 7, 返回 Counter(6)2.5 流运算符 (, )必须用友元classComplex{public:doublereal,imag;Complex(doubler0,doublei0):real(r),imag(i){}friendstd::ostreamoperator(std::ostreamos,constComplexc);friendstd::istreamoperator(std::istreamis,Complexc);};std::ostreamoperator(std::ostreamos,constComplexc){osc.real c.imagi;returnos;// 必须返回 os支持链式调用}std::istreamoperator(std::istreamis,Complexc){isc.realc.imag;returnis;}// std::cout c1 and c2 std::endl; ✅ 链式2.6 下标运算符 []classIntArray{private:int*data;intsize;public:IntArray(ints):size(s),data(newint[s]){}intoperator[](intindex){// 返回引用允许修改returndata[index];}constintoperator[](intindex)const{// const 版本只读returndata[index];}~IntArray(){delete[]data;}};IntArrayarr(5);arr[2]100;// 调用非 const 版本2.7 函数调用运算符 () — 仿函数classAdder{public:intoperator()(inta,intb)const{returnab;}};Adder add;std::coutadd(3,4)std::endl;// 7像函数一样调用对象三、运算符重载最佳实践原则说明保持语义一致不应该做减法不要滥用只有提高可读性时才重载算术运算返回新对象不返回引用临时对象赋值运算返回引用return *this支持链式const 正确性不修改对象的函数加const不可改变优先级重载不改变运算符优先级和结合性四、std::string 类运算符重载的教科书范本std::string大量使用运算符重载让字符串操作如内置类型般自然#includestringstd::string s1Hello;std::string s2 World;std::string s3s1s2;// 重载连接字符串s3!;// 重载追加if(s1s2){}// 重载比较内容charcs3[0];// [] 重载下标访问std::couts3;// 重载输出常用 string 操作速查方法功能示例/连接/追加s1 s2find(s)查找子串位置s.find(He)→ 0substr(pos, n)截取子串s.substr(0, 3)→ “Hel”replace(pos, n, s)替换s.replace(0,2,Hi)length()/size()长度s.length()c_str()转 C 串s.c_str()→const char *at(i)安全下标访问越界抛异常s.at(100)string vs C 风格字符串std::stringchar[]/char *内存管理自动手动安全性不易溢出容易缓冲区溢出操作便利丰富的成员函数需cstring函数推荐度⭐⭐⭐⭐⭐⭐⭐仅在 C 接口需要小结序号知识点一句话总结1运算符重载operatorX特殊函数让类支持运算符操作2成员 vs 友元成员用于单目/赋值友元用于双目/流运算符3不可重载运算符..*::sizeoftypeid?:###4流运算符必须用友元返回ostream支持链式5前后缀 前缀无参后缀有废弃 int 标记6赋值 vs 算术赋值返回*this引用算术返回新对象7std::string运算符重载的典范优先于 C 风格字符串下一篇文章我们将学习友元与设计模式初探——如何用friend打破封装边界以及如何用 Singleton 模式用静态成员打造全局唯一实例。本文是「C 从基础到项目实战」系列的第 8 篇。关注我不错过后续更新。