【C++ | 重载运算符】一文弄懂C++运算符重载,怎样声明、定义运算符,重载为友元函数

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-06-21 22:14:42

本文未经允许,不得转发!!!

目录

  • 🎄一、为什么需要重载运算符
  • 🎄二、怎样声明、定义重载运算符函数
    • ✨2.1 重载运算符的限制
    • ✨2.2 重载运算符定义
    • ✨2.3 例子-重载赋值运算符
  • 🎄三、重载为友元函数
  • 🎄四、总结



在这里插入图片描述

🎄一、为什么需要重载运算符

正常情况下,C++ 的运算符( +、-、*、/ 等)只能用于对基本类型的常量或变量进行运算,而不能用于类对象之间的运算。

类的对象直接的运算虽然可以通过成员函数或全局函数去实现,如date.Add(1),但这样的写法有时不易理解。

C++的提供了 重载运算符 的特性,允许将一些常见的运算符根据自定义类型的需要去重新定义,尽量让对象之间的运算可以像基本类型的运算一样简单、好理解。

运算符重载的目的是使得 C++ 中的运算符也能够用来操作对象。

下面例子演示了CDate类对象 使用 Add函数与 重载运算符operator+ 的运算结果:

// g++ 19_operatorAdd.cpp 
#include <iostream>
using namespace std;

class CDate
{
public:
	CDate(int year, int mon, int day);	// 构造函数声明
	CDate(const CDate& date);			// 拷贝构造函数声明
	CDate Add(int day);					// Add成员函数声明
	CDate operator+(int day);			// 加号运算符声明
	void show()
	{
		cout << "Date: " << m_year << "." << m_mon << "." << m_day << endl;
	}

private:
	int m_year;
	int m_mon;
	int m_day;
};

// 构造函数定义
CDate::CDate(int year, int mon, int day)
{
	m_year = year;
	m_mon = mon;
	m_day = day;
	//cout << "Calling Constructor" << ", this=" << this <<endl;
}

// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	//cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}

// Add成员函数定义
CDate CDate::Add(int day)
{
	CDate temp = *this;
	temp.m_day += day;
	cout << "Calling CDate::Add" << ", this=" << &temp << endl;
	return temp;
}

// 加号运算符定义
CDate CDate::operator+(int day)
{
	CDate temp = *this;
	temp.m_day += day;
	cout << "Calling operator+(int)" << ", this=" << &temp << endl;
	return temp;
}

int main()
{
	CDate date(2024,6,20);
	date.show();
	cout << endl;
	
	CDate date1 = date.Add(1);		// 成员函数
	date1.show();
	cout << endl;
	
	CDate date2 = date.operator+(1);// 直接调用运算符函数
	date2.show();
	cout << endl;
	
	CDate date3 = date + 1;			// 好理解
	date3.show();
	cout << endl;
	
	return 0;
}

运行结果如下:
在这里插入图片描述
例子中两个成员函数Addoperator+的实现是一样的,但三种调用方式中,date + 1会比较好理解。

例子中的date.operator+(1);等价于date + 1;在运算符表示法中,运算符左侧的对象(这里为 date)是调用对象,运算符右边的对象(这里为 1) 是作为参数被传递的对象


在这里插入图片描述

🎄二、怎样声明、定义重载运算符函数

重载运算符,本质上也是函数的一种,使用关键字operator加上运算符作为函数名,运算符函数的格式大致如下:

返回值类型 operator 运算符(形参列表)
{
	...
}

函数名是清楚了,那形参怎样写呢? 返回值类型应该是什么类型?这小节的剩下内容会解决这两个问题。

✨2.1 重载运算符的限制

