C++ 运算符重载:自定义类型的运算扩展

C++ 运算符重载:自定义类型的运算扩展

C++ 运算符重载:自定义类型的运算扩展

在这里插入图片描述

💡 学习目标:掌握运算符重载的核心语法与规则,能够为自定义类型重载常用运算符,实现类对象的灵活运算。
💡 学习重点:运算符重载的基本形式、成员函数与全局函数重载的区别、常见运算符的重载实现、禁止重载的运算符。

一、运算符重载的概念与核心价值

结论:运算符重载是 C++ 静态多态的重要体现,允许为自定义类型(如类、结构体)重新定义运算符的行为,让自定义对象可以像内置类型一样使用运算符。

运算符重载的核心价值:

  1. 简化代码书写:用直观的运算符替代繁琐的成员函数调用,提升代码可读性
  2. 统一操作风格:让自定义类型的运算逻辑与内置类型保持一致,降低学习和使用成本
  3. 扩展类型功能:根据业务需求定制运算符的行为,满足自定义类型的运算需求

⚠️ 注意事项:运算符重载不会改变运算符的优先级和结合性,也不会改变运算符的操作数个数。

二、运算符重载的基本语法

运算符重载的本质是函数重载,分为成员函数重载全局函数重载两种形式。

2.1 成员函数重载语法

将运算符重载函数定义为类的成员函数,语法格式如下:

class 类名 {public: 返回值类型 operator运算符(参数列表){// 自定义运算逻辑}};
  • 一元运算符(如 ++--)作为成员函数时,没有参数
  • 二元运算符(如 +-)作为成员函数时,只有一个参数,表示运算符右侧的操作数

2.2 全局函数重载语法

将运算符重载函数定义为全局函数,语法格式如下:

返回值类型 operator运算符(参数列表){// 自定义运算逻辑}
  • 一元运算符作为全局函数时,有一个参数
  • 二元运算符作为全局函数时,有两个参数,分别表示左右两侧的操作数
  • 如果需要访问类的私有成员,可将全局重载函数声明为类的友元函数

三、常见运算符的重载实现

3.1 二元运算符重载:以 + 为例

以自定义 Point 类为例,重载 + 运算符实现两个点的坐标相加。

3.1.1 成员函数重载 + 运算符
#include<iostream>usingnamespace std;classPoint{public:int x, y;// 构造函数Point(int x =0,int y =0):x(x),y(y){}// 成员函数重载 + 运算符 Point operator+(const Point& p){returnPoint(this->x + p.x,this->y + p.y);}// 打印点坐标voidprint(){ cout <<"("<< x <<", "<< y <<")"<< endl;}};intmain(){ Point p1(1,2),p2(3,4); Point p3 = p1 + p2;// 等价于 p1.operator+(p2) p3.print();// 输出 (4, 6)return0;}
3.1.2 全局函数重载 + 运算符
#include<iostream>usingnamespace std;classPoint{public:int x, y;// 构造函数Point(int x =0,int y =0):x(x),y(y){}// 打印点坐标voidprint(){ cout <<"("<< x <<", "<< y <<")"<< endl;}// 声明友元函数friend Point operator+(const Point& p1,const Point& p2);};// 全局函数重载 + 运算符 Point operator+(const Point& p1,const Point& p2){returnPoint(p1.x + p2.x, p1.y + p2.y);}intmain(){ Point p1(1,2),p2(3,4); Point p3 = p1 + p2;// 等价于 operator+(p1, p2) p3.print();// 输出 (4, 6)return0;}

3.2 一元运算符重载:以 ++ 为例

一元运算符分为前置++和后置++,二者的重载方式有区别。

3.2.1 前置 ++ 运算符重载

前置 ++ 表示先自增,再使用,重载时没有参数

#include<iostream>usingnamespace std;classCounter{private:int count;public:Counter(int c =0):count(c){}// 成员函数重载前置 ++ Counter&operator++(){this->count++;return*this;// 返回自增后的对象,支持链式操作}voidshow(){ cout <<"计数:"<< count << endl;}};intmain(){ Counter c(5);++c;// 等价于 c.operator++() c.show();// 输出 计数:6 Counter c2 =++c; c2.show();// 输出 计数:7return0;}
3.2.2 后置 ++ 运算符重载

后置 ++ 表示先使用,再自增,重载时需要添加一个占位参数 int 区分前置版本。

#include<iostream>usingnamespace std;classCounter{private:int count;public:Counter(int c =0):count(c){}// 成员函数重载后置 ++ Counter operator++(int){ Counter temp =*this;// 保存当前对象状态this->count++;return temp;// 返回自增前的对象}voidshow(){ cout <<"计数:"<< count << endl;}};intmain(){ Counter c(5); Counter c2 = c++;// 等价于 c.operator++(0) c.show();// 输出 计数:6 c2.show();// 输出 计数:5return0;}

3.3 关系运算符重载:以 == 为例

重载关系运算符(==!=<> 等),实现自定义对象的比较逻辑。

#include<iostream>#include<string>usingnamespace std;classStudent{private: string name;int id;public:Student(string name,int id):name(name),id(id){}// 成员函数重载 == 运算符booloperator==(const Student& s){returnthis->id == s.id;// 按学号判断是否为同一学生} string getName(){return name;}};intmain(){ Student s1("张三",2024001); Student s2("李四",2024002); Student s3("张三",2024001);if(s1 == s3){ cout << s1.getName()<<" 和 "<< s3.getName()<<" 是同一学生"<< endl;}else{ cout <<"不是同一学生"<< endl;}if(s1 == s2){ cout <<"是同一学生"<< endl;}else{ cout << s1.getName()<<" 和 "<< s2.getName()<<" 不是同一学生"<< endl;}return0;}

3.4 输入输出运算符重载:<<>>

<<>> 运算符通常需要全局函数 + 友元的方式重载,因为左侧操作数是 ostreamistream 对象。

#include<iostream>#include<string>usingnamespace std;classPerson{private: string name;int age;public:Person(string name ="",int age =0):name(name),age(age){}// 声明友元函数,重载 << 运算符friend ostream&operator<<(ostream& os,const Person& p);// 声明友元函数,重载 >> 运算符friend istream&operator>>(istream& is, Person& p);};// 重载 << 运算符,用于输出对象 ostream&operator<<(ostream& os,const Person& p){ os <<"姓名:"<< p.name <<",年龄:"<< p.age;return os;// 返回 os,支持链式输出}// 重载 >> 运算符,用于输入对象 istream&operator>>(istream& is, Person& p){ is >> p.name >> p.age;return is;// 返回 is,支持链式输入}intmain(){ Person p; cout <<"请输入姓名和年龄:"<< endl; cin >> p;// 等价于 operator>>(cin, p) cout <<"你输入的信息:"<< p << endl;// 等价于 operator<<(cout, p)return0;}

四、运算符重载的限制条件

4.1 禁止重载的运算符

以下运算符不允许重载,使用时需注意:

  • 成员访问运算符:.
  • 成员指针访问运算符:.*
  • 作用域解析运算符:::
  • 条件运算符:?:
  • 预处理运算符:#

4.2 必须以成员函数重载的运算符

以下运算符只能通过类的成员函数重载,不能使用全局函数:

  • 赋值运算符:=
  • 函数调用运算符:()
  • 下标运算符:[]
  • 箭头运算符:->

4.3 重载赋值运算符 = 的注意事项

赋值运算符 = 是类的默认成员函数,编译器会自动生成一个浅拷贝版本。当类中包含指针成员时,必须手动重载 = 实现深拷贝,避免浅拷贝导致的内存泄漏。

#include<iostream>#include<cstring>usingnamespace std;classString{private:char*str;public:// 构造函数String(constchar*s =""){ str =newchar[strlen(s)+1];strcpy(str, s);}// 析构函数~String(){delete[] str;}// 重载赋值运算符 =,实现深拷贝 String&operator=(const String& s){if(this==&s){// 防止自赋值return*this;}// 释放当前对象的内存delete[] str;// 分配新内存并拷贝数据 str =newchar[strlen(s.str)+1];strcpy(str, s.str);return*this;// 支持链式赋值}voidshow(){ cout << str << endl;}};intmain(){ String s1("Hello C++"); String s2; s2 = s1;// 调用重载的 = 运算符 s2.show();// 输出 Hello C++return0;}

⚠️ 核心注意点:重载赋值运算符时,必须返回 *this 的引用,否则无法支持链式赋值(如 a = b = c)。

五、实战案例:重载运算符实现复数运算

💡 需求:设计一个 Complex 类表示复数,重载 +-*<< 运算符,实现复数的加减乘运算和输出功能。

5.1 需求分析

  1. 复数的形式为 a+bia + bia+bi,包含实部 real 和虚部 imag 两个成员变量
  2. 重载 +:(a+bi)+(c+di)=(a+c)+(b+d)i(a+bi)+(c+di) = (a+c)+(b+d)i(a+bi)+(c+di)=(a+c)+(b+d)i
  3. 重载 -:(a+bi)−(c+di)=(a−c)+(b−d)i(a+bi)-(c+di) = (a-c)+(b-d)i(a+bi)−(c+di)=(a−c)+(b−d)i
  4. 重载 *:(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i(a+bi)*(c+di) = (ac-bd)+(ad+bc)i(a+bi)∗(c+di)=(ac−bd)+(ad+bc)i
  5. 重载 <<:输出复数的标准格式

5.2 完整代码实现

#include<iostream>usingnamespace std;classComplex{private:double real;// 实部double imag;// 虚部public:// 构造函数Complex(double real =0,double imag =0):real(real),imag(imag){}// 成员函数重载 + 运算符 Complex operator+(const Complex& c){returnComplex(this->real + c.real,this->imag + c.imag);}// 成员函数重载 - 运算符 Complex operator-(const Complex& c){returnComplex(this->real - c.real,this->imag - c.imag);}// 成员函数重载 * 运算符 Complex operator*(const Complex& c){double r =this->real * c.real -this->imag * c.imag;double i =this->real * c.imag +this->imag * c.real;returnComplex(r, i);}// 友元函数重载 << 运算符friend ostream&operator<<(ostream& os,const Complex& c);};// 实现 << 运算符重载 ostream&operator<<(ostream& os,const Complex& c){if(c.imag >=0){ os << c.real <<" + "<< c.imag <<"i";}else{ os << c.real <<" - "<<-c.imag <<"i";}return os;}intmain(){ Complex c1(3,4),c2(1,-2); Complex c3 = c1 + c2; Complex c4 = c1 - c2; Complex c5 = c1 * c2; cout <<"c1 = "<< c1 << endl; cout <<"c2 = "<< c2 << endl; cout <<"c1 + c2 = "<< c3 << endl; cout <<"c1 - c2 = "<< c4 << endl; cout <<"c1 * c2 = "<< c5 << endl;return0;}

5.3 运行结果

c1 = 3 + 4i c2 = 1 - 2i c1 + c2 = 4 + 2i c1 - c2 = 2 + 6i c1 * c2 = 11 - 2i 

六、运算符重载的开发规范

  1. 保持语义一致:重载后的运算符行为应与内置类型的语义相近,避免让使用者产生困惑
  2. 优先使用成员函数:对于一元运算符和复合赋值运算符(如 +=-=),优先使用成员函数重载
  3. 输入输出用全局友元<<>> 运算符必须使用全局函数 + 友元的方式重载
  4. 深拷贝处理:当类包含指针成员时,必须手动重载赋值运算符 = 实现深拷贝
  5. 避免过度重载:只重载实际需要的运算符,不要为了重载而重载,以免增加代码复杂度

七、本章总结

✅ 运算符重载的本质是函数重载,分为成员函数和全局函数两种实现方式。
✅ 运算符重载不会改变运算符的优先级、结合性和操作数个数。
✅ 部分运算符有固定的重载方式,如 = 必须用成员函数,<< 必须用全局友元函数。
✅ 合理使用运算符重载可以简化代码、统一风格,是 C++ 面向对象编程的重要技能。

Read more

Python 爬虫实战:爬取新闻网站头条与正文内容

前言 在信息爆炸的时代,新闻数据是舆情分析、行业研究、内容创作的重要素材。通过 Python 爬虫技术批量获取新闻网站的头条与正文内容,能够突破人工采集的效率瓶颈,实现结构化的数据沉淀与深度分析。本文以新浪新闻(综合类新闻平台)为核心数据源,系统讲解新闻头条列表、单篇新闻正文的爬取方法,涵盖 HTML 解析、动态内容处理、数据清洗等核心环节,同时兼顾反爬策略与合规性要求,为新闻数据的获取与应用提供完整的技术方案。 摘要 本文以新浪新闻(https://news.sina.com.cn/)为数据来源,详细阐述 Python 爬虫爬取新闻头条与正文内容的全流程。核心技术包括requests库的 HTTP 请求发送、BeautifulSoup的 HTML 结构解析、lxml的高效解析引擎、re的正则表达式数据清洗,以及针对动态加载内容的requests-html辅助处理。通过完整的代码案例,实现新浪新闻头条列表(标题、链接、发布时间、来源)的批量爬取,以及单篇新闻正文(

By Ne0inhk
RTX50系显卡+CUDA+torch+python对应关系

RTX50系显卡+CUDA+torch+python对应关系

前言 本人的显卡是RTX5070,使用时发现它对CUDA、torch和python版本有要求,试图按照老项目的依赖文件进行安装发现安不了,因此记录一下(截至2025年6月)。 提示:以下是本篇文章正文内容,下面案例可供参考 一、RTX50系显卡只能使用CUDA12.8 二、目前只支持torch2.7.0和2.7.1 1.去pytorch官网的https://download.pytorch.org/whl/torch/看一下,确实只有2.7.0和2.7.1有对应cuda12.8的。 注:此图中包含了所有对应CUDA12.8的whl文件,可根据操作系统和python版本选择 torch2.7.1及对应torchvision torchaudio安装指令: pip3 install torch torchvision torchaudio --index-url https://download.pytorch.

By Ne0inhk
C++红黑树的深入解析:从理论到实践

C++红黑树的深入解析:从理论到实践

红黑树作为一种自平衡的二叉搜索树,是计算机科学中的经典数据结构之一,广泛应用于各种需要高效查找、插入和删除操作的场景中,比如STL中的 map 和 set。虽然它的基本原理并不复杂,但在实现过程中,需要处理许多细节,比如颜色的调整、旋转操作等。本文将对红黑树的结构、插入、删除等操作进行详细的剖析,以帮助大家更好地理解和实现这一高效的数据结构。 一、红黑树的基本概念与规则 红黑树是一种满足特定性质的二叉搜索树。与普通的二叉搜索树不同,红黑树的每个结点都附加了一个颜色属性,且通过一些规则保证了树的平衡性。红黑树的四条基本规则如下: 每个节点的颜色要么是红色,要么是黑色。根节点是黑色的。如果一个节点是红色的,那么它的两个子节点必须是黑色的。 这就意味着红色节点不能有连续的红色节点。从任意一个节点到其所有叶子节点的路径上,必须包含相同数量的黑色节点。 这条规则保证了树的平衡性,防止树的某些路径过长。 红黑树的这些规则通过“颜色约束”来间接实现自平衡,因此每次进行插入、删除操作时,都需要确保这些规则被满足。 以上均为红黑树示例。 二、红黑树的高度与效率 红黑树的高度是它的关键特性之

By Ne0inhk
python之路并不一马平川:带你踩坑Pandas

python之路并不一马平川:带你踩坑Pandas

这是我的亲身经历。作为一名全能型的混子,Pandas是我吃饭的家伙之一,但光是把它请到我的电脑上,就差点让我“饭碗不保”。这是一段长达数周,充满挫折、困惑和最终解脱的曲折历程。我将带你完整回顾我踩过的每一个坑,以及那最后的“救命稻草”。我将以第一视角,带你完整回顾我踩过的那些坑,以及我是如何一步步爬出来的。 记得刚入行那年,我接手的第一个项目是个电商小程序开发。当时为了赶进度,我直接跳过了需求分析阶段,结果上线后发现支付接口和后台数据对不上,不得不紧急下架整改。那三天三夜不眠不休的debug经历,现在想起来还心有余悸。 去年在开发智能家居App时,我又犯了个典型错误:没有做好版本兼容性测试。当用户反馈老型号设备无法连接时,我们才发现蓝牙协议栈对新老设备的处理方式完全不同。这个教训让我养成了建立完整测试矩阵的习惯。 最惨痛的经历是去年年底的云服务迁移。当时为了节省成本,我选择了直接全量迁移数据库,结果因为网络波动导致数据不一致,差点酿成重大事故。现在我做数据迁移时都会严格遵循"全量备份-增量同步-数据校验"的标准流程。 这些血泪教训让我明白,在技术这条路上,捷径往往是最远的路。每

By Ne0inhk