【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 篇。关注我,不错过后续更新。

相关推荐
原创小甜甜1 小时前
OOM 排查复盘:Hutool 序列化 Request 导致 Java Heap Space
java·开发语言·python
z200509301 小时前
今日算法(回溯全排列)
c++·算法·leetcode
萨小耶1 小时前
[Java学习日记10】聊聊checked exception和runtime exception
java·开发语言·学习
不会C语言的男孩1 小时前
C++ Primer 第6章:函数
开发语言·c++
dnbug Blog1 小时前
C语言 简介
c语言·开发语言
码上有光1 小时前
c++:多态
java·jvm·c++·多态·多态原理
Lumbrologist1 小时前
【C++】零基础入门 · 第 18 节:互斥锁与线程同步
java·开发语言·c++
tangchao340勤奋的老年?1 小时前
C++ OpenGL显示地图
c++·opengl
炸炸鱼.1 小时前
Zabbix企业级高级应用:从自动化监控到自定义告警完全指南
开发语言·php