【c++】类和对象(五)赋值运算符重载

Alt

🔥个人主页Quitecoder

🔥专栏c++笔记仓

Alt

朋友们大家好,本篇文章带大家认识赋值运算符重载,const成员,取地址及const取地址操作符重载等内容

目录

  • 1.赋值运算符重载
    • 1.1运算符重载
      • 1.1.1特性:
    • 1.2赋值运算符重载
    • 1.3 赋值运算符的其他性质
    • 1.4前置++和后置++重载
  • 2.const成员函数
  • 3.取地址及const取地址操作符重载

1.赋值运算符重载

1.1运算符重载

运算符重载是一种编程语言特性,它允许开发者为已有的运算符提供自定义的实现。这意味着你可以改变某些运算符在你自定义的类或数据类型上的行为。比如,你可以定义加号运算符(+)如何在你自定义的数据结构上进行运算

什么意思呢,我们来讲解:首先我们定义日期类Date,并实例化两个对象:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);return 0;
}

我们如何判断两个年份相等呢?

如果是常规方法,我们会写一个比较函数,来判断是否相同:

bool issame(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << issame(d1, d2) << endl;return 0;
}

那如果我们想直接通过用d1==d2来判断是否相同呢?这里就用到了操作符重载

运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,注意这里说的重载与我们的函数重载不是一个意思

函数名字为:关键字operator后面接需要重载的运算符符号

bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << (d1==d2) << endl;return 0;
}

在这里插入图片描述

在这里插入图片描述
我们发现,直接进行判断时,调用了比较函数

但是这里是全局的定义的operator==,这里会发现运算符重载成全局的就需要成员变量是公有的,即我的成员不能是private私有的,那么封装性如何保证?

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date & d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
bool operator==(const Date& d2)
{return _year == d2._year&& _month == d2._month&& _day == d2._day;
}

这部分是Date类中==运算符的重载。这个重载让你可以使用==来比较两个Date对象是否相等,即它们的年、月、日是否都相同

关键点讲解

  • 参数operator==函数接受一个类型为const Date&的参数d2,它是比较操作的右侧操作数。左侧操作数是调用这个函数的对象,this指针指向的对象
  • const关键字:参数使用const修饰符和引用传递来保证效率和避免不必要的拷贝,同时确保不会修改传入的对象
  • 函数体:函数体中,通过比较两个Date对象的年、月、日字段来决定这两个对象是否相等。如果所有字段都相等,则返回true;否则,返回false

我们接着调用这个函数:

int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << d1.operator==(d2) << endl;cout << (d1==d2) << endl;return 0;
}

注意
注意这里的顺序,d1在前与在后是不同的,如果我们写一个小于函数的运算符重载,顺序不同,意思刚好相反

我们有两种方式进行调用,这两种方式是相同的:
在这里插入图片描述

在上面的讲解之后,相信大家对运算符重载有了一定的了解,他就是允许自定义对象使用运算符它的返回值是根据运算符来决定的比如完成加减操作,我们就返回int类型,判断是否大于小于,就用bool类型

1.1.1特性:

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

1.2赋值运算符重载

在这里插入图片描述
我们知道,拷贝赋值有两种,拷贝构造和赋值重载,我们看拷贝构造:

Date d1(2018, 9, 26);
Date d2(d1);

那如果我们用赋值运算符重载呢?可以写成下面的形式:

d2=d1;

关键区别

  • 拷贝构造函数在对象创建时使用,用于初始化新对象。赋值运算符重载在对象已存在时使用,用于将一个对象的值赋给另一个对象
  • 目的:拷贝构造函数的目的是创建一个新的、状态相同的对象副本。赋值运算符的目的是改变一个已存在对象的状态,使其与另一个对象的状态相同
  • 拷贝构造函数通常接收一个对同类对象的常引用。赋值运算符重载通常返回对象的引用,并接收一个对同类对象的常引用作为参数

我们接下来讲解赋值运算符重载的具体实现来体现上面的特点:

能不能直接这么写呢?:

void operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

在这里插入图片描述

这个在单个赋值是可以的,那如果,我想像c语言一样同时实现多个变量的连续赋值的场景呢?

int b;
int c;
b=c=10;

那我们这个函数就无法满足要求了,我们该如何修改呢?