清楚了重载运算符的大致格式之后,再来了解一下C++对重载运算符的限制:

  • 1、重载后的运算符必须至少有一个操作数是用户定义的类型(类、结构体、枚举),这样可以防止给基本类型重载运算符;
  • 2、使用运算符时不能违反运算符原来的句法规则,例如,不能将加号(+)重载为减法运算。也不能修改运算符优先级;
  • 3、不能创建新运算符,例如,不能定义 operator **( )函数来表示求幂。
  • 4、不能重载下面的运算符:
    • sizeof:sizeof 运算符。
    • .:成员运算符。
    • .*:成员指针运算符。
    • :::作用域解析运算符。
    • ?::条件运算符。
    • typeid:—个 RTTI 运算符。
    • const_cast:强制类型转换运算符。
    • dynamic_cast:强制类型转换运算符。
    • reinterpret_cast:强制类型转换运算符。
    • static_cast:强制类型转换运算符。
  • 5、下面的运算符只能通过成员函数进行重载
    • =: 赋值运算符。
    • ( ):函数调用运算符。
    • [ ]:下标运算符。
    • ->:通过指针访问类成员的运算符。

在这里插入图片描述


✨2.2 重载运算符定义

我们知道重载运算符的函数名是:operator运算符,那么形参个数和类型应该是什么?

重载运算符函数形参

重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。一元运算符有一个参数, 二元运算符有两个。对于二元运算符来说,左侧运算对象传递给第一个参数,而右侧运算对象传递给第二个参数。
如果一个运算符函数是成员函数, 则它的第一个(左侧)运算对象绑定到隐式的 this 指针,因此,成员运算符函数的(显式)参数数量比运算符的运算对象总数少一个。

从上面这段话可以知道,重载运算符函数的形参由运算符的操作数的个数、类型决定的。如果是成员函数,则左边操作对象的类型的形参会省略,以this指针传到运算符函数。以CDate类为例:

CDate date(2024,6,20);
CDate date1(2024,6,20);
CDate date2 = date + date1;	// 形参列表是:(const CDate &date, const CDate &date1)
CDate date2 = date + 1;		// 形参列表是:(const CDate &date, int i)
CDate date2 = date + 1.5;	// 形参列表是:(const CDate &date, double d)

重载运算符函数返回值类型
重载运算符的返回类型通常情况下应该与其内置版本的返回类型兼容:逻辑运算符和关系运算符应该返冋 bool,算术运算符应该返回一个类类型的值,赋值运算符和复合赋值运算符则应该返冋左侧运算对象的一个引用。

下面代码以重载加号运算符为例,这里是让一个CDate对象加上一个整型:

// 加号运算符定义
CDate CDate::operator+(int day)
{
	CDate temp = *this;
	temp.m_day += day;
	cout << "Calling operator+(int)" << ", this=" << &temp << endl;
	return temp;
}

✨2.3 例子-重载赋值运算符

赋值运算符的重载也是C++的一个特别重要的知识点,可以重载为 拷贝赋值运算符 、移动赋值运算符 两种,需要详细了解的可以参考这两篇文章:
【C++ | 拷贝赋值运算符函数】一文了解C++的 拷贝赋值运算符函数
【C++ | 移动赋值运算符】一文了解C++11的 移动赋值运算符

这个小节只介绍怎样声明、定义赋值运算符,拷贝赋值运算符声明、定义如下。赋值运算符只能重载为函数名为operator=,其形参都是一个const CDate &类型的,返回值是该类型的引用(这样支持连续赋值a=b=c):

CDate& operator=(const CDate &date);// 拷贝赋值运算符声明
// 赋值运算符函数定义
CDate& CDate::operator=(const CDate& date)
{
	if(this == &date)	// 赋值给自身
		return *this;
	delete [] str;		// 释放旧的数据
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	cout << "Calling operator=" << ", this=" << this << ", Copy Data" <<endl;
	return *this;
}

在这里插入图片描述

🎄三、重载为友元函数

如果一个运算符的左操作数是类对象,则这个运算符一般是可以重载为成员函数的,例如上文的date+1。那如果表达式是1+date,则上面重载的加号运算符operator+匹配不到这个表达式。这是我们就需要将重载运算符写到类外部,而类外定义的函数无法访问类的私有成员,所以又需要将这个类外定义的函数声明为类的友元函数。关于C++的友元,有不了解的,可以参考这篇文章:C++ | 友元(friend)】友元函数、友元类、友元成员函数详解及例子代码。

