【C++ 从基础到项目实战】C++(八):运算符重载——让你的类用起来像内置类型

📌 阅读时长:25分钟 | 关键词:C++、运算符重载、operator、友元重载、<<重载、String类

引言

你有没有想过:为什么 int 可以 a + bstring 可以 s1 + s2,而自己写的 MyVector 类只能用 v.add(w) 这种丑陋的语法?答案是运算符重载 ------它允许你为自定义类型定义 +-<< 等运算符的行为,让代码像操作内置类型一样自然。

一、运算符重载基础

1.1 概念与语法

运算符重载本质上就是给自定义类写一个特殊的函数 ,函数名是 operator + 运算符:

cpp 复制代码
返回类型 operator运算符(参数列表) {
    // 实现逻辑
}
cpp 复制代码
class Complex {
public:
    double real, imag;

    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // 重载 + 运算符(成员函数)
    Complex operator+(const Complex &other) const {
        return Complex(real + other.real, imag + other.imag);
    }
};

int main() {
    Complex c1(2, 3), c2(4, 5);
    Complex c3 = c1 + c2;  // 等价于 c1.operator+(c2)
    // c3 = (6, 8i)
}

1.2 可重载 vs 不可重载

可重载 不可重载
+ - * / % . 成员访问
== != < > <= >= .* 成员指针访问
= += -= *= /= :: 作用域解析
++ -- (前后缀) sizeof
<< >> (流) ?: 三元条件
[] () (下标/函数调用) typeid
new delete # ## 预处理

1.3 两种重载方式

方式 语法 左操作数 使用场景
成员函数 Ret operatorX(Para) 必须是本类对象 单目运算符、赋值类
友元函数 friend Ret operatorX(L,R) 可以是其他类型 双目运算符、流运算符
cpp 复制代码
// 成员函数版本
Complex operator+(const Complex &other) const;
// 等价于 c1.operator+(c2)

// 友元函数版本
friend Complex operator+(const Complex &a, const Complex &b);
// 等价于 operator+(c1, c2)

💡 流运算符 <<>> 必须 用友元函数,因为左操作数是 std::ostream 而非本类。

二、各类运算符重载实例

2.1 算术运算符 (+, -, *, /)

cpp 复制代码
class Complex {
public:
    double real, imag;
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    Complex operator+(const Complex &o) const {
        return Complex(real + o.real, imag + o.imag);
    }
    Complex operator-(const Complex &o) const {
        return Complex(real - o.real, imag - o.imag);
    }
    Complex operator*(const Complex &o) const {
        return Complex(real * o.real - imag * o.imag,
                       real * o.imag + imag * o.real);
    }
};

2.2 关系运算符 (==, !=, <, >)

cpp 复制代码
class Point {
public:
    int x, y;
    Point(int x, int y) : x(x), y(y) {}

    bool operator==(const Point &o) const {
        return x == o.x && y == o.y;
    }
    bool operator!=(const Point &o) const {
        return !(*this == o);    // 复用 ==
    }
};

2.3 赋值运算符与复合赋值 (+=, -=)

cpp 复制代码
class MyNumber {
public:
    int value;
    MyNumber(int v) : value(v) {}

    MyNumber &operator=(const MyNumber &o) {     // 返回引用,支持链式赋值
        if (this != &o) value = o.value;
        return *this;
    }

    MyNumber &operator+=(const MyNumber &o) {
        value += o.value;
        return *this;
    }
};

// a = b = c;  // 链式赋值,依赖返回引用

2.4 自增自减 (++, --)

区分前缀和后缀的秘诀:后缀版本多一个不用的 int 参数

cpp 复制代码
class Counter {
public:
    int value;

    Counter &operator++() {       // 前缀 ++
        ++value;
        return *this;
    }

    Counter operator++(int) {     // 后缀 ++ (int 是标记)
        Counter temp = *this;
        ++value;
        return temp;              // 返回旧值
    }
};

Counter c{5};
++c;   // c.value = 6, 返回 c 自己
c++;   // c.value = 7, 返回 Counter(6)

