C++之类和对象--赋值运算符重载和const成员函数

目录

1.赋值运算符重载

1.1运算符重载

 1.2赋值运算符重载

1.3其它特性 

2.const成员函数

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


hello,欢迎大家来到小恶魔频道,今天讲解的是C++里面的赋值运算符重载以及const成员函数

1.赋值运算符重载

1.1运算符重载

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

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 22);Date d2(2024, 1, 1);return 0;
}

像以上代码

在这个代码中,我们如何比较d1和d2的是否相同呢?

常规方法:写一个函数去比较

bool Compare(const Date& dt1,const Date& dt2)
{return dt1._year == dt2._year&& dt1._month == dt2._month&& dt1._day == dt2._day;
}

 运行完后发现为0,也就是不相等

那么如果接下来我们想要直接比较d1==d2

这时候就会运用到运算符重载

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

 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

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

所以这里我们可以改写为:

bool operator==(const Date& dt1,const Date& dt2)
{return dt1._year == dt2._year&& dt1._month == dt2._month&& dt1._day == dt2._day;
}
int main()
{Date d1(2024, 4, 22);Date d2(2024, 1, 1);cout << (d1 == d2) << endl;return 0;
}

注意:这里的d1和d2必须用括号括起来,不然会无法运行

这样也是直接成功了,我们可以看一下反义编码去验证一下

 

这里发现调用了operator==,直接进行了函数比较 

但是问题也就来了

这里我们的函数比较是建立在全局变量上的,也就是说我们的函数成员需要变成共有才能够使用函数,既然是共有的成员变量,怎么保证其分装性呢?

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}bool operator==(const Date& dt2){return _year == dt2._year&& _month == dt2._month&& _day == dt2._day;}
private:int _year = -1;int _month = -1;int _day = -1;
};int main()
{Date d1(2024, 4, 22);Date d2(2024, 1, 1);cout << d1.operator==(d2) << endl;cout << (d1 == d2) << endl;return 0;
}

在这里我们将函数分装到类的里面,依旧是利用的运算符重载==

只不过这里我们值传递了一个参数,另一个参数我们利用这里隐藏的this指针去实现代码的构建,从而实现==

细说剖析:

  1. 首先呢,值传递一个参数也就是const Date& dt2,它是右边的比较数的参数,而我们的左边比较数利用的是这里面隐藏的this指针,也及时this指针指向的对象。
  2. 这里呢,我们加入了关键字:const,同时也加入了引用传参。加入这两个是为了避免传递值的修改以及避免空间的开辟浪费和传递的效率。
  3. 最后就是bool值,我们这里的函数返回类型利用的是bool值,如果他们的年月日相同就会返回真(true)也就是1不同就会返回假(false)也就是0。

我们是这调用这个函数 看一看

我们再通过反义编码看一看

我们发现这里都调用了operator==函数 

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

注意:

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

 1.2赋值运算符重载

在这里我们知道拷贝赋值有两种,一种是 拷贝构造一种是拷贝赋值

Date d1(2024,4,22);
Date d2(d1);

这样直接进行拷贝构造

那如果运用运算符重载呢?

d2 = d1;

这两者有什么区别呢?

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

我们在初始化哪里加上一个     

   cout << "Date" << endl;

 运行发现这里只打印了一次Date

也就变向说明了两者的区别

既然运算符重载是将对象的值赋值给另一个对象,我们想一下

可不可以进行连续赋值呢?

比如:a=b=10这种?

在C语言中我们通常是这样的

int a,b;
a = b = 10;

但是在赋值运算符重载中,我们则是需要更新一下自己的方式:返回*this

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

在这里我们的返回类型是Date,没有使用引用,而是进行的传值返回

所以我们这里返回的不是*this,而是他的一个拷贝

可以看到返回this指针时调用了他的拷贝

所以为了加快效率我们这次加入引用

Date& operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}
int main()
{Date d1(2024, 4, 22);Date d2(1,1,1);Date d3 = d2 = d1;return 0;
}

这次就没有拷贝 

但是问题来了,如果这里我传参传的是自己呢?如果我给自己赋值会怎么样?

这里是不行的

为什么不行呢?

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

所以这里我们还需要改进以下代码

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

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

1.3其它特性 

这里我们看到报错了

然后我们把成员类型设置为公有的,发现还是报错

这是因为:赋值运算符只能重载成类的成员函数不能重载成全局函数

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

       故赋值运算符重载只能是类的成员函数

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

答案是会的

这里我们把operator=给注释掉后 结果仍旧一样

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

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

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

答案需要的 

如果我们使用的是栈的话

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc fail");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){_array[_size++] = data;}~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共享同一份内存空间,最后销毁时会导致同一份内存空间释放两次而引起程序崩溃

这里有点类似于我们之前学习的拷贝构造,如果不分开,也是占用的同一块内存,最后会崩溃报错

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

2.const成员函数

假如我们现在定义一个const对象,想访问它的Print函数,我们发现是调用不了的:

