详解运算符重载——探索运算符重载的应用

news2024/12/23 4:11:33

        前言:运算符重载是面向对象的一个重要的知识点。我们都知道内置类型可以进行一般的运算符的运算。但是如果是一个自定义类型, 这些运算符就无法使用了。那么为了解决这个问题, 我们的祖师爷就在c++中添加了运算符重载的概念。 本篇主要通过实例——日期类的实现来讲述了运算符重载的知识点。  

运算符重载的应用

        首先, 先了解以下运算符重载能做些什么,先看下面这张图

        在这张图中, 红框框是我定义的一个自定义类型。 这个自定义类型的成员包括了string类型的_name。 int类型的_age.它代表的就是一个人的类型。 每一个类的实例化对象都有他们的姓名, 也有他们的年龄。 那么很显然, 它的实例化对象也可以进行年龄的加法,也就是增长年龄。 

        所以, 绿色箭头指向的位置, 如果我的本意是想要让实例化对象p1的_age加2, 也就是p1这个实例化对象的年龄增加了2。但是很显然, 这个运算对于+这个运算符来说, 是做不到的, 因为对于运算符来说, 他们默认只能处理内置类型, 而不能处理自定义类型。

        这里如果想要处理自定义类型, 就需要使用我们的运算符重载, 运算符重载的目的就是为了让一个运算符可以处理自定义类型的运算。  

        如下是我重载的peo类的一个加法运算符。

//这里我将整个类搬过来方便观察。
struct peo 
{
	string _name;
	int _age;

    
    //默认构造, 这里是默认构造, 因为每一个参数都有缺省, 那么这个就是一个默认构造函数
	peo(string str = string(), int age = 0) 
		:_age(age)
	{
		_name = str;
	}

    //定义的peo类的加法运算
    peo operator+(const int x)
    {
	    peo tmp = *this;
	    tmp._age += x;
	    return tmp;
    }


};

        拥有了这个运算之后我们原先的代码就可以跑了:

现在是还没有执行加法运算的时候:

        下图是运算之后的p1:

        可以看到, p1的年龄从原本的18变成了20.

运算符重载的定义

        知道了运算符重载的作用,那么我们如何来重载一个运算符呢。 首先, 重载运算符的函数名统一都要使用"operator", 然后在operator后面加要重载的运算符。

        比如, 我如果想要重载一个加法运算符, 那么函数名就是这样: "operator+"

        然后就是参数的问题, c++规定, 被重载的运算符是按照操作数的前后进行传参的。 也就是说, 如果我重载了一个运算符+, 那么当我使用这个操作符的时候, “+”前面的操作符就是要传的第一个形参。 “+"后面就是要传的第二个形参。 我们利用上面的代码进行举例:

        这里需要着重注意的是第二个参数, 也就是那个const int x。 如果这里的参数, 我不使用传值接收, 而是使用传引用接收,也就是int& x, 那么就是不可以的。这里的const不能丢。 因为这里我们通常传送整形传送的都是常量,也就是右值,不能取地址的值。而如果形参引用想要接收右值有两个方法:一个是使用const 将左值引用变为常性, 那么就可以接收右值了。 一个是使用右值引用,右值引用时c++11的语法, 这个内容现在先知道有就行, 后续文章会学习到。 

        所以, 如果使用左值引用接收, 也就是我们平常使用的引用&来接收, 就需要使用const, 其实这里也时我们对于const 理解的进一步加深。 就是我们平时在使用左值引用参数接收左值时, 一定要分析好, 这个左值引用是否需要接收右值, 如果不接受右值, 那么可以不加const, 但是如果接收右值的话, 一定要加const。 而其中比较烦人的就是函数的返回值, 要知道, 函数的返回值如果是一个传值返回, 那么它在返回的时候会创建一个将亡值。 如果我们要对这个函数的返回值进行运算,或者其他需要调用函数的操作。 一定要注意,调用的这个函数是一个左值引用接收的话, 这个左值引用参数一定要加const。

        好, 这里这个板块的内容大致就这些, 我们再重载几个运算符,这个板块的内容就这么过了。


struct peo 
{
	string _name;
	int _age;

	peo(string str = string(), int age = 0) 
		:_age(age)
	{
		_name = str;
	}

    //加法运算符重载
	peo operator+(const int x)
	{
		peo tmp = *this;
		tmp._age += x;
		return tmp;
	}
    
    //比较运算符重载
	bool operator>(const peo& d) 
	{
		return _age > d._age;
	}