我们不妨探讨连续赋值的本质:

b=c=10;

这里执行步骤:

  • 10赋值给c,c=10这个表达式返回值为左操作数c
  • c再作为b=c的有操作数给b赋值,返回值为左操作数b

在这里插入图片描述

所以,我们的自定义类型也要符合这里的行为

所以,我们需要对函数进行修改:

第一次修改

Date operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}

返回左操作数,返回*this

我们这里用的是传值返回,意味着这里返回的不是*this,返回的是*this的拷贝,则需要调用拷贝构造函数:
在这里插入图片描述
在这里插入图片描述
所以我们需要再次修改:

第二次修改:

Date& operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}

我们返回引用

还有一个问题,如果自身给自身赋值呢?

d1=d1;

为什么自赋值不行?

自赋值在大多数情况下是可以工作的,但是在特定的情况下,如果没有正确处理,它可能会引起错误或意外的行为。考虑自赋值的主要原因是为了确保当对象赋值给自身时,程序仍然能够正确、安全地运行
特别是在类中涉及到动态内存管理时,不正确处理自赋值可能会导致问题。例如,假设一个类内部分配了动态内存,如果在赋值操作中首先释放了这块内存(预备重新分配),而源对象和目标对象实际上是同一个对象,那么这个操作实际上会破坏源对象的状态,导致未定义行为

我们还需要再次修改一次:

Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}

我们这里判断条件是地址的比较,如果地址不相同说明不是同一个对象,可以赋值

1.3 赋值运算符的其他性质

赋值运算符只能重载成类的成员函数不能重载成全局函数
在这里插入图片描述

我们首先得把成员类型设置为公有的,发现还是报错,
在这里插入图片描述

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

如果我们不写赋值运算符重载,编译器是否会默认生成呢?

在这里插入图片描述
结果是会生成的

所以这里与我们拷贝构造等函数性质一致:

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

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?

答案是需要的,如遇到下面的动态内存管理

typedef int DataType;
class Stack
{
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);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

在这里插入图片描述

  1. s1对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间,然后存了4个元素1 2 3 4
  2. s2对象调用构造函数创建,在构造函数中,默认申请了10个元素的空间,没有存储元素
  3. 由于Stack没有显式实现赋值运算符重载,编译器会以浅拷贝的方式实现一份默认的赋值运算符重载即只要发现Stack的对象之间相互赋值,就会将一个对象中内容原封不动拷贝到另一个对象中
  4. s2 = s1;当s1给s2赋值时,编译器会将s1中内容原封不动拷贝到s2中,这样会导致两个问题:
    • s2原来的空间丢失了,存在内存泄漏
    • s1和s2共享同一份内存空间,最后销毁时会导致同一份内存空间释放两次而引起程序崩溃

注意
如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

1.4前置++和后置++重载

在C++中,前置++和后置++运算符都可以被重载,以提供用户定义类型(比如类)的自增功能。它们之间的主要区别在于参数和返回值,这影响了它们的使用和效率

前置++

前置++直接对对象进行自增操作,并返回自增后的对象引用。这意味着它在自增后立即返回对象的状态,使得操作可以立即反映在对象上

Date& operator++(){_day += 1;return *this;}

后置++

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

注意:后置++是先使用后+1因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1

Date operator++(int){Date temp(*this);_day += 1;return temp;}

temp是临时对象,因此只能以值的方式返回,不能返回引用

2.const成员函数

假如我们现在定义一个const对象,想访问它的Print函数,我们发现是调用不了的:
在这里插入图片描述

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这里权限进行放大了,接着,我们来介绍const成员函数

原来是const Date*,而我的this类型是Date*,意味着需要将this指针也改为const Date*,所以才有了下面的函数

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改内容是只读的

在这里插入图片描述

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022,1,13);d1.Print();const Date d2(2022,1,13);d2.Print();
}

我们查看结果:
在这里插入图片描述

如果没有const修饰的函数呢,我Date类型的对象能否调用const成员函数呢?

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; int _month; int _day; 
};
void main()
{Date d1(2022, 1, 13);d1.Print();
}

在这里插入图片描述
可以的,这里是权限的缩小

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗? 不可以,权限放大
  2. 非const对象可以调用const成员函数吗? 可以,权限缩小
  3. const成员函数内可以调用其它的非const成员函数吗? 不可以,权限放大
  4. 非const成员函数内可以调用其它的const成员函数吗?可以,权限缩小