class Date
{
public:Date(int year, int month, int day){//加上这里的打印是为了调试更加清楚cout << "Date" << endl;_year = year;_month = month;_day = day;}bool operator==(const Date& dt2){return _year == dt2._year&& _month == dt2._month&& _day == dt2._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;return *this;}}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year = -1;int _month = -1;int _day = -1;
};

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

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

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

class Date
{
public:Date(int year, int month, int day){//加上这里的打印是为了调试更加清楚cout << "Date" << endl;_year = year;_month = month;_day = day;}bool operator==(const Date& dt2){return _year == dt2._year&& _month == dt2._month&& _day == dt2._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;return *this;}}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print() const{cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year = -1;int _month = -1;int _day = -1;
};

 我们在Print函数后加上const

这样就可以了

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

可以的,这里是权限的缩小 

请思考下面的几个问题:

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

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

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

这个我们主要是了解,不作为重点讲解,后边用到会更新讲解

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

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

这里是默认成员函数,我们删去这两个函数照样可以取地址 

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}//Date* operator&()//{//	return this;//}//const Date* operator&()const//{//	return this;//}
private:int _year = 1;int _month = 1;int _day = 1; 
};
void main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;
}

这里,我们没有必要深究这个东西究竟有什么用,我们只进行简单的语法了解即可

看到这里,一键三连支持一下呗。

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

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

相关文章

基于Springboot的新生宿舍管理系统

基于SpringbootVue的新生宿舍管理系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringbootMybatis工具&#xff1a;IDEA、Maven、Navicat 系统展示 用户登录 首页 公告信息管理 院系管理 班级管理 学生管理 宿舍信息管理 宿舍安排管理…

Unity系统学习笔记

文章目录 1.基础组件的认识1.0.组件继承关系图1.1.项目工程文件结构&#xff0c;各个文件夹都是做什么的&#xff1f;1.2.物体变化组件1.2.3.三维向量表示方向1.2.4.移动物体位置附录&#xff1a;使用变换组件实现物体WASD移动 1.3.游戏物体和组件的显示和禁用1.3.1.界面上的操…

python 对图片进行操作

Pillow是一个强大的图像处理库&#xff0c;它提供了许多用于打开、操作和保存图像的功能。 Image模块&#xff1a; Image模块提供了用于打开、创建、编辑和保存图像的基本功能。可以使用Image.open()函数来打开图像文件&#xff0c;或者使用Image.new()函数来创建新的图像,还可…

DSP系统的设计过程与选型

DSP的设计步骤分几个阶段&#xff0c;应用系统的设计过程如图所示。 技术指标的确定 器件的选型原则 其他因素的考虑

css再学习

id选择器的id仅能使用一次&#xff1b; 可去除也可添加加粗效果&#xff1b; font 行高为数字n时&#xff08;不添加单位&#xff09;是浏览器默认font-size的n倍&#xff1b; 行高实现文字垂直居中&#xff08;仅适应于单行文字&#xff09;&#xff1a;将line-height设置…

思科警告:全球出现大规模针对 VPN 服务的暴力破解攻击事件

近日&#xff0c;全球范围内出现了大量针对思科、CheckPoint、Fortinet、SonicWall 和 Ubiquiti 设备的 VPN 和 SSH 服务的大规模凭据暴力破解活动。 原文地址&#xff1a;https://mp.weixin.qq.com/s/UoMgC8Bp6OMJiXgeU3XbyA 暴力攻击是指使用许多用户名和密码尝试登录帐户或…

科研基础与工具(论文写作)

免责申明&#xff1a; 本文内容只是学习笔记&#xff0c;不代表个人观点&#xff0c;希望各位看官自行甄别 参考文献 科研基础与工具&#xff08;YouTube&#xff09; 学术写作句型 Academic Phrase bank 曼彻斯特大学维护的一个网站 写论文的时候&#xff0c;不不知道怎么…

【C语言】数据的存储_数据类型:浮点型存储

常见的浮点数&#xff1a; 3.1415926 1E10 浮点型包括&#xff1a;float、double、long double类型 浮点数表示的范围&#xff1a;float.h中定义 浮点数存储规则&#xff1a; 第二个n和*pFloat在内存中明明是同一个数&#xff0c;但浮点数和整数解读结果差别很大。 要理解这…

【热门话题】常用经典目标检测算法概述

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 常用经典目标检测算法概述1. 滑动窗口与特征提取2. Region-based方法R-CNN系列M…

SOTAX溶出测试系统PC触摸屏维修三部曲

SOTAX溶出测试系统作为一款广泛应用于制药行业的知名品牌&#xff0c;具有高精度、操作简便、稳定性好等特点。它适用于各种类型的药品研发和生产环节&#xff0c;为科研人员提供可靠的数据支持。瑞士SOTAX溶出仪是实验室中常用的设备&#xff0c;其触摸屏是用户交互的重要界面…

apache和IIS区别?内网本地服务器项目怎么让外网访问?

Apache和IIS是比较常用的搭建服务器的中间件&#xff0c;它们之间还是有一些区别差异的&#xff0c;下面就详细说说 Apache和IIS有哪些区别&#xff0c;以及如何利用快解析实现内网主机应用让外网访问。 首先说说apache和IIS最基本的区别。Apache运行的操作系统通常为Unix或Lin…

05_Qt资源文件添加

Qt资源文件添加 Qt 资源系统是一个跨平台的资源机制&#xff0c;用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源&#xff08;图标、文本翻译等&#xff09;&#xff0c;那么&#xff0c;将其放置在资源文件中&#xff0c;就…