    //比较运算符重载
	bool operator==(const peo& d) 
	{
		return _age == d._age;
	}

    //赋值运算符重载
	peo& operator=(const peo& d) 
	{
		_name = d._name;
		_age = d._age;
	}


};

赋值运算符重载

        其他的运算符都很好说, 本篇真正要着重讲解的运算符其实只有三个, 也可以说是两个, 因为有两个是差不多的。 这三个运算符就是:赋值运算符, 流提取运算符, 流插入运算符。这里流提取和流插入的性质是类似的。

       这个板块先来讲述赋值运算符。

        我的前面的文章——解析默认构造函数的那篇文章中主要分析了三个默认函数——默认构造函数,默认拷贝构造,默认析构函数。 

        这三个默认函数的性质我们再来复习以下:默认构造函数是对内置类型不做处理, 对于自定义类型会去调用它的默认构造函数。 

        默认拷贝构造是对内置类型就是进行浅拷贝, 对于自定义类型会去调用它的拷贝构造。 

        默认析构函数就是对内置类型不做处理, 对于自定义类型会去调用它的析构函数。

        但是, 其实除了上面的三个 ”我们不写,系统自动生成“ 的函数, 还有一个函数如果我们不写,系统也会自动生成。 它就是——默认赋值运算符重载。现在, 我们不写, 让编译器自己重载一个赋值运算符帮我们计算一下我们定义的peo类。

注意, 我们没有进行重载赋值符号, 我们现在通过调试观察编译器是如何帮我们进行处理的:

现在还没有调用赋值符号, 此时p1的_age成员的值是18。

        f10下一步, 执行赋值符号, 这里的p1的_age成员的值已经变了, 说明我们虽然没有自己实现赋值运算符的重载, 但是编译器自己帮我们生成了。 

        编译器默认生成的赋值运算符重载的性质:编译器默认生成的赋值重载对于内置类型进行浅拷贝, 对于自定义类型会去调用它的赋值重载。

        其实, 对于默认赋值运算符重载我们可以这么理解:


class A 
{
public:
	int a;
	int b;
	//......其他成员变量

	A(const A& ka) 
	{
		//a = ka.a;
		//b = ka.b;
		//....其他成员变量使用赋值运算符进行赋值
	}

};

        就是类似于上图, 其实编译器默认生成的赋值运算符重载我们可以理解为:对象的每一个成员变量都是用赋值符号进行直接赋值操作。 那么对于内置类型就是普通的浅拷贝。 对于自定义类型就会去调用这个自定义类型的赋值运算符重载。 

        对于赋值重载来说, 其实最重要的就是默认赋值重载。而赋值重载的重载方式与其他的重载符号没有什么不同。

流提取和流插入

const问题

        我们平时使用的>>就是库里面重载的一个流提取运算符。对于istream,由于知识储备不足, 博主不能给出详细的解释。 我们在这个板块只讨论很浅的知识点。我们看下面的一段代码:

#include<iostream>
using namespace std;
#include<assert.h>

struct student 
{
	char _name[20];
	int _age;
	char _gender[10];
	int _num;
	
};

const istream& operator>>(const istream& in, student& stu) 
{
	cout << "请输入姓名, 年龄, 性别, 和学号:>";
	in >> stu._name >> stu._age >> stu._gender >> stu._num;
	return in;
}

        这一串代码中的流提取是有问题的。 为什么? 

        这里我们可以这么理解: const 修饰istream类型的对象 in,对象in具有常性。然后在istream类里面的流指针成员变量(这里可以想象成有一个流指针成员变量)也具有了常性。 那么, 如果类被赋予了常性, 成员指针变成什么?其实是变成指针常量, 也就是不能修改指向。 所以, 如果istream类型的in具有常性, 那么它里面的指针成员就会都变成指针常量。 流指针无法在终端一个字符一个字符的改变读取数据。所以图中的流提取是不对的。

        流插入也是同样的道理, 如果给ostream的实例化对象赋予常性, 那么里面的流指针就无法改变指向, 那么就无法依次在终端输入数据。 所以, 对于流插入和流提取的运算符, 最重要的一点就是不能给他们加上const。 否则这里就无法正常编译。 正确的重载如下:

#include<iostream>
using namespace std;
#include<assert.h>

struct student 
{
	char _name[20];
	int _age;
	char _gender[10];
	int _num;
	
};

