C++ 运算符重载全解析:+、=、>>、<<、++、==

什么是运算符重载?

        运算符重载是 C++ 提供的一种语法扩展机制,使得自定义类也可以使用类似于内置类型的操作符,换句话说,你可以为你的类赋予自然的操作符语义,让代码更优雅、更贴近业务逻辑。 

基本语法

        重载函数本质上是一个特殊函数:

返回类型 operator符号(参数列表) 

它可以是:

  • 成员函数
  • 非成员函数(全局函数,或友元) 

一、重载 + 运算符 

为什么要重载 + 运算符?

        假设你有一个二维向量类:

class Vector2D { public: int x, y; }; 

        现在你希望支持类似以下操作:

Vector2D a(1, 2), b(3, 4); Vector2D c = a + b; // 理想行为 

        这是不可能的,除非你 告诉编译器“+”在你的类中应该怎么用 —— 也就是重载它。 

如何重载 + 运算符?

成员函数版本

class Vector2D { public: int x, y; Vector2D(int x = 0, int y = 0) : x(x), y(y) {} Vector2D operator+(const Vector2D& other) const { return Vector2D(x + other.x, y + other.y); } }; 

使用方式:

Vector2D a(1, 2), b(3, 4); Vector2D c = a + b; // 自动调用 operator+ 
建议使用 const 引用作为参数,避免不必要的拷贝
const 成员函数表示不会修改当前对象 

全局函数版本(推荐)

        有些时候你希望支持这种写法:

Vector2D a(1, 2); Vector2D c = a + 5; // 或者 5 + a; 

        此时就不能用成员函数,因为左侧的 5 并不是类对象。

做法:写成友元 + 全局函数

class Vector2D { public: int x, y; Vector2D(int x = 0, int y = 0) : x(x), y(y) {} friend Vector2D operator+(const Vector2D& lhs, const Vector2D& rhs); }; Vector2D operator+(const Vector2D& lhs, const Vector2D& rhs) { return Vector2D(lhs.x + rhs.x, lhs.y + rhs.y); } 

支持混合类型加法

        你还可以重载 int + Vector2D 或 Vector2D + int:

Vector2D operator+(const Vector2D& v, int val) { return Vector2D(v.x + val, v.y + val); } Vector2D operator+(int val, const Vector2D& v) { return Vector2D(v.x + val, v.y + val); } 

小练习:重载 + 支持字符串拼接类

class MyString { string data; public: MyString(string s) : data(s) {} MyString operator+(const MyString& other) const { return MyString(data + other.data); } friend ostream& operator<<(ostream& os, const MyString& s) { os << s.data; return os; } }; 

使用:

MyString a("Hello, "), b("World!"); cout << a + b << endl; // 输出:Hello, World! 

二、重载 += 运算符 

 operator+=operator+ 的区别

