C++类和对象进阶:运算符重载深度详解

C++类和对象进阶:运算符重载

  • 前言
  • 引入
  • 运算符重载
    • 定义
    • 语法
    • 注意事项
    • 重载为全局函数
    • 重载为成员函数
    • 运算符重载的本质
  • 默认赋值运算符重载(默认成员函数)
  • 编译器自己生成的赋值运算符重载函数
    • 需要自己实现的场景
    • 总结默认赋值运算符重载
  • 拷贝构造函数和赋值重载的区分
    • 验证
  • 总结

前言

在C++中,运算符重载允许我们为自定义类型赋予与内置类型相似的操作方式,极大提升了代码的可读性和灵活性。本文将深入探讨运算符重载的规则与实现,并重点分析默认成员函数之一的赋值运算符重载函数

在这里插入图片描述

引入

class Date {
public:Date(int year = 2025, int month = 2, int day = 22) {this->_year = year;this->_month = month;this->_day = day;}
private:int _year;int _month;int _day;
};
int main(){Date d1, d2;//d1 == d2;	// 若无运算符重载,这样的写法未定义。//if(d1 > d2){;}	// 若无运算符重载,这样的写法未定义。
}

思考以下场景
如果想

  • 1. 比较两个日期类对象是否相等
  • 2. 两个日期相减的运算来计算相差的天数
  • 3. 计算一个日期100天后是什么日期

C++为了满足自定义类型中以上类似需求并为了增强代码的可读性,引入了运算符重载

运算符重载

定义

运算符重载是具有特殊函数名的函数,也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似

语法

函数名字为:关键字operator后面接需要重载的运算符符号
例如要对==进行重载

//假定返回值为bool
bool operator==(int x, int y);	//声明

函数原型:返回值类型 operator操作符(参数列表)

注意事项

  • 不能通过连接其他符号来创建新的操作符:比如operator@。
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义。
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针。
  • .*::, sizeof, ? : , . 注意以上5个运算符不能重载。

总结以上内容:

  • 函数格式返回类型 operator运算符(参数列表)
  • 关键限制
    1. 能重载C++中已有的运算符,不能创造新的运算符
    2. 重载运算符主要是针对自定义类型的,因此operator必须有一个类类型参数.
    3. 不能改变运算符对内置类型操作的原始含义
    4. 要重载的运算符有几个操作数,operator中就有几个参数(算上this指针)
    5. 以下运算符不可重载:.* :: sizeof ?: .

重载为全局函数

bool operator<(const Date& d1, const Date& d2) {	if (d1._year < d2._year)return true;else if (d1._year == d2._year && d1._month < d2._month)return true;else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day)return true;elsereturn false;
}
class Date {
public:Date(int year = 2025, int month = 2, int day = 22) {this->_year = year;this->_month = month;this->_day = day;}
//private:	//暂时设为public,是为了让全局重载的<可以访问到类内的成员变量
public:int _year;int _month;int _day;
};
int main(){Date d1(2025, 2, 12);Date d2(2024, 2, 12);cout << (d2 < d1) << endl;//d2 < d1 会被编译器转换成 d2.operator(d1),本质上是调用函数return 0;
}

在这里插入图片描述

注意:d1 < d2, 重载后的 <Date类对象使用<号时,左操作数是这里的d1,右操作数是这里的d2

这里会发现运算符重载成全局函数,需要成员变量是公有的,那么问题来了,封装性如何保证?

为解决这一问题,我们可以重载成成员函数。

重载为成员函数

//运算符重载
class Date {
public:Date(int year = 2025, int month = 2, int day = 22) {this->_year = year;this->_month = month;this->_day = day;}//操作符是几个操作数,operator函数就有几个参数(应该包括上隐含的this指针参数)//也可以全局重载,但在类内重载更方便,可以直接访问私有成员bool operator<(const Date& d) {	//自定义类型,最好传引用,类内不能通过形参d修改原变量,加上constif (this->_year < d._year)return true;else if (this->_year == d._year && this->_month < d._month)return true;else if (this->_year == d._year && this->_month == d._month && this->_day < d._day)return true;elsereturn false;}
private:int _year;int _month;int _day;
};
  • bool operator<(const Date& d);,这里需要注意的是,由于第一个形参是this, 左操作数是*this,是调用函数的对象,右操作数是传入的另一个对象
  • 是否需要重载运算符,要看这些运算符对该类型是否有意义

对比分析

特性成员函数形式全局函数形式
访问权限可直接访问私有成员需友元声明
左操作数类型必须是类对象任意类型
隐式this参数
对称性操作符不便于处理更适合(如<<流操作符)

掌握以上规则,我们便学会了如何对运算符进行重载

接下来来看六大默认成员函数中的:赋值运算符重载

