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