【浅尝C++】运算符重载(含类的3大默认成员函数:赋值、取地址、const对象取地址运算符重载)

在这里插入图片描述

🎈归属专栏:浅尝C++
🚗个人主页:Jammingpro
🐟记录一句:在Linux与C++中来回横跳,哪个学累了,就去学另外一个~~


文章前言:本篇文章简要介绍C++的运算符重载,同时接着上一篇文章,继续介绍类的余下3个成员函数,即赋值运算符重载、普通对象取地址运算符重载、const对象取地址运算符重载。


文章目录

  • 赋值运算符重载
    • 运算符重载
    • 赋值运算符重载
    • 前置后置++及--重载
  • 普通对象及const对象取地址运算符重载


我们知道,如果一个类什么都没有,那么它就是一个空类。空类真的什么都没有吗?答案是否定的。任何类在什么都不写时,编译器会自动生成下图的6个默认成员函数,上一篇文章已经介绍了构造函数、析构函数及拷贝构造函数,这一篇文章将介绍余下的3个默认成员函数。👇
ps:默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
在这里插入图片描述

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)

注意:
①不能通过连接其他符号来创建新的操作符:比如operator@ (必须是C++中已经存在的运算符,其他符号不能重载)
②重载操作符必须有一个类类型参数 (即调用重载运算符的变量,其中一个必须是类类型)
③用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
④作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this(第一个参数是类对象自身,不需要用户显示传入,编译器会自动传入该对象的this指针)。
.*::sizeof?:. 注意以上5个运算符不能重载。

下面我们实现一个复数类,并在类内重载==运算符👇

#include <iostream>
using namespace std;

class Complex
{
public:
	Complex(double r, double i)
	{
		_real = r;
		_imag = i;
	}
	bool operator==(const Complex& obj)
	{
		return _real == obj._real && _imag == obj._imag;
	}
private:
	double _real;
	double _imag;
};

int main()
{
	Complex c1(1, 2);
	Complex c2(1, 2);
	if(c1 == c2)
		cout << "they are equal" << endl;
	else
		cout << "too bad" << endl;
	return 0;
}

可不可以不在类内实现呢?当然可以,我们可以写成下方所示的全局函数👇
ps:下面代码涉及到友元的概念,如果还不知道友元是什么,可以将复数的属性设为公有的(public),并将带friend关键字的行删除,这样操作代码仍可以正常运行。

#include <iostream>
class Complex
{
public:
	Complex(double r, double i)
	{
		_real = r;
		_imag = i;
	}
private:
	friend bool operator==(const Complex& c1, const Complex& c2);
	double _real;
	double _imag;
};

bool operator==(const Complex& c1, const Complex& c2)
{
	return c1._real == c2._real && c1._imag == c2._imag;
}

int main()
{
	Complex c1(1, 2);
	Complex c2(1, 2);
	if(c1 == c2)
		cout << "they are equal" << endl;
	else
		cout << "too bad" << endl;
	return 0;
}

赋值运算符重载

赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要符合连续赋值的含义
下面的复数类显示给出了赋值运算符重载,在下面代码注释中,将给出上面重载格式的详细描述👇

#include <iostream>
using namespace std;

class Complex
{
public:
	//复数类的构造函数
	Complex(double r, double i)
	{
		_real =r;
		_imag = i;
	}
	//复数类的拷贝构造函数
	Complex(const Complex& c)
	{
		_real = c._real;
		_imag = c._imag;
	}
	//赋值运算符重载
	//返回参数使用引用是为了提高效率,避免调用拷贝函数构造临时对象。这里也可以使用非引用返回
	//参数使用const是为了避免赋值过程中被修改,引用传递是为了提高效率
	Complex& operator=(const Complex& c)
	{
		//防止自己给自己赋值
		if(this != &c)
		{
			_real = c._real;
			_imag = c._imag;
			return *this;
			//返回*this,即返回当前赋值运算符左值对象自身
		}
	}private:
	double _real;
	double _imag;
}