运算符重载的本质

在这里插入图片描述
由上图汇编代码可以看到:
d1 < d2
d1.operator(d2)
本质都是调用了类内的函数。也正因如此,运算符重载函数也可以按照函数重载的规则重载

默认赋值运算符重载(默认成员函数)

我们早已知道,赋值运算符重载是类内的一个默认成员函数。

C语言中自定义类型可以完成赋值操作(例如:同类型结构体之间的赋值),C++中的class同样支持赋值操作,只不过C++对这一行为进行了优化与升级

  • 赋值运算符重载格式
    • 参数类型:const T&,传递引用可以提高传参效率
    • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
    • 检测是否自己给自己赋值,允许自己给自己进行赋值,但此时不需要拷贝,直接返回当前对象本身。
    • 返回*this :要复合连续赋值的含义
//运算符重载
class Date {
public:Date(int year = 2025, int month = 2, int day = 22) {this->_year = year;this->_month = month;this->_day = day;}//操作符是几个操作数,operator函数就有几个参数(应该包括上隐含的this指针参数)//也可以全局重载,但在类内重载更方便,可以直接访问私有成员bool operator<(const Date& d) {	//自定义类型,最好传引用,类内不能修改,加上constif (this->_year < d._year)return true;else if (this->_year == d._year && this->_month < d._month)return true;else if (this->_year == d._year && this->_month == d._month && this->_day < d._day)return true;elsereturn false;}//赋值运算符重载是默认成员函数,编译器会自己生成, 不能写成全局的,Date& operator=(const Date& d) {//if (*this != d)	// 这样子比较,代价有点大,将 != 重载后是可以实现的,但是没必要if (this != &d) {	//防止这样的赋值 d1 = d1   如果自己给自己赋值,可以不复制this->_year = d._year;this->_month = d._month;this->_day = d._day;}return *this;	//返回对象的别名,出了作用域,*this生命周期还在}
private:int _year;int _month;int _day;
};

注意赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:

  • 赋值运算符重载成全局函数时,就没有this指针了无法访问类内的私有变量,需要给两个参数,如下示例:
Date& operator=(Date& left, const Date& right){if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;}
  • 赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
    一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
    运算符重载只能是类的成员函数。

编译器自己生成的赋值运算符重载函数

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。

  • 观察以下程序
class Stack{
typedef int DataType;
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};
int main(){Stack s1;s1.Push(1);s1.Push(2);Stack s2;s2 = s1;return 0;
}

以上程序会报错
报错原因和往期文章中的拷贝构造函数类似。
在这里插入图片描述

  • main函数结束时,会调用Stack中的析构函数对s1和s2两个对象进行析构
  • 编译器默认生成的赋值运算符重载进行的是值拷贝。由于进行了s2 = s1的赋值操作,赋值过后, s2和s1中变量_array存放了同一块空间的地址
  • main函数结束后,会调用st1st2的析构函数,由于地址相同,则会对同一块空间析构两次(对同一块空间free两次)。因此会报错。

需要自己实现的场景

在这里插入图片描述

总结默认赋值运算符重载

  • 默认成员函数:默认赋值运算符重载是默认成员函数
  • 重载特性:只能重载成类的成员函数不能重载成全局函数,若定义为全局函数会与编译器默认生成的冲突。
  • 拷贝方式:编译器生成的默认赋值运算符重载函数完成字节序的值拷贝了(浅拷贝)
  • 需要手动实现的场景:如果类中未涉及到资源管理(在堆区申请空间),赋值运算符是否实现都可以;一旦涉及到资源管理则必须要手动实现深拷贝。

拷贝构造函数和赋值重载的区分

思考一下情景,会调用拷贝构造还是赋值重载?

Date d1(2025, 2, 12);
Date d2(2024, 3, 13);
//思考会分别调用什么函数
Date d3 = d1;	//拷贝构造  还是  赋值运算符重载 ?
d2 = d1;		//拷贝构造  还是  赋值运算符重载 ?

验证

这是我们的测试代码

