c++阶梯之类与对象(中)< 续集 >

前文:

c++阶梯之类与对象(上)-CSDN博客

c++阶梯之类与对象(中)-CSDN博客


前言:

在上文中,我们学习了类的六个默认成员函数之构造,析构与拷贝构造函数,接下来我们来看看剩下的默认成员函数。 


目录

前文:

前言:

5. 赋值运算符重载

5.1 运算符重载

普通函数版 

运算符重载版 

 5. 2 赋值运算符重载

1. 赋值运算符重载格式

 2. 实例

注意: 

 5.3 特殊的运算符重载 前置++与后置++

 6. Date类的实现(多功能)

Date.h 

Date.cpp 

main.cpp 

补充知识:

7.const成员 

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


5. 赋值运算符重载

5.1 运算符重载

我们知道函数重载,那么运算符重载又是什么?

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意
<  > 不能通过连接其他符号来创建新的操作符:比如operator@
<  > 重载操作符必须有一个类类型参数
<  > 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
 <  > 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
 <  >   .*  ::  sizeof  ?:  . 注意以上5个运算符不能重载。这个经常在笔试选择题中出

我们可以这样认为,运算符重载就是给运算符赋予一个新的含义,<专为自定义类型而生> 不过默认并不会改变运算符的基本逻辑。

我们来看看他在实际中的应用,我们实现两个Date类对象比较,1.是否相等 2. 是否小于

普通函数版 

我们可以看到,在这个版本里,代码编写者取名十分随意,那么当别人看到这段代码,内心的活动估计十分激昂。虽然在功能上很完美,但其代码却会让人抓耳挠腮。 

class Date
{
public:Date(int year = 2024, int month = 2, int day = 6){_year = year;_month = month;_day = day;}//Data类拷贝构造,赋值重载,析构都不需要自己实现bool compare1(const Date& d) //DateEqual {return _year == d._year&& _month == d._month&& _day == d._day;}bool compare2(const Date& d)//DateLess{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;}private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 2, 7);Date d2(1997, 1, 1);cout << d1.compare1(d2) << endl;cout << d1.compare2(d2) << endl;return 0;
}

运算符重载版 

这一版本的代码可读性大大增强,还免去了起名的麻烦,同时在调用函数的时候,自定义类型也可以像内置类型一样简单快捷的使用运算符。

class Date
{
public:Date(int year = 2024, int month = 2, int day = 6){_year = year;_month = month;_day = day;}//Data类拷贝构造,赋值重载,析构都不需要自己实现bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}bool operator<(const Date& d){if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;}private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 2, 7);Date d2(1997, 1, 1);return 0;
}int main()
{Date d1(2024, 2, 7);Date d2(1997, 1, 1);/*cout << d1.operator<(d2) << endl;cout << d1.operator==(d2) << endl;*/cout << (d1 < d2) << endl;//在编译器看来,等同于cout << d1.operator<(d2) << endl;cout << (d1 == d2) << endl;//在编译器看来,等同于cout << d1.operator==(d2) << endl;return 0;
}

 注意:因为 << 的运算符很高,因此在调用时需要加括号。

运算符重载并不会改变运算符的底层逻辑,因此对于双操作数运算符来说,左操作数就是函数的第一个参数,右操作数是第二个参数。

那么运算符重载能定义成全局的吗,是的可以,但是这样我们就不能访问类的私有成员。但将类成员公有化,如何保证封装性呢?

我们可以使用友元函数,或者定义成成员函数。 

 5. 2 赋值运算符重载

1. 赋值运算符重载格式

参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义 

 2. 实例

我们发现,所谓的赋值重载与之前学到的拷贝构造几乎是一个模子里刻出来的,既然他们一样,那我们还有必要学他吗?

这里有一个误区,他们的实现确实一样,但有一个不同点。

<  > 拷贝构造是对并不存在的对象进行拷贝构造,既有拷贝又有构造;

<  > 赋值重载是对已经存在的对象进行赋值(拷贝)。 

class Date
{
public:Date(int year = 2024, int month = 2, int day = 6){_year = year;_month = month;_day = day;}Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}bool operator<(const Date& d){if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;}bool operator!=(const Date& d){return !(*this == d);}Date& operator=(const Date& d){if (*this != d){_year = d._year;_month = d._month;_day = d._day;}return *this;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 2, 7);Date d2(1997, 1, 1);int a = 10;int b = 20;Date d3 = d1;//拷贝构造,即Date d3(d1);Date d4;d4 = d1;//赋值重载return 0;
}

我们在汇编视角可以看的更加清晰。 

  

注意: 

赋值重载只能定义成类的成员函数,不能定义成全局函数。

<  > 编译器不支持定义为全局函数,如果定义成全局会报错。

<  > 定义成全局函数,那么类中没有实现,作为默认成员函数之一,编译器会自动生成一个默认赋值重载,那么当调用的时候就会产生冲突。

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

我们不写,编译器会自动生成,那么我们还需要写吗?