赋值运算符只能重载成类的成员函数不能重载成全局函数
下面这段代码将赋值运算符重载为全局函数,下面代码在编译时将报错
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

#include <iostream>
using namespace std;

class Complex
{
public:
	Complex(double r, double i)
	{
		_real = r;
		_imag = i;
	}
	Complex(const Complex& c)
	{
		_real = c._real;
		_imag = c._imag;
	}
private:
	friend Complex& operator=(const Complex& c1, const Complex& c2);
	double _real;
	double _imag;
};

Complex& operator=(const Complex& c1, const Complex& c2)
{
	if (&c1 != &c2)
	{
		c1._real = c2._real;
		c1._imag = c2._imag;
		return c1;
	}
}

int main()
{
	Complex c1(1, 2);
	Complex c2 = c1;
}

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝(即浅拷贝)。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像复数类这样的类是没必要的。跟拷贝构造函数一样,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。像下面的Stack类就需要自己实现重载赋值运算符。

class Stack
{
public:
	Stack(int n)
	{
		m_mem = new char[n];
		m_size = 0;
		m_capacity = n;
	}
	Stack& operator=(const Stack& s)
	{
		m_mem = new char[s.m_capacity];
		strcpy(m_mem, s.m_mem);
		m_size = s.m_size;
		m_capacity = s.m_capacity;
		return *this;
	}
private:
	char* m_mem;
	int m_size;
	int m_capacity;
};

下面图片演示的是编译器自动生成的重载赋值函数,其为值拷贝(即浅拷贝)。导致两个Stack对象指向同一内存空间,当两个Stack对象生命周期结束后,将分别释放该内存空间,一个内存空间被重复释放,将导致错误。
在这里插入图片描述

前置后置++及–重载

既然上面将运算符重载讲解的差不多了,这里就顺带叙述以下前置后置++和–。前置和后置的区别在于:前置运算符重载无需参数,后置运算符重载需要一个占位参数。具体讲解内容将在下面的代码注释中给出👇

class Counter
{
public:
	Counter(int n)
	{
		storage = n;
	}
	//前置++运算符重载,参数列表无参数
	Counter& operator++()
	{
		storage++;
		return *this;
	}
	//后置++运算符,参数列表需要一个占位参数
	//后置运算符返回的是++之前的值,因此要先保存一份原来的值,而保存下来的值存储在临时变量中,因此不能返回引用
	Counter operator++(int)
	{
		Counter copy = *this;
		++storage;
		return copy;
	}
	//前后置--与上面同理
private:
	int storage;
}

从上面代码可以知道,后置运算符的效率低于前置运算符,因为后置运算符需要创建临时对象保存当前对象+1或-1前的状态。同时,由于保存对象原状态的变量为临时变量,函数执行结束后生命周期结束,因此只能使用值返回,不能使用引用返回。创建值返回的临时变量需要再调用1次宝贝构造函数。后置运算符共调用了2次拷贝构造函数创建2个临时对象,因此效率较低。

普通对象及const对象取地址运算符重载

这一部分的代码实现与上述运算符重载类似,这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载。直接上代码👇

class Complex
{
public:
	Complex* operator&()
	{
		return this;
	}
	const Complex* operator&() const//->这里函数后面的const相当于给this指针加上const属性,防止this指针指向的对象内容被修改
	{
		return this;
	}
private:
	double _real;
	double _imag;
}

上面出现了const关键字,我们这通过一些问题对const关键字做一些介绍:
①const对象可以调用非const成员函数吗?
const对象是不可以修改的,非const成员函数可以修改对象中的成员,显然const对象不可以调用非const成员函数。
②非const对象可以调用const成员函数吗?
非const对象的成员数值可以修改,既然可以修改,那么就可以调用const和非const的成员函数了。
③const成员函数内可以调用其它的非const成员函数吗?
const成员函数是为了不修改对象本身,而非const成员函数可以修改对象成员,因此,const成员函数内不可以调用其它的非const成员函数
④非const成员函数内可以调用其它的const成员函数吗?
非const成员函数内可以修改对象成员,因此它可以调其他任何的const和非const成员函数。