class Date {
public:Date(int year = 2025, int month = 2, int day = 22){this->_year = year;this->_month = month;this->_day = day;}Date& operator=(const Date& d) {if (this != &d) {this->_year = d._year;this->_month = d._month;this->_day = d._day;}return *this;}Date(const Date& d) {cout << "Date(const Date& d) " << endl;_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main(){Date d1(2025, 2, 12);Date d2(2024, 3, 13);//思考会分别调用什么函数Date d3 = d1;	//拷贝构造  还是  赋值运算符重载 ?d2 = d1;		//拷贝构造  还是  赋值运算符重载 ?
}

我们来调试验证:

C++拷贝构造与赋值重载的区分

总结

//区分拷贝构造和赋值运算符重载
Date d1(2025, 2, 12);
Date d2(2024, 3, 13);
//用一个已经存在的对象初始化另一个对象  -----  调用拷贝构造函数
Date d3 = d1;	//等价于 Date d3(d1);//已经存在的两个对象之间赋值拷贝  -----  赋值运算符重载函数
d2 = d1;
  • 用一个已经存在的对象初始化另一个对象 ----- 调用拷贝构造函数
  • 已经存在的两个对象之间赋值拷贝 ----- 赋值运算符重载函数

总结

特性运算符重载赋值运算符重载
必要性增强代码可读性资源管理的必要手段
默认行为浅拷贝
典型应用场景算术运算、比较运算对象复制、资源管理
实现要点操作数类型、返回值优化深拷贝、自赋值检查

最佳实践建议

  1. 优先使用成员函数形式进行运算符重载
  2. 对于资源管理类必须实现深拷贝赋值
  3. 流操作符(<<, >>)建议采用全局函数+友元形式
  4. 保持运算符的语义一致性(例如+不应修改操作数)

通过合理使用运算符重载,可以显著提升代码的表达能力,使自定义类型的使用更加直观自然。但需谨记"能力越大责任越大",不当的运算符重载反而会降低代码可维护性。

文章到此结束啦,欢迎各位大佬在评论区讨论交流,如果觉得文章写的不错,还请留下免费的赞和收藏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/17376.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

three.js 使用geojson ,实现中国地图区域,边缘流动效果

three.js 使用geojson &#xff0c;实现中国地图区域&#xff0c;边缘流动效果 在线链接&#xff1a;https://threehub.cn/#/codeMirror?navigationThreeJS&classifyexpand&idgeoBorder 国内站点预览&#xff1a;http://threehub.cn github地址: https://github.co…

PortSwigger——WebSockets vulnerabilities

文章目录 一、WebSockets二、Lab: Manipulating WebSocket messages to exploit vulnerabilities三、Lab: Manipulating the WebSocket handshake to exploit vulnerabilities四、Using cross-site WebSockets to exploit vulnerabilities4.1 跨站WebSocket劫持&#xff08;cro…

【数据结构初阶第十节】队列(详解+附源码)

好久不见。。。别不开心了&#xff0c;听听喜欢的歌吧 必须有为成功付出代价的决心&#xff0c;然后想办法付出这个代价。云边有个稻草人-CSDN博客 目录 一、概念和结构 二、队列的实现 Queue.h Queue.c test.c Relaxing Time&#xff01; ————————————《有没…

AVL树:高效平衡的二叉搜索树

&#x1f31f; 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。&#x1f31f; 引言&#x1f914; 在数据结构的奇妙世界里&#xff0c;二叉搜索树&#xff08;BST&#xff09;原本是查找数据的好帮手。想象一下…

Qt Designer菜鸟使用教程(实现一个本地英文翻译软件)

1 安装Qt Designer 安装这个包的时候会自带安装 Qt Designer, 安装目录为python的安装根目录的 Lib/site-packages/qt5_applications/Qt/bin 目录下。 pip install pyqt5-tools2 新建窗体 2.1 新建主窗体 创建之后如下图&#xff1a; 设置主窗口大小&#xff1a; 设置窗…

机械学习基础-5.分类-数据建模与机械智能课程自留

data modeling and machine intelligence - CLASSIFICATION 为什么我们不将回归技术用于分类&#xff1f;贝叶斯分类器&#xff08;The Bayes Classifier&#xff09;逻辑回归&#xff08;Logistic Regression&#xff09;对逻辑回归的更多直观理解逻辑 /sigmoid 函数的导数我们…

C++ 网络编程

1. socket Socket 是一种用于网络通信的编程接口&#xff0c;它提供了一种类似于文件描述符的接口&#xff0c;允许不同计算机之间的进程进行通信。Socket 可以工作在多种协议上&#xff0c;最常用的是 TCP/IP 和 UDP/IP 协议 1.1 UDP 1.1.1 概念 UDP&#xff08;用户数据报协…

C/C++内存管理

目录 前言 1、C/C内存划分 2、C语言中的动态内存管理方式 3、C内存管理方式 3.1操作内置类型 3.2操作自定义类型 3.3为什么对应的new和delete必须搭配使用&#xff08;了解&#xff09; 4、operator new与operator delete函数 5、new和delete的实现原理 5.1内置类型 5…

微软开源GraphRAG的使用教程-使用自定义数据测试GraphRAG

微软在今年4月份的时候提出了GraphRAG的概念&#xff0c;然后在上周开源了GraphRAG,Github链接见https://github.com/microsoft/graphrag,截止当前&#xff0c;已有6900Star。 安装教程 官方推荐使用Python3.10-3.12版本&#xff0c;我使用Python3.10版本安装时&#xff0c;在…

RK3568平台开发系列讲解(调试篇)网卡队列均衡负载

🚀返回专栏总目录 文章目录 一、RPS 的介绍1. RPS 的工作原理2. RPS 配置3. 启用和调优 RPS4. RPS 优势二、下行测试iperf测试沉淀、分享、成长,让自己和他人都能有所收获!😄 RPS(Receive Packet Steering) 是一种用于提高网络接收性能的技术,通常用于多核处理器系统中…

RagFlow + Docker Desktop + Ollama + DeepSeek-R1本地部署自己的本地AI大模型工具

前期准备 首先&#xff0c;我们需要下载 Ollama 以及配置相关环境。 Ollama 的 GitHub仓库 &#xff08;https://github.com/ollama/ollama&#xff09;中提供了详细的说明&#xff0c;简单总结如下: Step1&#xff1a;下载 Ollama 下载&#xff08;https://ollama.com/dow…

变分边界详解

起因 当时看VAE论文时有这么一段&#xff0c;但是看完直接一头雾水&#xff0c;这都那跟哪&#xff0c;第一个公式咋做的变换就变出那么一堆。网上搜了很多博客都语焉不详&#xff0c;只好自己来写一篇&#xff0c;希望能解答后来人的疑惑。 公式1 参考文章&#xff1a;证据…

云消息队列 ApsaraMQ Serverless 演进:高弹性低成本、更稳定更安全、智能化免运维

如今&#xff0c;消息队列已成为分布式架构中不可或缺的关键服务&#xff0c;为电商、物联网、游戏和教育等行业&#xff0c;提供了异步解耦、集成、高性能和高可靠的核心价值。 过去一年&#xff0c;我们发布了云消息队列 ApsaraMQ 全系列产品 Serverless 化&#xff0c;面向…

【蓝桥杯嵌入式】8_IIC通信-eeprom读写

全部代码网盘自取 链接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码&#xff1a;3ii2 1、电路图 这个电路允许通过I2C总线与EEPROM(M24C02-WMN6TP)和数字电位器(MCP4017T-104ELT)进行通信。EEPROM用于存储数据&#xff0c;而数字电位器可以用…

DeepSeek处理自有业务的案例:让AI给你写一份小众编辑器(EverEdit)的语法着色文件

1 DeepSeek处理自有业务的案例&#xff1a;让AI给你写一份小众编辑器(EverEdit)的语法着色文件 1.1 背景 AI能力再强&#xff0c;如果不能在企业的自有业务上产生助益&#xff0c;那基本也是一无是处。将企业的自有业务上传到线上训练&#xff0c;那是脑子进水的做法&#xff…

Java常用设计模式面试题总结(内容详细,简单易懂)

设计模式的分类 创建型模式&#xff1a;通过隐藏对象创建的细节&#xff0c;避免直接使用 new 关键字实例化对象&#xff0c;从而使程序在判断和创建对象时更具灵活性。常见的模式包括&#xff1a; 工厂模式抽象工厂模式单例模式建造者模式原型模式 结构型模式&#xff1a;通…

使用HX搭建UNI-APP云开发项目(适合新手小白与想学云开发的宝子)

什么是uni-app云开发 uni-app云开发是uni-app提供的一套后端服务,它可以帮助开发者快速搭建起一个完整的后端服务,包括数据库、云函数、存储等。开发者只需要关注前端页面的开发,后端服务由uni-app云开发提供。 uni-app云开发的优势: 快速搭建后端服务:uni-app云开发提供了…

零基础学CocosCreator·第九季-网络游戏同步策略与ESC架构

课程里的版本好像是1.9&#xff0c;目前使用版本为3.8.3 开始~ 目录 状态同步帧同步帧同步客户端帧同步服务端ECS框架概念ECS的解释ECS的特点EntityComponentSystemWorld ECS实现逻辑帧&渲染帧 ECS框架使用帧同步&ECS 状态同步 一般游戏的同步策略有两种&#xff1a;…

最新版Edge浏览器集成ActiveX控件之金山WpsDocFrame控件

背景 WpsDocFrame控件‌是由金山公司开发的ActiveX控件&#xff0c;主要用于OA系统中&#xff0c;支持在浏览器中嵌入WPS文档的查看和编辑功能。 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有…

Win10系统IP地址修改_出现了一个意外的情况不能完成所有你在设置中所要求的更改---Windows工作笔记001

今天在修改win10系统中的ip地址的时候报错了 来看看如何解决吧,挺简单,简单记录一下 这个时候就需要使用cmd命令来修改 一定要使用,管理员权限,运行cmd才可以 然后首先: 输入 netsh 然后输入 ip 然后输入: set address "以太网" 172.19.126.199 255.255.255.0…