下面看看怎样将加号运算符重载为友元函数:

  • 1、看要实现怎样的操作。这里是要实现1+date
  • 2、确定重载运算符的形参,1+date的第一个操作数是int型,第二个操作数是CDate,所以形参可以是:(int i, const CDate &date)
  • 3、确定返回值类型,这里我们就直接返回 CDate 类,所以函数原型如下;
    CDate operator+(int day, const CDate &date);
    
  • 4、写具体函数实现,我们这里想要的是,让天数加上 i ,完整实现如下:
    // 友元函数定义
    CDate operator+(int day, const CDate &date)
    {
    	CDate temp = date;
    	temp.m_day += day;
    	cout << "Calling operator+(int, CDate)" << ", temp=" << &temp << endl;
    	return temp;
    }
    
  • 5、将函数声明为类的友元函数,在类声明开头使用friend如下声明:
    class CDate
    {
    	friend CDate operator+(int day, const CDate &date);	// 友元函数声明
    	...
    }
    

重载运算符时,优先重载为成员函数,如果不能重载为成员函数,就重载为友元函数。
编译器查找运算符函数的顺序是,先去类中查找匹配的成员函数,如果找不到就到全局函数寻找。


在这里插入图片描述

🎄四、总结

👉本文主要介绍了为什么需要重载运算符,重载运算符有哪些限制,怎样声明、定义运算符函数,怎样将运算符重载为友元函数。

上面例子的完整代码:

// g++ 19_operator.cpp 
#include <iostream>
using namespace std;

class CDate
{
	friend CDate operator+(int day, const CDate &date);	// 友元函数声明
public:
	CDate(int year, int mon, int day);	// 构造函数声明
	CDate(const CDate& date);			// 拷贝构造函数声明
	CDate& operator=(const CDate &date);// 拷贝赋值运算符声明
	CDate operator+(int day);			// 加号运算符声明
	CDate operator+(const CDate& date);	// 加号运算符声明
	
	void show()
	{
		cout << "Date: " << m_year << "." << m_mon << "." << m_day << endl;
	}

private:
	int m_year;
	int m_mon;
	int m_day;
};

// 构造函数定义
CDate::CDate(int year, int mon, int day)
{
	m_year = year;
	m_mon = mon;
	m_day = day;
	//cout << "Calling Constructor" << ", this=" << this <<endl;
}

// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	//cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}

// 赋值运算符函数定义
CDate& CDate::operator=(const CDate& date)
{
	if(this == &date)	// 赋值给自身
		return *this;
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	cout << "Calling operator=" << ", this=" << this << ", Copy Data" <<endl;
	return *this;
}

// 加号运算符定义
CDate CDate::operator+(int day)
{
	CDate temp = *this;
	temp.m_day += day;
	cout << "Calling operator+(int)" << ", this=" << &temp << endl;
	return temp;
}

// 加号运算符定义
CDate CDate::operator+(const CDate& date)
{
	CDate temp = *this;
	temp.m_day += date.m_day;
	cout << "Calling operator+(const CDate&)" << ", this=" << &temp << endl;
	return temp;
}

// 友元函数定义
CDate operator+(int day, const CDate &date)
{
	CDate temp = date;
	temp.m_day += day;
	cout << "Calling operator+(int, CDate)" << ", temp=" << &temp << endl;
	return temp;
}

int main()
{
	CDate date(2024,6,20);
	date.show();
	cout << endl;

	CDate date1 = date + 1;
	date1.show();
	cout << endl;
	
	CDate date2 = date + date1;
	date2.show();
	cout << endl;
	
	date2 = 1+date;
	date2.show();
	cout << endl;
	
	return 0;
}

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据库讲解---(数据库保护)【下】