运算符是否修改自身返回值类型性能
+❌ 不修改新对象中等
+=✅ 修改自身返回引用(*this更优

        举例对比:

a = a + b; // 创建新对象并赋值 a += b; // 原地修改 a,更高效 

基本写法(成员函数)

class Vector2D { public: int x, y; Vector2D(int x = 0, int y = 0) : x(x), y(y) {} Vector2D& operator+=(const Vector2D& other) { x += other.x; y += other.y; return *this; } }; 

使用:

Vector2D a(1, 2), b(3, 4); a += b; // a = (4, 6) 

返回 *this 的意义?

        返回引用是为了支持链式调用:

a += b += c; 

        这样 b += c 会返回 b 的引用,接着 a += (b),逻辑就通了。 

建议搭配 operator+ 一起写

        通常这样配套使用:

Vector2D operator+(const Vector2D& lhs, const Vector2D& rhs) { Vector2D temp = lhs; temp += rhs; return temp; } 

        这样避免了重复逻辑:+ 内部直接调用 +=,更清晰。 

示例:重载 += 实现字符串拼接类

class MyString { string data; public: MyString(string) : data(s) {} MyString& operator+=(const MyString& other) { data += other.data; return *this; } friend ostream& operator<<(ostream& os, const MyString& s) { os << s.data; return os; } }; 

使用:

MyString a("Hello "), b("World"); a += b; cout << a; // 输出 Hello World 

三、重载 >> 实现自定义输入

        默认情况下,cin 只能读取内置类型(int、float、string 等)。对于自定义类,你必须自己定义输入逻辑。

        通过重载 operator>>,你可以实现如下优雅写法:

cin >> person; // 自动读取 name、age、gender 等字段 

 基本语法(推荐写成全局友元函数)

class Person { public: string name; int age; // 声明为友元函数,允许访问私有成员 friend istream& operator>>(istream& in, Person& p); }; // 实现 istream& operator>>(istream& in, Person& p) { in >> p.name >> p.age; return in; } 

使用示例:

Person p; cin >> p; 

        注意:引用传参 + 返回流对象引用 是标准做法,支持链式输入。 

完整示例:输入二维坐标点

class Point { public: int x, y; friend istream& operator>>(istream& in, Point& p); friend ostream& operator<<(ostream& out, const Point& p); }; istream& operator>>(istream& in, Point& p) { cout << "请输入x和y:"; in >> p.x >> p.y; return in; } ostream& operator<<(ostream& out, const Point& p) { out << "(" << p.x << ", " << p.y << ")"; return out; } 

使用方式:

Point pt; cin >> pt; cout << pt; // 输出 (x, y) 

 应用场景举例

  1. 输入学生信息
cin >> student; // 读取姓名、学号、成绩等 
  1. 输入日期
cin >> date; // 读取年、月、日 
  1. 批量数据输入
vector<Point> vec(3); for (auto& pt : vec) cin >> pt; 

小技巧:配合 getline

        如果类成员中有空格,如字符串:

class Book { public: string title; int pages; friend istream& operator>>(istream& in, Book& b) { getline(in >> ws, b.title); // 跳过前导空格 in >> b.pages; return in; } }; 

四、重载 << 实现自定义输出 

        默认情况下,cout << 只能用于内置类型和部分 STL 类型(如 stringvector<int> 等)。

对于自定义类,如 PersonDatePoint,你需要自己定义如何将对象内容格式化输出到流中。

        重载 << 后的代码将更直观、可维护性更高:

cout << person << endl; 

 基本语法:推荐使用全局友元函数

class Person { public: string name; int age; // 友元声明,允许访问私有成员 friend ostream& operator<<(ostream& out, const Person& p); }; // 函数定义 ostream& operator<<(ostream& out, const Person& p) { out << "姓名: " << p.name << ", 年龄: " << p.age; return out; } 

注意:

  • 参数用 常引用,避免拷贝
  • 返回 ostream&,支持链式输出 

完整示例:输出二维坐标点

class Point { private: int x, y; public: Point(int x = 0, int y = 0) : x(x), y(y) {} friend ostream& operator<<(ostream& out, const Point& p); }; ostream& operator<<(ostream& out, const Point& p) { out << "(" << p.x << ", " << p.y << ")"; return out; } 

使用方式:

Point pt(3, 4); cout << pt << endl; // 输出 (3, 4) 

支持链式调用

        由于 cout 是一个 ostream 类型对象,重载函数返回 ostream& 可支持多次连续输出:

cout << p1 << ", " << p2 << ", " << p3 << endl; 

        如果你忘记 return out;,将会破坏链式调用行为。 

常见错误

错误写法正确写法
void operator<<(ostream&, T&)ostream& operator<<(ostream&, const T&)
写成类的成员函数(不推荐)写成友元的全局函数
忘记返回 out应该 return out; 支持链式调用

实际项目应用举例

  1. 打印调试对象
cout << user; // 打印用户信息 
  1. 格式化输出日志信息
logFile << "[INFO] " << timestamp << ": " << event << endl; 
  1. 配合泛型容器使用
vector<Person> vec = {p1, p2}; for (auto& p : vec) cout << p << endl; 

配合文件流也适用

        重载的 << 不仅支持 cout,也支持其他 ostream 类型如 ofstream

ofstream out("person.txt"); out << p1 << endl; 

重载<<与>>的区别 

运算符目的传参类型返回类型
operator<<输出到流ostream&, const T&ostream&
operator>>从流中读取istream&, T&istream&

五、重载自增运算符 ++(前置与后置) 

基础回顾:什么是 ++

        自增运算符 ++ 是一种一元运算符,可以作用于变量前或后:

int a = 5; ++a; // 前置 ++a:先加后用 a++; // 后置 a++:先用后加 

        同样地,我们可以将这种语义赋予自己的类,比如:

MyInteger a(10); ++a; // 让a的内部值+1 a++; // 后置自增 

前置和后置 ++ 的底层区别

        在 函数签名层面,C++ 是通过参数来区分前置和后置的:

类型函数原型说明
前置 ++aMyClass& operator++()无参数,返回引用
后置 a++MyClass operator++(int)有一个 int 虚参,返回值拷贝

        注:int 参数只是为了区分用的,不会传递值进去。 

完整示例代码

        让我们通过一个例子来看如何重载前置与后置 ++

#include <iostream> using namespace std; class MyInteger { private: int val; public: MyInteger(int v = 0) : val(v) {} // 前置 ++a MyInteger& operator++() { ++val; // 先加 return *this; // 返回自身引用 } // 后置 a++ MyInteger operator++(int) { MyInteger temp = *this; // 保存旧值 val++; // 自增 return temp; // 返回旧值 } void show() const { cout << "val = " << val << endl; } }; 

使用示例:

int main() { MyInteger a(10); ++a; // 前置:a变11 a.show(); a++; // 后置:a仍然变12,但返回旧值 a.show(); MyInteger b = ++a; // b = 13, a = 13 b.show(); MyInteger c = a++; // c = 13, a = 14 c.show(); } 

返回值差异详解

类型返回类型原因
前置 ++aT&(引用)可以链式调用,避免拷贝
后置 a++T(值拷贝)要返回旧值,所以需要保留临时对象
++(++a); // 合法:前置返回引用 (a++)++; // 不合法:后置返回临时变量,不能再自增 

链式调用场景

        我们可以支持链式写法:

MyInteger a(1); (++a)++; a.show(); // 合法:先++a,得到引用,再执行后置++ 

这就要求:

  • 前置返回引用 T&
  • 后置返回临时值 T 

常见错误

错误写法正确写法
没写后置函数参数 int必须写一个虚拟 int 参数区分
后置返回引用 T&应该返回 T 的拷贝
前置返回 voidT应返回引用 T& 以支持链式调用

面试经典问题

💬 “C++ 中如何实现 a++ 与 ++a 的区别?”

答:通过重载不同的函数原型实现:

  • T& operator++() 实现前置
  • T operator++(int) 实现后置,其中 int 是虚参,仅用于区分

并且返回类型设计为:前置返回引用、后置返回副本。 

六、重载 == 来比较对象是否相等

为什么我们要重载 ==

        在 C++ 中,内置类型之间可以直接比较:

int a = 10, b = 20; cout << (a == b); // 输出 0(false) 

        但你自己写的类对象,默认不能比较“值相等”:

class Person { public: string name; int age; }; int main() { Person p1{"Tom", 18}; Person p2{"Tom", 18}; cout << (p1 == p2); // ❌ 报错!没有定义 == 运算符 } 

        原因:C++ 不知道怎么“比较两个对象”,默认只会比较地址或不支持。因此你需要自己告诉它什么是“两个对象相等”。 

如何重载 == 运算符?

        你可以在类中写一个特殊的函数,名字就叫 operator==,告诉 C++ 如何比较两个对象。

基本语法:

bool operator==(const ClassName& other) const; 

完整案例讲解

        我们来写一个完整类,表示“人”的信息:姓名和年龄。我们希望两个 Person 如果名字和年龄都一样,就认为他们是同一个人。

#include <iostream> using namespace std; class Person { public: string name; int age; // 构造函数 Person(string n, int a) : name(n), age(a) {} // 运算符重载:判断两个对象是否相等 bool operator==(const Person& other) const { return this->name == other.name && this->age == other.age; } }; 

测试代码:

int main() { Person p1("Tom", 18); Person p2("Tom", 18); Person p3("Jerry", 20); if (p1 == p2) { cout << "p1 和 p2 相等!" << endl; } else { cout << "p1 和 p2 不相等!" << endl; } if (p1 == p3) { cout << "p1 和 p3 相等!" << endl; } else { cout << "p1 和 p3 不相等!" << endl; } } 

输出结果:

p1 和 p2 相等! p1 和 p3 不相等! 

重点解释每一行代码

bool operator==(const Person& other) const 
关键词解释
bool返回值是布尔类型,true 表示相等,false 表示不相等
operator==告诉 C++ 这是一个运算符函数,重载 == 运算符
const Person& other比较的另一个对象,引用可以避免拷贝,const防止被修改
const(末尾)表示这个函数不会修改当前对象,更安全,推荐写

== 重载必须写在类里面吗?

        不一定。你也可以写在类外,但推荐写在类里,逻辑更清晰。

        类外写法(不推荐给新手):

bool operator==(const Person& a, const Person& b) { return a.name == b.name && a.age == b.age; } 

注意事项总结

常见问题正确做法
忘记加 const 引用比较函数参数要用 const ClassName&,避免拷贝提高性能
没有定义 == 就直接比较对象会报错,需要重载
比较逻辑不清晰根据你对象的数据来决定比较什么字段

配合重载 !=

        通常 ==!= 是成对出现的,只要写好 ==,你可以轻松写:

bool operator!=(const Person& other) const { return !(*this == other); // 复用前面的 == } 

== 运算符重载和 STL 一起用!

你定义好 == 后,你就可以使用:

  • std::find 查找元素
  • std::set, std::map 比较键值
  • 判断对象是否存在于容器中
#include <vector> #include <algorithm> vector<Person> vec = {p1, p2}; if (find(vec.begin(), vec.end(), p1) != vec.end()) { cout << "找到了!" << endl; } 

七、 重载 [](数组访问运算符) 

为什么要重载 []

        我们知道,C++内置数组可以通过下标访问元素:

int arr[5] = {10, 20, 30, 40, 50}; cout << arr[2]; // 输出 30 

        但是,如果你写了自己的类,想用 obj[2] 这种写法访问类里的数据,C++默认是不支持的,因为编译器不知道你这个类应该怎么“用下标取数据”。

        这时候,我们就需要重载 [] 运算符,告诉编译器“你该怎么用下标访问我的类”。

[] 运算符重载的基本语法

返回类型& operator[](参数); 
  • 返回类型&:一般返回的是引用,方便修改数据(可读可写)
  • operator[]:告诉编译器这是重载的[]运算符
  • 参数:下标,一般用 int 表示访问哪个元素

 举个最简单的例子

#include <iostream> using namespace std; class MyArray { private: int data[5]; public: MyArray() { for (int i = 0; i < 5; ++i) data[i] = i * 10; } int& operator[](int index) { return data[index]; // 返回对应位置元素的引用 } }; int main() { MyArray arr; cout << arr[2] << endl; // 输出 20 arr[2] = 99; // 修改数据 cout << arr[2] << endl; // 输出 99 return 0; } 

重点讲解

  • 为什么返回引用 int&
    因为返回引用,可以既读又写。比如上面例子,arr[2] = 99; 修改了数组元素。如果返回的是值,就不能修改原数据。
  • 参数 index 是什么?
    你用下标访问时,传入的就是这个数字,比如 arr[2]index 就是2。

增加“安全措施”——越界检查

        原生数组不会帮你检查下标是否合法,访问越界会导致错误。我们可以加代码手动检查:

int& operator[](int index) { if (index < 0 || index >= 5) { cout << "⚠️ 越界访问,返回第一个元素!" << endl; return data[0]; } return data[index]; } 

支持 const 对象访问

        如果对象是 const,它调用的是 const 版本的 operator[],这时候我们要写一个只读版本:

int operator[](int index) const { if (index < 0 || index >= 5) { cout << "⚠️ 越界访问,返回0!" << endl; return 0; } return data[index]; } 

const 版本返回的是值,不能修改数据,只能读。

完整示例代码(含注释)

#include <iostream> using namespace std; class MyArray { private: int data[5]; public: MyArray() { for (int i = 0; i < 5; ++i) data[i] = i * 10; } // 非const版本,返回引用,可读写 int& operator[](int index) { if (index < 0 || index >= 5) { cout << "⚠️ 越界访问,返回第一个元素!" << endl; return data[0]; } return data[index]; } // const版本,只读访问 int operator[](int index) const { if (index < 0 || index >= 5) { cout << "⚠️ 越界访问,返回0!" << endl; return 0; } return data[index]; } }; int main() { MyArray arr; cout << arr[2] << endl; // 20 arr[2] = 99; cout << arr[2] << endl; // 99 const MyArray cArr; cout << cArr[3] << endl; // 30 // cArr[3] = 50; // ❌ 不能修改,编译报错 cout << arr[10] << endl; // 越界访问提示 } 

 八、重载赋值运算符= 

        赋值运算符=是我们日常最常用的操作之一,比如:

int a = 5; int b = 10; b = a; // 把a的值赋给b,b变成5 

        在C++里,赋值操作是用=来完成的。

为什么需要重载赋值运算符?

        当你定义自己的类时,比如:

class Person { public: string name; int age; }; 

        如果写了:

Person p1, p2; p2 = p1; 

编译器默认会给你生成一个赋值运算符,但它只做简单的“逐成员赋值”(浅拷贝)。

        如果你的类里有指针或动态分配的资源(比如堆内存),简单的浅拷贝会导致两个对象指向同一块内存,出问题!

        所以,我们要自己重载赋值运算符,保证赋值时数据被正确复制(深拷贝),避免程序崩溃和内存错误。

赋值运算符重载的格式

ClassName& operator=(const ClassName& rhs); 
  • ClassName&:返回自身对象的引用,支持链式赋值(a = b = c;
  • operator=:表示重载赋值运算符
  • const ClassName& rhs:右侧赋值对象的常量引用,避免拷贝,提高效率
#include <iostream> #include <string> using namespace std; class Person { public: string name; int age; // 赋值运算符重载函数 Person& operator=(const Person& rhs) { // 1. 自我赋值检测,防止 p = p 时出错 if (this == &rhs) return *this; // 2. 逐成员赋值,将 rhs 的数据复制给当前对象 name = rhs.name; age = rhs.age; // 3. 返回自身引用,支持链式赋值 return *this; } }; int main() { Person p1; p1.name = "Alice"; p1.age = 30; Person p2; p2 = p1; // 调用赋值运算符重载 cout << "p2.name = " << p2.name << ", p2.age = " << p2.age << endl; } 
  • Person& operator=(const Person& rhs)
    定义赋值运算符函数,rhs 是右侧的赋值对象,使用const &避免复制且保证不被修改。
  • if (this == &rhs)
    判断自己是否给自己赋值。如果是,就直接返回,避免重复赋值带来的问题(比如内存释放两次)。
  • name = rhs.name; age = rhs.age;
    rhs对象的成员变量复制给当前对象。这里是浅拷贝,但对于string类成员,它内部会自己管理深拷贝。
  • return *this;
    返回当前对象的引用,支持a = b = c;这种连续赋值写法。

什么时候必须重载赋值运算符?

当你的类含有指针成员或动态资源时,默认浅拷贝会导致:

  • 多个对象指向同一块资源,修改一个会影响另一个
  • 对象析构时重复释放同一块资源,程序崩溃

这时你必须写深拷贝版本的赋值运算符

指针成员的赋值运算符(深拷贝)示例

#include <iostream> #include <cstring> using namespace std; class Person { private: char* name; int age; public: Person() : name(nullptr), age(0) {} Person(const char* n, int a) : age(a) { name = new char[strlen(n) + 1]; strcpy(name, n); } // 重载赋值运算符,深拷贝 Person& operator=(const Person& rhs) { if (this == &rhs) // 自我赋值检测 return *this; delete[] name; // 释放旧资源 name = new char[strlen(rhs.name) + 1]; // 分配新内存 strcpy(name, rhs.name); // 复制内容 age = rhs.age; return *this; } void print() { cout << "Name: " << (name ? name : "null") << ", Age: " << age << endl; } ~Person() { delete[] name; } }; int main() { Person p1("Tom", 20); Person p2; p2 = p1; // 调用深拷贝赋值 p2.print(); } 

Read more

Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

Java 中间件:RocketMQ 顺序消息(全局/分区顺序)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:RocketMQ 顺序消息(全局 / 分区顺序) * 什么是顺序消息? * RocketMQ 顺序消息的工作原理 * 全局顺序 vs 分区顺序 * RocketMQ 顺序消息的核心机制 * 全局顺序消息的实现 * 全局顺序的配置要求 * Java 代码示例:全局顺序消息 * 全局顺序的局限性 * 分区顺序消息的实现 * 分区顺序的设计思路 * Java 代码示例:分区顺序消息 * 分区顺序的关键要点 * 顺序消息的消费机制详解 * ConsumeOrderlyStatus 枚举 * 消费失败的处理机制 * 并发消费 vs 顺序消费

By Ne0inhk
【LLM基础】大模型的上游与下游:一篇文章讲清 AI 流水线的全貌

【LLM基础】大模型的上游与下游:一篇文章讲清 AI 流水线的全貌

你以为 ChatGPT 的核心是 Transformer?其实在模型开始训练之前,但在工程实践中,模型真正开始训练之前,80% 的成败已经被决定了。 一、背景:为什么需要区分"上游"和"下游"? 1.1 学大模型,别只盯着模型本身 很多人学大模型,第一反应是去看 Transformer 架构、注意力机制、RLHF 对齐。这些当然重要,但它们只是整条流水线中的 一个环节。 一个大模型从无到有、从训练到落地,涉及的环节远比"模型本身"多得多:数据从哪里来?怎么清洗?怎么分词?预训练之后怎么变成一个能聊天的产品? 1.2 一个类比:大模型就像一座工厂 如果把大模型比作一座工厂,那

By Ne0inhk
Seedance 2.0(即梦 2.0)深度解析:AI 视频进入「导演级」可控时代

Seedance 2.0(即梦 2.0)深度解析:AI 视频进入「导演级」可控时代

2026 年 2 月 12 日,字节跳动 Seed 实验室正式发布Seedance 2.0(即梦 2.0) 多模态音视频生成大模型。它以统一多模态联合架构为底座,在运动稳定性、角色一致性、多镜头叙事与音画同步上实现全面突破,成为当前国内最接近工业级生产的 AI 视频模型之一。 一、核心定位与行业地位 * 定位:全能型 AI 视频生成模型,支持文生视频、图生视频、视频续作、音频驱动、多模态混合生成 * 成绩:在权威榜单Video Arena中文生视频、图生视频双赛道登顶 * 输出规格:2K 电影级分辨率,最长支持15 秒高质量多镜头成片,支持视频平滑延长 二、四大核心能力(真正解决创作痛点) 1. 多模态全能参考:一次输入,全域控制

By Ne0inhk
人工智能:自然语言处理在医疗健康领域的应用与实战

人工智能:自然语言处理在医疗健康领域的应用与实战

人工智能:自然语言处理在医疗健康领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在医疗健康领域的应用场景和重要性 💡 掌握医疗健康领域NLP应用的核心技术(如电子病历分析、医学文本分类、疾病预测) 💡 学会使用前沿模型(如BERT、GPT-3)进行医疗健康文本分析 💡 理解医疗健康领域的特殊挑战(如医学术语、数据隐私、数据质量) 💡 通过实战项目,开发一个电子病历分析应用 重点内容 * 医疗健康领域NLP应用的主要场景 * 核心技术(电子病历分析、医学文本分类、疾病预测) * 前沿模型(BERT、GPT-3)在医疗健康领域的使用 * 医疗健康领域的特殊挑战 * 实战项目:电子病历分析应用开发 一、医疗健康领域NLP应用的主要场景 1.1 电子病历分析 1.1.1 电子病历分析的基本概念 电子病历分析是对电子病历文本进行分析和处理的过程。在医疗健康领域,电子病历分析的主要应用场景包括: * 病历结构化:将非结构化的电子病历文本转换为结构化数据 * 病历检索:检索相关的电子病历 * 病历质量评估:

By Ne0inhk