2.5 流运算符 (<<, >>):必须用友元

cpp 复制代码
class Complex {
public:
    double real, imag;
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    friend std::ostream &operator<<(std::ostream &os, const Complex &c);
    friend std::istream &operator>>(std::istream &is, Complex &c);
};

std::ostream &operator<<(std::ostream &os, const Complex &c) {
    os << c.real << " + " << c.imag << "i";
    return os;             // 必须返回 os,支持链式调用
}

std::istream &operator>>(std::istream &is, Complex &c) {
    is >> c.real >> c.imag;
    return is;
}

// std::cout << c1 << " and " << c2 << std::endl;  ✅ 链式

2.6 下标运算符 \[\]

cpp 复制代码
class IntArray {
private:
    int *data;
    int size;
public:
    IntArray(int s) : size(s), data(new int[s]) {}

    int &operator[](int index) {       // 返回引用,允许修改
        return data[index];
    }

    const int &operator[](int index) const {  // const 版本,只读
        return data[index];
    }

    ~IntArray() { delete[] data; }
};

IntArray arr(5);
arr[2] = 100;   // 调用非 const 版本

2.7 函数调用运算符 () --- 仿函数

cpp 复制代码
class Adder {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

Adder add;
std::cout << add(3, 4) << std::endl;  // 7,像函数一样调用对象!

三、运算符重载最佳实践

原则 说明
保持语义一致 + 不应该做减法
不要滥用 只有提高可读性时才重载
算术运算返回新对象 不返回引用(临时对象)
赋值运算返回引用 return *this,支持链式
const 正确性 不修改对象的函数加 const
不可改变优先级 重载不改变运算符优先级和结合性

四、std::string 类:运算符重载的教科书范本

std::string 大量使用运算符重载,让字符串操作如内置类型般自然:

cpp 复制代码
#include <string>

std::string s1 = "Hello";
std::string s2 = " World";
std::string s3 = s1 + s2;        // + 重载:连接字符串
s3 += "!";                       // += 重载:追加
if (s1 == s2) {}                 // == 重载:比较内容
char c = 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 不可重载运算符 . .* :: sizeof typeid ?: # ##
4 流运算符 必须用友元,返回 ostream& 支持链式
5 前后缀 ++ 前缀无参,后缀有废弃 int 标记
6 赋值 vs 算术 赋值返回 *this 引用;算术返回新对象
7 std::string 运算符重载的典范,优先于 C 风格字符串

下一篇文章,我们将学习友元与设计模式初探 ------如何用 friend 打破封装边界,以及如何用 Singleton 模式用静态成员打造全局唯一实例。


本文是「C++ 从基础到项目实战」系列的第 8 篇。关注我,不错过后续更新。

相关推荐
fpcc11 分钟前
跟我学C++中级篇——编译期的条件选择
开发语言·c++
SomeB1oody25 分钟前
【RustyML入门】6.3. 并行归约
开发语言·后端·机器学习·rust·教程
mqiqe2 小时前
AgentScope Java 2.0 协议集成全景解析:A2A、AG-UI、Agent Protocol 三大开放协议实战指南
java·开发语言·ui
lzhdim2 小时前
12、JavaScript常见的内存泄露问题 - JavaScript学习系列文章
开发语言·前端·javascript·学习·ecmascript
脉动数据行情2 小时前
Java 实现台股 TWSE/TPEx 行情采集(个股 + K 线)
java·开发语言·twse·tpex·台股
爱学习的小邓同学2 小时前
Golang --- (1)第一个Golang程序
开发语言·后端·golang
zx_741484813 小时前
【Python 入门】面向对象基础:类、对象、成员变量与构造方法
开发语言·python
liangshanbo12154 小时前
虚拟列表深度面试题整理
java·开发语言·前端
ZISHU_9874 小时前
用 TLabel 给 SynTouch BioTac 数据做语义标注:从原始信号到结构化标注
开发语言·人工智能·python·数据·机器人触觉
略略略咯咯4 小时前
C#Unity
开发语言·unity·c#