目录 一.并发控制 1.1并发操作引发的问题 1.1.1丢失修改 1.1.2不可重复读 1.1.3读”脏“数据 1.2调度及其可串行化 1.3事务的隔离性级别 1.4封锁技术【重要】 1.4.1排他锁 1.4.2共享锁 1.5封锁协议 1.5.1一级封锁协议 1.5.2二级封锁协议 1.5.3三级封锁协议 1.5.…

全新的会声会影2024最新官方免费永久破解版本下载

大家好&#xff01;&#x1f31f; 今天要跟大家分享一个超级棒的视频编辑软件&#xff0c;它就是全新的会声会影2024最新永久破解版本。作为视频创作爱好者的我&#xff0c;真的被这款软件的强大功能和便利性彻底征服了&#xff01;&#x1f389;&#x1f389;&#x1f3ac; 【…

什么是OAuth2分布式授权协议?

今天我将和大家一起探讨在系统安全领域非常常见的一种授权协议&#xff0c;这就是OAuth2协议&#xff0c;这个协议通常用于对请求访问进行安全控制。在引入这个协议之前&#xff0c;让我们先来回顾两个基本概念&#xff0c;一个是认证&#xff0c;一个是授权。这两个概念比较容…

【Splitpanes】Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。

【Splitpanes】Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。 介绍安装使用示例与文档 介绍 Vue.js 靠谱、简单并支持触摸的窗格分割器/调整器。用于实现可调节窗口&#xff0c;支持Vue2、Vue3。 安装 Vue3 npm install splitpanesVue2 npm install splitpaneslegac…

CentOS 7.9检测硬盘坏区、实物定位(三)

系列文章目录 CentOS 7.9上创建JBOD&#xff08;一&#xff09; CentOS 7.9上创建的JBOD阵列恢复&#xff08;二&#xff09; 文章目录 系列文章目录前言一、在系统中找到硬盘对应的盘符二、使用命令定位实物1.badblocks检测坏块2.对2T以上的硬盘检测&#xff08;对本篇非必要…

百度文心智能体,创建属于自己的智能体应用

百度文心智能体平台为你开启。百度文心智能体平台&#xff0c;创建属于自己的智能体应用。百度文心智能体平台是百度旗下的智能AI平台&#xff0c;集成了先进的自然语言处理技术和人工智能技术&#xff0c;可以用来创建属于自己的智能体应用&#xff0c;访问官网链接&#xff1…

数据仓库 基础教程

数据仓库 基础教程 1. 数据仓库概述 数据仓库(Data Warehouse,简称DW或者DWH)是通过集成来自多个异构数据源的数据来构建的。它支持分析报告、结构化和/或特别查询和决策制定。本教程采用循序渐进的方法来解释数据仓库的所有必要概念。 “数据仓库”一词最早是由Bill Inmon在1…

fataadmin导出Exel文件图片太大

// 导出图片过大处理 exportOptions: {ignoreColumn: [0, operate],onBeforeSaveToFile: function (data, fileName, type, charset, encoding, bom) {return $.fn.bootstrapTable.defaults.extend.savestatus;},onCellHtmlHyperlink: function ($cell, rowIndex, colIndex, hr…

C#开发-集合使用和技巧(一)常用集合和方法介绍

C#开发-集合使用和技巧 &#xff08;一&#xff09;常用集合和方法介绍常用集合和方法介绍 C#开发-集合使用和技巧1. 集合基础集合介绍集合跟数组对比 2.我们常用的集合类型列表List<T>键值对集合Dictionary<TKey,TValue>队列Queue<T>其他一些集合类型堆栈St…

修改vscode的主题颜色

1、首选项--主题--颜色主题 2、选择一个喜欢的颜色主题 这样就可以了。

工作实践:11种API性能优化方法

一、索引优化 接口性能优化时&#xff0c;大家第一个想到的通常是&#xff1a;优化索引。 确实&#xff0c;优化索引的成本是最小的。 你可以通过查看线上日志或监控报告&#xff0c;发现某个接口使用的某条SQL语句耗时较长。 此时&#xff0c;你可能会有以下疑问&#xff…