istream& operator>>(istream& in, student& stu) 
{
	cout << "请输入姓名, 年龄, 性别, 和学号:>";
	in >> stu._name >> stu._age >> stu._gender >> stu._num;
	return in;
}

ostream& operator<<(ostream& out, const student& stu) 
{
	out << stu._name << " " << stu._age << " " << stu._gender << " " << stu._num << endl;;
	return out;
}

 定义位置的问题

        然后, 对于流插入和流提取还有一个比较重要的问题就是它的定义的位置。 我们知道, 对于一个类来说, 它里面的不管是非静态成员函数还是运算符重载的第一个参数都默认是this指针。但是, 要知道, 我们在进行流插入或者流提取操作时, 谁在前, 谁在后?(就像 cin >> a)

        很明显, 流插入或者流提取符号在前, 然后标识符在后。 所以, 我们在重载流插入和流提取操作符的时候就要让第一个参数是istream或者ostream。 但是, 如果我们将这两种重载运算符放在类域中。那么第一个参数就会变成this指针, 这个就无法满足流插入流提取运算符重载的规则了。 就不对了。 所以, 我们应该将流插入运算符或者流提取运算符的定义放在类域外。 

        就像上面的一串代码, 这里搬一下放在下面:

        就像如图, 这里我定义的流提取和流插入就是在类域外。

        

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Emacs之增加/取消输入括号自动匹配(一百三十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

安卓apk文件签名

一、环境准备 链接: https://pan.baidu.com/s/1D3WxIL5M5ewyFNTqJzARPw 提取码: pd6w 上篇博文编译的apk文件 1、docker build -t android-build:v1.0.1 . 直接制作镜像 2、docker run -it android-build:v1.0.1 /bin/bash 运行进入容器 指定sdk的路径&#xff0c;然后直接…

AI - 提示词意外收获 (5)

提示词&#xff1a; A soft pink rose with opalescent leaves, located in a surreal desert under the light of a binary star system, The dual shadows and contrasting lights create a dreamlike quality, emphasizing the roses unique beauty,翻译: 一种柔软的粉红…

RESA 车道线检测模型-debug分析

车道线检测模型 RESA 该模型只有一个关键点就是resa模块&#xff0c;把这个想清楚就没什么了&#xff0c;下面看代码 class RESA(nn.Module):def __init__(self, cfg):super(RESA, self).__init__()# self.iter cfg.resa.iter# chan cfg.resa.input_channel# fea_stride c…

绝地求生:PUBG七周年:杜卡迪联动即将到来!

4.13号PUBG官博放出来一个图片让大家猜测是什么东西。 结合之前绝地求生的官方的公告&#xff0c;该载具皮肤毫无疑问就是著名摩托车品牌&#xff1a;杜卡迪。 这篇文章就来简单分析一下本次即将到来的摩托车联动的具体细节。 品牌介绍 杜卡迪&#xff08;Ducati Motor &…

【测试开发学习历程】python常用的模块(中)

目录 5 time模块 5.1、Python中的四种格式的时间&#xff1a; 5.2、time模块中的常用函数 6 I/O流操作 6.1 创建文件 6.2 读取一个文件存入到另外一个文件 6.3 with open as 结构 6.4 open和with open as的区别 7 Excel的操作模块-openpyxl 7.1、新建Excel文件进行读…

PTA 编程题(C语言)-- 判断素数

题目标题&#xff1a; 判断素数 题目作者 陈越 浙江大学 本题的目标很简单&#xff0c;就是判断一个给定的正整数是否素数。 输入格式&#xff1a; 输入在第一行给出一个正整数N&#xff08;≤ 10&#xff09;&#xff0c;随后N行&#xff0c;每行给出一个小于…

渗透测试实战——第一站

仅供交流学习使用&#xff0c;请勿用于非法用途 前言&#xff1a;刚学了sql注入&#xff0c;只听理论总感觉没啥用&#xff0c;今天花了一半个多小时&#xff0c;去尝试寻找有漏洞的网站&#xff0c;最终找到了一个&#xff1b;实践是检验真理的唯一标准。 我是通过黑客常用语法…

网络基础先导

前言&#xff1a;最好在牢固前面几大件&#xff08;编程语言、数据结构、操作系统&#xff09;&#xff0c;并且您有一个服务器的基础上&#xff08;我使用的是腾讯云中配置最低的服务器&#xff09;再来学习本系列的网络知识。 1.网络发展简要 下面就是简单提及一些概念而已&…

Shortened LLaMA:针对大语言模型的简单深度剪枝法

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 论文标题 & 发表会议&#xff1a;Shortened LLaMA: A Simple Depth Pruning for Large Language Models&#xff08;ICLR 2024 Workshop&#xff09; 论文地址&#xff1a;https://arxiv.org/abs/…

绝地求生:PWS韩国联赛结束:KDF夺冠,DNW三年来首次错失世界赛

4.14号PWS韩国联赛结束了为期3天的决赛&#xff0c;KDF战队以73击杀117分获PWS第一阶段冠军&#xff0c;队内Heaven获MVP&#xff0c;DK_seoul伤害王。 常规赛靠前的DNW和Gen.G决赛均发挥失常都没有进入前八&#xff0c;其中上届世界冠军DNW在双S核心出走后时隔三年首次错失世界…

OpenHarmony轻量系统开发【4】编写第一个程序、启动流程分析

摘要&#xff1a;本文简单介绍如何编写第一个hello world程序&#xff0c;以及程序是被执行的 适合群体&#xff1a;适用于Hi3861开发板&#xff0c;启动流程分析 4.1编写第一个程序 编写一个hello world程序比较简单&#xff0c;可以参考官网&#xff1a; https://gitee.c…

【位运算 贪心】2835. 使子序列的和等于目标的最少操作次数

算法可以发掘本质&#xff0c;如&#xff1a; 一&#xff0c;若干师傅和徒弟互有好感&#xff0c;有好感的师徒可以结对学习。师傅和徒弟都只能参加一个对子。如何让对子最多。 二&#xff0c;有无限多1X2和2X1的骨牌&#xff0c;某个棋盘若干格子坏了&#xff0c;如何在没有坏…

粤嵌—2024/3/21—Pow(x,n)

代码实现&#xff1a; 方法一&#xff1a;常规解法——超时 double myPow(double x, int n) {if (n 0) {return 1.0;}if (x 1.0) {return x;}double num x;if (n > 0) {for (int i 1; i < n; i) {num num * x;}} else {n -n;for (int i 1; i < n; i) {num num…

Java编译期注解处理器AbstractProcessor使用

我们接触的注解主要分为以下两类 运行时注解&#xff1a;通过反射在运行时动态处理注解的逻辑编译时注解&#xff1a;通过注解处理器在编译期动态处理相关逻辑 编译期注解我们常用的有Lombok&#xff0c;在class文件中自动生成get和set方法 解编译期处理流程最关键的一个类就…

springMVC理解

springMVC是一种思想&#xff0c;将软件划分为&#xff0c;模型Model&#xff0c;视图View&#xff0c;控制器Controller。 MVC的工作原理&#xff1a;用户通过前端视图页面&#xff0c;发送请求到服务器&#xff0c;在服务器中请求被Controller接收&#xff0c;Controller调用…

JVM之JVM栈的详细解析

Java 栈 Java 虚拟机栈&#xff1a;Java Virtual Machine Stacks&#xff0c;每个线程运行时所需要的内存 每个方法被执行时&#xff0c;都会在虚拟机栈中创建一个栈帧 stack frame&#xff08;一个方法一个栈帧&#xff09; Java 虚拟机规范允许 Java 栈的大小是动态的或者是…

数据可视化基础与应用-04-seaborn库人口普查分析--如何做人口年龄层结构金字塔

总结 本系列是数据可视化基础与应用的第04篇seaborn&#xff0c;是seaborn从入门到精通系列第3篇。本系列主要介绍基于seaborn实现数据可视化。 参考 参考:我分享了一个项目给你《seaborn篇人口普查分析–如何做人口年龄层结构金字塔》&#xff0c;快来看看吧 数据集地址 h…

系统架构最佳实践 -- 供应链系统架构

供应链系统是现代企业管理中不可或缺的一部分&#xff0c;它涉及到从原材料采购到产品销售的整个生产流程。一个高效的供应链系统可以帮助企业实现成本控制、库存优化和客户满意度提升等目标。在本文中&#xff0c;我们将讨论供应链系统的设计与实践。 一、供应链系统设计 业务…

112 arcpy 发布 mxd地图文件 到 arcgis服务器 为 地图服务

前言 此文档主要是记录一下 最近的一次机遇 arcpy 来发布 地图文件到 arcgis服务器 上面 arcpy 主要是来自于 ArcGIS_Desktop_105_154030.zip 安装之后会在 python 的安装目录 安装另外的一份带 arcgis 的 python 环境, 然后 本文相关类库 也是基于 这个 arcpy 的 python 环境…