视情况而定,值拷贝对于Date类来说很适用,那么对于我们之前写的Stack类呢?他会造成不小的麻烦 。关于这一点,在c++阶梯之类与对象(中)-CSDN博客中的拷贝构造函数一节有详细讲解,感兴趣的宝子可以去看看。

 5.3 特殊的运算符重载 前置++与后置++

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

Date operator++(int)//后置++,后置++使用后加1,因此返回的结果是自己本身,返回之后自身+1
{Date tmp = *this;_day+=1;return tmp;
}Date& operator++()//前置++,前置++使用前++,前置++返回的结果是自身+1
{_day++;return *this;
}

 6. Date类的实现(多功能)

在Date类实现常用运算符的重载,日期计算器,两日期的间隔等功能。

Date.h 

#include<iostream>
using namespace std;
class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month);// 全缺省的构造函数Date(int year , int month , int day );// 拷贝构造函数Date(const Date& d);// 赋值运算符重载Date& operator=(const Date& d);// 析构函数~Date();// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day);// 日期-天数Date operator-(int day);// 日期-=天数Date& operator-=(int day);// 前置++Date& operator++();// 后置++Date operator++(int);// 后置--Date operator--(int);// 前置--Date& operator--();// >运算符重载bool operator>(const Date& d);// ==运算符重载bool operator==(const Date& d);// >=运算符重载bool operator >= (const Date& d);// <运算符重载bool operator < (const Date& d);// <=运算符重载bool operator <= (const Date& d);// !=运算符重载bool operator != (const Date& d);// 日期-日期 返回天数int operator-(const Date& d);//打印void Print();private:int _year;int _month;int _day;};

Date.cpp 

#include"Date.h"
//获取每个月的天数
int Date::GetMonthDay(int year, int month)
{int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if ((month == 2) && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){return 29;}return monthDays[month];
}
//全缺省构造函数
Date::Date(int year = 1997, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}//拷贝构造
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}//赋值运算符重载
Date& Date::operator=(const Date& d)
{if (*this != d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}//析构
Date::~Date()
{;
}//日期+=天数
Date& Date::operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}//日期+天数
Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;
}//日期-=天数
Date& Date::operator-=(int day)
{_day -= day;while (_day <= 0){_day += GetMonthDay(_year, _month);_month--;if (_month == 0){_year--;_month = 12;}}return *this;
}//日期-天数
Date Date::operator-(int day)
{Date tmp = *this;//拷贝构造tmp -= day;return tmp;}//后置++
Date Date::operator++(int)//后置++,后置++使用后加1,因此返回的结果是自己本身,返回之后自身+1
{Date tmp = *this;_day++;return tmp;
}//前置++
Date& Date::operator++()//前置++,前置++使用前++,前置++返回的结果是自身+1
{_day++;return *this;
}//后置--
Date Date::operator--(int)
{Date tmp = *this;_day --;return tmp;
}//前置--
Date& Date::operator--()
{_day--;return *this;
}//重载==
bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}//重载!=
bool Date::operator!=(const Date& d)
{return !(*this == d);
}//重载<
bool Date::operator<(const Date& d)
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;
}//重载<=
bool Date::operator<=(const Date& d)
{return (*this < d) || (*this == d);
}//重载>
bool Date::operator>(const Date& d)
{return !(*this <= d);
}//重载>=
bool Date::operator>=(const Date& d)
{return !(*this < d);
}//日期-日期=天数
int Date::operator-(const Date& d)
{Date max = *this, min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){min += 1;n++;}return n * flag;	
}//打印
void Date::Print()
{cout << _year << "/" << _month << "/" << _day << endl;
}

main.cpp 

#include"Date.h"int main()
{Date d1(2024, 2, 7);Date d2(1997, 5, 3);cout << (d1 - d2) << endl;d1 -= 10;d1.Print();d2 += 20;d2.Print();cout << (d1 < d2) << endl;Date d3(1, 1, 1);d1.Print();d3 = d1++;d3.Print();++d3;d3.Print();return 0;
}

Date的实现是对近期知识的总结,糅合了许多语法,并且难度也不大,值得一写。 

补充知识:

补充上述相关的简略友元与操作数顺序知识。 这里使用重载流插入操作符<< 来进行演示。

 我们根据所学的知识写了 << 的重载成员函数,但在使用的时候出现了问题。

我们之前说过, 运算符重载并不会改变运算符的底层逻辑,因此对于双操作数运算符来说,左操作数就是函数的第一个参数,右操作数是第二个参数。

因此按我们写的,应该这么使用

但很显然,这与我们预期的不符。成员函数隐含形参this,这个我们没有办法更改,因此只能将他实现为全局函数,然后更改函数参数的顺序。这里会用到友元,现在我们只需要知道友元的存在,不必深究。

虽然此时可以使用 << 输出Date类型了,但他只能输出一个,正常的 << 是可以连续流插入的。

这里的问题在于运算符的结合性,<< 是从左往右,cout<<d1 之后需要返回一个值,然后再调用 << 。我们做如下更改,

此时的流插入操作符重载和我们心目中的就一般无二了。 

7.const成员 

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