Mistral AI最新力作——Mistral Large媲美GPT-4

Mistral AI自豪地宣布&#xff0c;他们的最新力作——Mistral Large&#xff0c;已经正式面世。这款尖端的文本生成模型不仅在多语言理解上表现出色&#xff0c;更在推理能力上达到了顶级水平。Mistral Large能够处理包括文本理解、转换和代码生成在内的复杂多语言推理任务。 M…

go-admin-ui开源后台管理系统华为云部署

1.华为云开通8000与9527端口 2.编译 编译成功 3.发布到远程服务器 4.登陆华为云终端 5.安装Nginx 6.查看服务启动状态 7.添加网站 添加与修改配置www-data 改为 www 自定义日志输出格式 添加网站配置文件go_admin_ui.conf 添加如下内容: location 下的root指向网站文件夹 修…

former系列在时间序列预测任务上的研究综述

总&#xff1a;基于Transformer的LSTF解决方案 现有基于Transformer的LSTM解决方案设计要素总结如下&#xff1a; 从图中可以看出&#xff0c;Transformer在时序中应用具体包含以下几个步骤&#xff1a; 1&#xff09;时序分解&#xff1a;对于数据处理&#xff0c;TSF中0均…

zookeeper + kafka消息队列

zookeeper kafka 消息队列 一、消息队列简介 1、什么是消息队列 消息队列&#xff08;Message Queue&#xff09;是一种用于跨进程或分布式系统中传递消息的通信机制。消息队列在异步通信、系统解耦、负载均衡和容错方面具有重要作用。 &#xff08;1&#xff09;特性 异步…

“打造智能售货机系统,基于ruoyi微服务版本生成基础代码“

目录 # 开篇 1. 菜单 2. 字典配置 3. 表配置 3.1 导入表 3.2 区域管理 3.3 合作商管理 3.4 点位管理 4. 代码导入 4.1 后端代码生成 4.2 前端代码生成 5. 数据库代码执行 6. 点位管理菜单顺序修改 7. 页面展示 8. 附加设备表 8.1 新增设备管理菜单 8.2 创建字…

GBDT算法详解

GBDT算法详解 梯度提升决策树&#xff08;Gradient Boosting Decision Trees&#xff0c;GBDT&#xff09;是机器学习中一种强大的集成算法。它通过构建一系列的决策树&#xff0c;并逐步优化模型的预测能力&#xff0c;在各种回归和分类任务中取得了显著的效果。本文将详细介…

【mysql 安装启动失败】 没有网下 libssl.so.10 not found 如何解决?

问题描述&#xff1a; libssl.so.10 > not found libcrypto.so.10 > not found [rootmysql tools]# ls -l /usr/sbin/mysqld -rwxr-xr-x. 1 root root 64290024 Sep 14 2022 /usr/sbin/mysqld [rootmysql tools]# ldd /usr/sbin/mysqldlinux-vdso.so.1 (0x00007fff97105…

Blazor 组件:创建、生命周期、嵌套和 UI 集成

在本文中&#xff0c;您将获得以下问题的答案。 什么是 Blazor 组件&#xff1f;如何使用组件&#xff1f;Blazor 组件的生命周期是什么&#xff1f;我们可以从一个组件调用另一个组件吗&#xff1f;如何创建 Blazor 组件&#xff1f;在组件中哪里写 C# 代码&#xff1f; 什么…

碳化硅陶瓷膜的生产工艺和应用

一、生产工艺 碳化硅陶瓷膜的生产工艺多样&#xff0c;其中浸渍提拉法和喷涂法为两大主流技术。 浸渍提拉法 浸渍提拉法是一种广泛应用的制备方法。其过程主要包括&#xff1a;先将陶瓷颗粒或者聚合物前体分散在水或有机溶剂中&#xff0c;形成均质稳定的制膜液。随后&#xff…