指针和引用才存在权限放大

3.取地址及const取地址操作符重载

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date* operator&(){return this;}const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

在这里插入图片描述
这里是默认成员函数,我们删去这两个函数照样可以取地址

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year; // 年int _month; // 月int _day; // 日
};

在这里插入图片描述
所以,我们没有必要深究这个东西究竟有什么用,我们只进行简单的语法了解即可

当然,如果你想让它普通对象定义后只能返回空值,你可以这么写:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date* operator&(){return nullptr;}
private:int _year; int _month; int _day; 
};

日常并没有这种需要

本节内容到此结束!!!感谢大家阅读!!

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

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

相关文章

刷题之动态规划

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;开始刷动态规划的题目了&#xff0c;要特别注意初始化的时候给什么值。 动态规划5个步骤 状态表示 &#xff1a;dp数组中每一个下标对应值的含义是什么->dp[i]表示什么状态转移方程&#xff1a; dp[i] 等于什么1 和 2 是…

软考101-上午题-【信息安全】-网络安全

一、网络安全 1-1、安全协议 SSL(Secure Socket Layer&#xff0c;安全套接层)是 Netscape 于 1994年开发的传输层安全协议&#xff0c;用于实现 Web 安全通信。1996 年发布的 SSL3.0 协议草案已经成为一个事实上的Web 安全标准。 端口号是43。 SSL HTTP HTTPS TLS(Transpo…

XXE漏洞知识及ctfshow例题

XXE漏洞相关知识 XXE全称为XML Enternal Entity Injection 中文叫xml外部实体注入 什么是xml 简单了解XML&#xff1a; &#xff08;xml和html的区别可以简易的理解成&#xff1a;xml是用来储存数据和传输数据的而html是用来将数据展现出来&#xff09; XML 指可扩展标记语…

vue3封装Element导航菜单

1. 导航外层布局 AsideView.vue <template><el-menu:default-active"defaultActive"class"my-menu":collapse"isCollapse":collapse-transition"false"open"handleOpen"close"handleClose"><menu…

Mysql or与in的区别

创建一个表格 内涵一千万条数据 这张表中&#xff0c;只有id有建立索引&#xff0c;且其余都没有 测试1&#xff1a;使用or的情况下&#xff0c;根据主键进行查询 可以看到根据主键id进行or查询 花费了30-114毫秒&#xff0c;后面30多毫秒可能是因为Mysql的Buffer Pool缓冲池的…

大模型重塑电商,淘宝、百度、京东讲出新故事

配图来自Canva可画 随着AI技术日渐成熟&#xff0c;大模型在各个领域的应用也越来越深入&#xff0c;国内互联网行业也随之进入了大模型竞赛的后半场&#xff0c;开始从“百模大战”转向了实际应用。大模型从通用到细分垂直领域的跨越&#xff0c;也让更多行业迎来了新的商机。…

【Vue3源码学习】— CH2.6 effect.ts:详解

effect.ts&#xff1a;详解 1. 理解activeEffect1.1 定义1.2 通过一个例子来说明这个过程a. 副作用函数的初始化b. 执行副作用函数前c. 访问state.countd. get拦截器中的track调用e. 修改state.count时的set拦截器f. trigger函数中的依赖重新执行 1.3 实战应用1.4 activeEffect…

LLM推理入门指南②:深入解析KV缓存

在本系列文章《LLM推理入门指南①&#xff1a;文本生成的初始化与解码阶段》中&#xff0c;作者对Transformer解码器的文本生成算法进行了高层次概述&#xff0c;着重介绍了两个阶段&#xff1a;单步初始化阶段&#xff0c;即提示的处理阶段&#xff0c;和逐个生成补全词元的多…

浪潮信息AIStation与潞晨科技Colossal-AI 完成兼容性认证!

为进一步提升大模型开发效率&#xff0c;近年来&#xff0c;浪潮信息持续加强行业合作&#xff0c;携手业内头部&#xff0c;全面进攻大模型领域。日前&#xff0c;浪潮信息AIStation智能业务创新生产平台与潞晨科技Colossal-AI大模型开发工具完成兼容性互认证。后续&#xff0…

FFmpeg将绿幕视频处理成透明视频播放

怎么在网页端插入透明视频呢&#xff0c;之前在做Web3D项目时&#xff0c;使用threejs可以使绿幕视频透明显示在三维场景中&#xff0c;但是在网页端怎么让绿幕视频透明显示呢&#xff1f; 如图上图&#xff0c;视频背景遮挡住后面网页内容 想要如下图效果 之前有使用过ffmpeg…

机器人码垛机:智能仓储系统的重要组成部分

随着科技的飞速进步&#xff0c;机器人技术已经渗透到了许多行业领域&#xff0c;其中&#xff0c;仓储业尤为显著。机器人码垛机作为智能仓储系统的重要组成部分&#xff0c;不仅提高了码垛效率&#xff0c;还降低了人工成本和安全风险。然而&#xff0c;在其广泛应用的同时&a…

HTML5 和 CSS3 提高

一、HTML5 的新特性 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性。 声明…

NineAi3.5 –支持GPT绘图,语音播报,联网访问,上下文关联,语音模式

NineAi3.5 –支持GPT绘图&#xff0c;语音播报&#xff0c;联网访问&#xff0c;上下文关联&#xff0c;语音模式 基于ChatGPT开发的一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c; 还能根据聊天的上下文进行互动&…

Java NIO详解

一、概念 NIO, 即new io&#xff0c;也叫非阻塞io 二、NIO三个核心组件&#xff1a; Buffer数据缓冲区Channel通道Selector选择器 1、Buffer缓冲区 缓冲区本质上是一个可以存放数据的内存块&#xff08;类似数组&#xff09;&#xff0c;可以在这里进行数据写入和读取。此…

【Java】LinkedList模拟实现

目录 整体框架IMyLinkedList接口IndexNotLegalException异常类MyLinkedList类成员变量(节点信息)addFirst(头插)addLast(尾插)在指定位置插入数据判断是否存在移除第一个相等的节点移除所有相等的节点链表的长度打印链表释放回收链表 整体框架 IMyLinkedList接口 这个接口用来…

京东云服务器价格_云主机价格查询系统_2024年京东云优惠活动

2024年京东云服务器优惠价格表&#xff0c;轻量云主机优惠价格5.8元1个月、轻量云主机2C2G3M价格50元一年、196元三年&#xff0c;2C4G5M轻量云主机165元一年&#xff0c;4核8G5M云主机880元一年&#xff0c;游戏联机服务器4C16G配置26元1个月、4C32G价格65元1个月、8核32G费用…

OpenHarmony如何模拟搭建本地http静态服务

简介 本文是在基于OpenHarmony 4.0的基础上&#xff0c;介绍了一种编写一个前端http静态服务的思路. 方案设计 在OpenHarmony上&#xff0c;如果想要访问本地网页。有两种方案 u 方案一&#xff1a;使用file协议&#xff0c;将html放至entry/src/main/resource/rawfile下&#…

构建ELK+Filebeat+kafka+zookeeper大数据日志分析平台

主机IP 角色 所属服务层 部署服务 192.168.11.11 日志生产 采集层 filebeat 192.168.11.12 日志缓存 数据处理层、缓存层 Zookeeperkafkalogstash 192.168.11.13 192.168.11.14 日志展示 持久、检索、展示层 Logstashelasticsearchkibana 数据流向 filebeat--…

华清远见STM32U5开发板助力2024嵌入式大赛ST赛道智能可穿戴设备及IOT选题项目开发

第七届&#xff08;2024&#xff09;全国大学生嵌入式芯片与系统设计竞赛&#xff08;以下简称“大赛”&#xff09;已经拉开帷幕&#xff0c;大赛的报名热潮正席卷而来&#xff0c;高校电子电气类相关专业&#xff08;电子、信息、计算机、自动化、电气、仪科等&#xff09;全…

新能源充电桩站场AI视频智能分析烟火检测方案及技术特点分析

新能源汽车充电起火的原因多种多样&#xff0c;涉及技术、设备、操作等多个方面。从技术层面来看&#xff0c;新能源汽车的电池管理系统可能存在缺陷&#xff0c;导致电池在充电过程中出现过热、短路等问题&#xff0c;从而引发火灾。在设备方面&#xff0c;充电桩的设计和生产…