我们知道,成员函数中隐含的参数this指针是不允许显式写出来的,因此我们要将this指针const化就只能通过上面的方法。

请思考下面的几个问题:
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?

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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

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

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

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

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

相关文章

堆排序-Python实现

简述 堆排序&#xff08;Heap Sort&#xff09;是一种基于比较的排序算法&#xff0c;它利用堆这种数据结构所设计的一种排序算法。堆排序是一种选择排序&#xff0c;它的最坏&#xff0c;最好&#xff0c;平均时间复杂度均为O(nlogn)&#xff0c;它也是不稳定排序。 堆 堆排…

【数据结构】链表OJ面试题4(题库+解析)

1.前言 前五题在这http://t.csdnimg.cn/UeggB 后三题在这http://t.csdnimg.cn/gbohQ 给定一个链表&#xff0c;判断链表中是否有环。http://t.csdnimg.cn/Rcdyc 记录每天的刷题&#xff0c;继续坚持&#xff01; 2.OJ题目训练 10. 给定一个链表&#xff0c;返回链表开始…

豪掷770亿!华为员工集体“分红大狂欢”:至少14万人受益

豪掷770亿&#xff01;华为员工集体“分红大狂欢”&#xff1a;至少14万人受益 近日&#xff0c;华为宣布了其2023年度分红计划&#xff0c;总金额高达770.85亿元&#xff0c;预计至少将惠及14万员工。这一消息引发了广泛关注和热议&#xff0c;成为业界的一大亮点。作为中国领…

嵌入式学习之Linux入门篇笔记——16,Linux工具之make工具和makefile文件

配套视频学习链接&#xff1a;http://【【北京迅为】嵌入式学习之Linux入门篇】 https://www.bilibili.com/video/BV1M7411m7wT/?p4&share_sourcecopy_web&vd_sourcea0ef2c4953d33a9260910aaea45eaec8 1.什么是 make 工具&#xff1f; 编译辅助工具。解决使用命令…

【漏洞复现】EasyCVR智能边缘网关用户信息泄漏漏洞

Nx01 产品简介 EasyCVR智能边缘网关是一种基于边缘计算和人工智能技术的设备&#xff0c;旨在提供高效的视频监控和智能分析解决方案。它结合了视频监控摄像头、计算能力和网络连接&#xff0c;能够在现场进行视频数据处理和分析&#xff0c;减轻对中心服务器的依赖。 Nx02 漏…

【代理模式】

定义&#xff1a;代理模式是一种结构型设计模式&#xff0c;它允许我们创建一个代理对象&#xff0c;用于控制对另一个对象的访问。 代理对象充当了被代理对象&#xff08;目标对象&#xff09;的代表&#xff0c;与被代理对象实现相同的接口&#xff0c;从而实现对被代理对象…

Vue源码系列讲解——虚拟DOM篇【一】(Vue中的虚拟DOM)

目录 1. 前言 2. 虚拟DOM简介 2.1什么是虚拟DOM&#xff1f; 2.2为什么要有虚拟DOM&#xff1f; 3. Vue中的虚拟DOM 3.1 VNode类 3.2 VNode的类型 3.2.1 注释节点 3.2.2 文本节点 3.2.3 克隆节点 3.2.4 元素节点 3.2.5 组件节点 3.2.6 函数式组件节点 3.2.7 小结 3…

安全的接口访问策略

渗透测试 一、Token与签名 一般客户端和服务端的设计过程中&#xff0c;大部分分为有状态和无状态接口。 一般用户登录状态下&#xff0c;判断用户是否有权限或者能否请求接口&#xff0c;都是根据用户登录成功后&#xff0c;服务端授予的token进行控制的。 但并不是说有了tok…

阿里云服务器多少钱一年?2024年阿里云服务器租用价格表

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

vivo发布2023 年度科技创新;阿里全新AI代理,可模拟人类操作手机

vivo 发布 2023 年度十大产品技术创新 近日&#xff0c;vivo 发布了「2023 年度科技创新」十大产品技术创新榜单&#xff0c;并将这些技术分为了 4 个板块。 「四大蓝科技」为 vivo 在去年推出的全新技术品牌&#xff0c;涵盖蓝晶芯片技术栈、蓝海续航系统、蓝心大模型、蓝河操…

SERVLET过滤器

SERVLET过滤器 全球因特网用户使用不同类型的Web浏览器访问应用服务器上存储的Web应用程序。每个浏览器根据对应的Web浏览器窗口中的设置显示应用程序中的信息。Web应用程序可能会有一些客户机的Web浏览器不支持的HTML标记或功能。这种情况下,应用程序在客户机的Web浏览器中可…

【人工智能】文本嵌入:向量存储与数据查询的智慧交织(12)

在当今信息激增的时代&#xff0c;将中文存储到向量数据库&#xff08;如Redis等&#xff09;并实现向量检索&#xff0c;正成为解决日常应用中文信息处理难题的关键利器。这项技术不仅赋予计算机对中文语义的理解能力&#xff0c;更让我们能够以更智能、高效的方式处理和检索中…