文章结语:这篇文章对C++中的运算符重载,同时接着上一篇文章,继续介绍类的余下3个成员函数,即赋值运算符重载、普通对象取地址运算符重载、const对象取地址运算符重载。
🎈欢迎进入浅尝C++专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

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

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

相关文章

如何用CHAT写“科技探索者”视频号运营方案

问CHAT&#xff1a;生成一篇“科技探索者”视频号运营方案&#xff0c;要求内容&#xff1a; &#xff08;1&#xff09;视频号的定位、面向的人群、主要发布哪方面的内容 &#xff08;2&#xff09;视频号的内容设计&#xff08;用什么样的方式来体现、最好有内容创意&#xf…

Java大型智慧工地APP云平台源码带AI智能识别功能

智慧工地为建筑全生命周期赋能&#xff0c;用创新的可视化与智能化方法&#xff0c;降低成本&#xff0c;创造价值。 一、智慧工地APP概述 智慧工地”立足于互联网&#xff0c;采用云计算&#xff0c;大数据和物联网等技术手段&#xff0c;针对当前建筑行业的特点&#xff0c;…

Spark local模式的安装部署

安装与配置Spark开发环境。 相关知识 Apache Spark是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab(加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架&#xff0c;Spark拥有Hadoop MapReduce所具有的优点&#xff1b;但…

Linux 进程(二)

1.当前工作目录 Linux 下使用 ls /proc 查看程序中的进程&#xff0c;其中这些蓝色的数字代表的就是进程。 其中cwd(current working directory)就是当前工作目录&#xff0c;那么为什么cwd 和 exe 是在同一级目录下呢因为 进程需要依赖可执行程序&#xff0c;可执行程序需要依…

Reactor模式

Reactor模式有点类似事件驱动模式。在事件驱动模式中&#xff0c;当有事件触发时&#xff0c;事件源会将事件分发到Handler&#xff08;处理器&#xff09;&#xff0c;由Handler负责事件处理。Reactor模式中的反应器角色类似于事件驱动 模式中的事件分发器&#xff08;Dispatc…

操作系统原理-作业一-进程同步

1.某理发店可同时供 10 人理发&#xff0c;当店中顾客少于 10 人时&#xff0c;则店外的顾客可立即进入&#xff0c;否则需在外面等待。请定义所需信号量并写出信号量各种取值( 大于 0 、等于 0 、小于0)分别代表的含义&#xff0c;并用 P 、 V 操作编程实现完成多个顾…

HCIP---MPLS---VPN

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 MPLS协议使用标签交换来转发报文&#xff0c;最初是为了提高IP报文转发效率而设计的&#xff0c;但是后来随着硬件性能的提升&#xff0c;路由表已经不再是路由表/防火墙的转发瓶颈&#…

树与二叉树堆:链式二叉树的实现

目录 链式二叉树的实现&#xff1a; 前提须知&#xff1a; 前序&#xff1a; 中序&#xff1a; 后序&#xff1a; 链式二叉树的构建&#xff1a; 定义结构体&#xff1a; 初始化&#xff1a; 构建左右子树的指针指向&#xff1a; 前序遍历的实现&#xff1a; 中序…

初识PO模式并在Selenium中简单实践

初识PO模式 PO&#xff08;PageObject&#xff09;是一种设计模式。简单来说就是把一些繁琐的定位方法、元素操作方式等封装到类中&#xff0c;通过类与类之间的调用完成特定操作。 PO被认为是自动化测试项目开发实践的最佳设计模式之一。 在学习PO模式前&#xff0c;可以先…

太快了!文生图片只需1秒,开源SDXL Turbo来啦!

11月29日&#xff0c;著名开源生成式AI平台Stability.ai在官网发布了&#xff0c;开源文生图模型SDXL Turbo。 根据使用体验&#xff0c;SDXL Turbo的生成图像效率非常快&#xff0c;可以做到实时响应&#xff08;可能小于1秒&#xff09;。 在你输入完最后一个文本后&#x…

基于模块暴露和Hilt的Android模块化方案

ModuleExpose 项目地址&#xff1a;https://github.com/JailedBird/ModuleExpose 序言 Android模块化必须要解决的问题是 如何实现模块间通信 &#xff1f;而模块之间通信往往需要获取相同的实体类和接口&#xff0c;造成部分涉及模块通信的接口和实体类被迫下沉到基础模块&…

Nginx性能调优策略

Nginx是一个高性能的Web服务器和反向代理服务器&#xff0c;常用于处理高并发的请求。以下是一些常见的Nginx性能调优策略&#xff1a; 一、调整worker_processes和worker_connections 在Nginx配置文件中&#xff0c;可以通过worker_processes和worker_connections参数来调整w…

vue2.6源码分析

vue相关文档 vue-cli官方文档 vuex官方文档 vue-router 官方文档 vue2.6源码地址 如何调试源码 package.json 添加了--sourcemap "scripts": {"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap" }新增…

InstructDiffusion-多种视觉任务统一框架

论文:《InstructDiffusion: A Generalist Modeling Interface for Vision Tasks》 github&#xff1a;https://github.com/cientgu/InstructDiffusion InstructPix2Pix&#xff1a;参考 文章目录 摘要引言算法视觉任务统一引导训练集重构统一框架 实验训练集关键点检测分割图像…

0x00000709一键修复的解决办法,0x00000709错误的原因

在使用打印机时&#xff0c;你可能会遇到一些错误代码&#xff0c;其中之一是0x00000709。这个错误代码表示无法设置默认打印机。如果你遇到这样的问题不用担心&#xff01;这篇文章将为你提供0x00000709一键修复的解决办法&#xff0c;帮助你解决0x00000709错误&#xff0c;并…

对于 ` HttpServletResponse ` , ` HttpServletRequest `我们真的学透彻了吗

对于 **HttpServletResponse , HttpServletRequest**我们真的学透彻了吗 问题引入 PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) {ExcelUtil<SysUser> util new ExcelUtil<SysUser>(SysUser.class);uti…

J-Flash工具的使用---擦除、烧录及校验

文章目录 前言一、打开J-Flash工具二、使用步骤1.创建工程&#xff0c;选择MCU&#xff0c;配置端口2.打开要烧录的文件3.连接J-Link4.擦除Flash5. 烧录固件 总结 前言 不使用IDE&#xff08;如keil、Iar&#xff09;如何来烧录固件。当我们的程序需要保密&#xff0c;不需要被…

YOLOv8改进 | 2023 | DWRSeg扩张式残差助力小目标检测 (附修改后的C2f+Bottleneck)

论文地址&#xff1a;官方论文地址 代码地址&#xff1a;该代码目前还未开源&#xff0c;我根据论文内容进行了复现内容在文章末尾。 一、本文介绍 本文内容给大家带来的DWRSeg中的DWR模块来改进YOLOv8中的C2f和Bottleneck模块&#xff0c;主要针对的是小目标检测&#xff0c…

Vite 了解

1、vite 与 create-vite 的区别 2、vite 解决的部分问题 3、vite配置文件的细节 3.1、vite语法提示配置 3.2、环境的处理 3.3、环境变量 上图补充 使用 3.4、vite 识别&#xff0c;vue文件的原理 简单概括就是&#xff0c;我们在运行 npm润dev 的时候&#xff0c;vite 会搭起…

fastReID论文总结

fastReID论文总结 fastReIDReID所面临的挑战提出的背景概念&#xff1a;所谓ReID就是从视频中找出感兴趣的物体&#xff08;人脸、人体、车辆等&#xff09;应用场景&#xff1a;存在的问题&#xff1a;当前的很多ReID任务可复用性差&#xff0c;无法快速落地使用解决方式&…