[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员
目录
1、前言
2、全缺省的构造函数
3、打印接口
4、拷贝构造
5、赋值运算符重载(operator=)
5.1赋值重载是默认成员函数,重载格式:
5.2 赋值重载不能为全局函数
5.3 编译器默认生成
6、析构函数
7、operator>
8、operator==
9、operator>=
10、operator<
11、operator<=
12、operator!=
13、operator+= (日期+=天数)
14、operator+ (日期+天数)
15、operator-= (日期-=天数)
16、operator- (日期-天数)
17、前置++,后置++,前置--,后置--
18、日期 - 日期(返回天数)
19、const成员
1、前言
本篇文章我们将主要实现以下的这些接口:
#include <iostream>
using namespace std;class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month) const;// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1);//打印接口void Print() const;// 拷贝构造函数// d2(d1)Date(const Date& d);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 析构函数~Date();//总结一下:只读函数可以加const,内部不涉及修改成员的都是只读函数// >运算符重载bool operator>(const Date& d) const;// ==运算符重载bool operator==(const Date& d) const;// >=运算符重载bool operator>=(const Date& d) const;// <运算符重载bool operator<(const Date& d) const;// <=运算符重载bool operator<=(const Date& d) const;// !=运算符重载bool operator!=(const Date& d) const;// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day) const;// 日期-天数Date operator-(int day) const;// 日期-=天数Date& operator-=(int day);// 函数重载// 运算符重载// 前置++Date& operator++(); //++d1 -> d1.operator()// 加一个int参数,进行占位,跟前置++构成函数重载进行区分// 后置++Date operator++(int); //d1++ -> d1.operator(0)// 后置--Date operator--(int);// 前置--Date& operator--();// 日期-日期 返回天数int operator-(const Date& d) const;private:int _year;int _month;int _day;};
本次的项目我们以多文件来写,包含以下三个文件:
接下来我们就对以上的接口一一进行实现:
2、全缺省的构造函数
对于全缺省的构造函数正常写的时候存在一个不足,万一传参传的月份与天是不存在的,虽然对实例化的对象初始化了,但是是违法的,因此我们需要判断一下,这里就需要我们对月份的天数写一个函数,构造的时候先对比一下。
我们这里先实现GetMonthDay接口:
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month) const
{static int monthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if(2 == month&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return monthDay[month];
}
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{if (month < 1 || month > 12|| day < 1 || day > GetMonthDay(year, month))// 判断日期是否合法{cout << "非法日期" << endl;exit(-1);}else{_year = year;_month = month;_day = day;}
}
对于GetMonthDay接口,我们写了一个数组存每个月有多少天,默认二月是28天,在下面我们会判断一下如果是找二月的天数,对年进行判断,看看是否为闰年,为闰年的时候直接返回29,其他的就直接返回月份对应的天数。我们对数组使用static修饰,因为后面会不断的调用此函数,因此我们将其放到静态区,只开辟一次,节省了时间,一次的时间不多,但是如果是大量的调用会极大的提升效率。
这里我们会有人问,为什么在GetMonthDay接口后面加一个const,这里我们来说明一下:
3、打印接口
打印接口没什么讲的,我们直接一把梭哈:
//打印接口
void Date::Print() const
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
打印接口对this使用const修饰是因为,有可能我们传过来的对象是const修饰的,如果Print接口不加const就涉及权限放大的问题,导致出错。改为const后就不存在权限问题了。
4、拷贝构造
对于拷贝如果还有不清楚的可以点击后面的链接,里面有对拷贝构造详细讲解哦:点这里哦
// 拷贝构造函数
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
5、赋值运算符重载(operator=)
5.1赋值重载是默认成员函数,重载格式:
参数类型:const T&,传引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this:要复合连续赋值的含义
我们按重载格式来写一下:
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{if (this != &d)// 存在this就是d的情况{_year = d._year;_month = d._month;_day = d._day;}return *this;
}
5.2 赋值重载不能为全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
我们自己写一下试试:
我这里使用的vs2019编译器,编译器会提示,编译期间直接报错了,因此赋值运算符重载是不可以为全局函数的。
5.3 编译器默认生成
当我们不写的时候编译器会自动生成一个复制重载函数,但是默认生成的函数对内置类型是直接赋值的,对自定义类型的成员变量需要调用对应的类赋值重载函数来完成赋值。
因此,如果成员变量里存在自定义类型(类类型),自定义类型的赋值重载函数必须是正确的,这样才是对的。
如两个栈实现一个队列,队列的赋值重载函数可以默认生成,但是栈的必须自己写,因为栈存在申请资源,如果直接拷贝,两个栈使用的是同一块资源,这样的话,一个是出栈的栈,一个是进栈的栈,不管进出其实是对同一个栈进行操作,这就是错误的。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
赋值与拷贝的区别:拷贝是一个已经存在的对象去初始化另一个要创建的对象;
赋值是两个已经存在的对象进行拷贝。
6、析构函数
对于析构函数如果还有不清楚的可以点击后面的链接,里面有对析构函数详细讲解哦:点这里哦
// 析构函数
Date::~Date()
{_year = 0;_month = 0;_day = 0;
}
7、operator>
对 日期>日期 的判断中
1、当年小于后,就不用再比了,直接返回false
2、当年相同比较月,月小于后,直接返回false
3、当年月都相同,日小于后,返回false
4、当以上判断都不正确,说明前面的日期 大于 后面的日期,返回true
// >运算符重载
bool Date::operator>(const Date& d) const
{if (_year < d._year)return false;else if (_year == d._year && _month < d._month)return false;else if (_year == d._year && _month == d._month && _day < d._day)return false;elsereturn true;
}
8、operator==
对 日期==日期 的判断很简单,年月日分别都相等就是正确的,我们看看代码实现:
// ==运算符重载
bool Date::operator==(const Date& d) const
{if (_year == d._year && _month == d._month && _day == d._day)return true;elsereturn false;
}
上面的代码还可以再精简一下:
bool Date::operator==(const Date& d) const
{return _year == d._year && _month == d._month && _day == d._day;
}
9、operator>=
对于 >= 来说,我们其实可以复用上面的 >与== ,不用再去写一套逻辑来判断,>= 的逻辑就是大于或者等于。
我们来看看实现代码:
// >=运算符重载
bool Date::operator>=(const Date& d) const
{return (*this > d) || (*this == d);
}
10、operator<
< 与 >= 是相反的逻辑,因此我们对 >= 取反就可以实现。
// <运算符重载
bool Date::operator<(const Date& d) const
{return !(*this >= d);
}
11、operator<=
<= 与 > 是相反的逻辑,因此我们对 > 取反就可以实现。
// <=运算符重载
bool Date::operator<=(const Date& d) const
{return !(*this > d);
}
12、operator!=
!= 与 == 是相反的逻辑,因此我们对 == 取反就可以实现。
// !=运算符重载
bool Date::operator!=(const Date& d) const
{return !(*this == d);
}
13、operator+= (日期+=天数)
+=是在原基础上进行修改,因此隐含的this不能用const修饰,因为在原基础上修改,所以出了+=函数体,对象还在,我们使用引用返回,这样可以减少拷贝。
我们画图对 += 天数分析:
代码实现:
// 日期+=天数
Date& Date::operator+=(int day)
{if (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);//月进位_month++;//月满了if (13 == _month){_year++;_month = 1;}}return *this;
}
我们来测试一下:
对照看看我们写的+=是否正确
14、operator+ (日期+天数)
+与+= 的区别在于+=是对对象本身+,而+不是对对象本身的改变。所以我们需要实例化一个新的对象,将传来的对象拷贝给新的对象,存放+天数的结果并返回。
// 日期+天数
Date Date::operator+(int day) const
{Date tmp(*this);tmp += day;return tmp;
}
这里我们拷贝了一份之后,就能复用+=的代码。
我们这里看看传的对象与+后的对象的结果:
这里我们可以看到+并没有影响到d1的结果。
15、operator-= (日期-=天数)
-=是在原基础上进行修改,因此隐含的this不能用const修饰,因为在原基础上修改,所以出了-=函数体,对象还在,我们使用引用返回,这样可以减少拷贝。
我们画图对-=天数进行分析:
代码实现:
// 日期-=天数
Date& Date::operator-=(int day)
{if (day < 0){return *this += (-day);}_day -= day;while (_day <= 0){_month--;if (0 == _month){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}
运行结果:
对比一下,看我们实现的是否正确:
16、operator- (日期-天数)
-与+ 的逻辑是类似的,-与-= 的区别在于-=是对对象本身-,而-不是对对象本身的改变。所以我们需要实例化一个新的对象,将传来的对象拷贝给新的对象,存放-天数的结果并返回。
// 日期-天数
Date Date::operator-(int day) const
{Date tmp(*this);tmp -= day;return tmp;
}
这里拷贝一份this之后,就可以在拷贝的tmp上复用-=。
我们来测试一下:
17、前置++,后置++,前置--,后置--
对于前置++与后置++,前置--与后置--,它们的函数名是相同的,但是实现的功能是不同的,如果在符号表里找这怎么能分得清楚呢?
对于这样的问题,C++有自己确定的规定,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递。后置--也是如此。
这样就能实现函数重载,符号表中函数名虽然相同,但是一个有参数一个没参数。
下面我们对这几个函数接口分别实现:
前置++的规则是:先++,再使用。因此前置++就相当于+=1,复用+=。
// 前置++
Date& Date::operator++() //++d1 -> d1.operator()
{*this += 1;return *this;
}
后置++的规则是:先使用,再++。这里我们先将this拷贝一份,然后对this+=1,返回拷贝的对象,这样就做到了先使用,再++。
// 后置++
Date Date::operator++(int) //d1++ -> d1.operator(0)
{Date tmp(*this);*this += 1;return tmp;
}
前置--的规则是:先--,再使用。因此前置++就相当于-=1,复用-=。
// 前置--
Date& Date::operator--()
{*this -= 1;return *this;
}
后置--的规则是:先使用,再--。这里我们先将this拷贝一份,然后对this-=1,返回拷贝的对象,这样就做到了先使用,再--。
// 后置--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
18、日期 - 日期(返回天数)
此接口是 日期-日期,这里有一个存在一个小细节,当 小日期-大日期 的时候,返回值就是负数,因此我们对这个细节要注意一点,我们来对这个接口的思路梳理一遍:
1、我们用假设法,假设this是大日期,将其赋值给max对象,第二个参数是小日期,将其赋值给min对象,并定义一个flag初始化为 1;
2、比较一下,如果this是小日期那么将max、min两个对象的内容重新赋值,并将flag赋值为 -1;
3、定义一个计数器 n,让小追大,++min一次,计数器也++,追上后就跳出循环,返回 n*flag 即可。
注意:我们让小追大的时候使用前置++,虽然前置++与后置++都可以实现,但是后置++的接口中需要拷贝两次,开始拷贝一次,返回值会再拷贝一次,前置++在大量计算的时候可以实现时间与空间双重优势。
我们来实现代码:
// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{int flag = 1;Date max = *this;Date min = d;if (max < min){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++n;++min;}return n * flag;
}
测试:
19、const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
这里我们使用 Print 接口来讲:
使用const修饰后,与原本的成员函数是不冲突的,因为是构成重载的。
我们来看看两种版本的 Print 接口:
void Date::Print()
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}void Date::Print() const
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
对于打印函数来讲,函数内部是不涉及修改成员的,这种就是只读函数。
因此我们使用了const修饰,还存在一种问题,当我们的对象是const修饰,如果Print函数不用const修饰,在调用的时候就存在权限放大问题。
我们先将const修饰的Print函数频闭掉:
我们再将const修饰的接口放开看看:
总结:相同函数,const修饰的this与不修饰的函数构成重载;
函数内部是不涉及修改成员的就是只读函数,const修饰后更安全,并且对于具有常属性的对象也可以兼容。
相关文章:

[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员
目录 1、前言 2、全缺省的构造函数 3、打印接口 4、拷贝构造 5、赋值运算符重载(operator) 5.1赋值重载是默认成员函数,重载格式: 5.2 赋值重载不能为全局函数 5.3 编译器默认生成 6、析构函数 7、operator> 8、ope…...

wonderful-sql 作业
Sql 作业 作业1: 答: create table Employee (Id integer not null, Name varchar(32) , Salary integer, departmentId integer, primary key (Id) );create table Department( Id integer primary key, Name varchar(30) not null );insert into emp…...

Go学习第六天
Golang变量内置pair结构详细说明 变量包括(type, value)两部分type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型类型断言能否成功,取决…...

opencv-34 图像平滑处理-2D 卷积 cv2.filter2D()
2D卷积是一种图像处理和计算机视觉中常用的操作,用于在图像上应用滤波器或卷积核,从而对图像进行特征提取、平滑处理或边缘检测等操作。 在2D卷积中,图像和卷积核都是二维的矩阵或数组。卷积操作将卷积核在图像上滑动,对每个局部区…...
Java 克隆技术详解,深拷贝与浅拷贝的区别及实现
什么是克隆,为什么在编程中使用克隆 克隆是指创建一个对象的副本,使得新创建的对象在内容上与原始对象相同。在编程中,克隆是常用的技术之一,它具有以下几个重要用途和优势: 复制对象:使用克隆可以创建一个…...
包装器function
std::function模板类是一个通用的可调用对象的包装器,用简单的、统一的方式处理可调用对象。 template<class _Fty> class function…… _Fty是可调用对象的类型,格式:返回类型(参数列表)。 包含头文件:#include <functi…...

Django Rest_Framework(三)
文章目录 1. 认证Authentication2. 权限Permissions使用提供的权限举例自定义权限 3. 限流Throttling基本使用可选限流类 4. 过滤Filtering5. 排序Ordering6. 分页Pagination可选分页器 7. 异常处理 ExceptionsREST framework定义的异常 8. 自动生成接口文档coreapi安装依赖设置…...

总结 IO、存储、硬盘、文件系统相关常识
目录 一、IO是什么? 二、存储 三、硬盘 四、文件系统 4.1 文件目录和组织方式 4.2 文化路径 4.3 文件类型 4.4 文件系统操作 一、IO是什么? IO是英文Input/Output的缩写,指输入/输出。在计算机科学中,IO通常指计算机与外部设备或…...
JavaScript、深入浅出Node.js前端技能汇总
JavaScript 前端技能汇总 Frontend Knowledge Structure 深入浅出Node.js 书籍pdf 《深入浅出Node.js》的相关代码 Javascript&jQuery教程 pdf html & css教程 pdf 高性能JavaScript_中英双语版 pdf 跳坑之js调用另一个js文件中函数 方法1; 在html文件中加入两…...
use gnustep objective-c
first app #import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {NSAutoreleasePool *pool [NSAutoreleasePool new];NSLog("first start");[pool drain];return 0; }tech 专注于概念,而不是迷失在语言技术细节中编程语言…...

8.15锁的优化
1.锁升级(锁膨胀) 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 偏向锁:不是真的加锁,而是做了一个标记,如果有别的线程来竞争才会真的加锁,如果没有别的线程竞争就不会加锁. 轻量级锁:一个线程占领锁资源后,另一个线程通过自旋的方式反复确认锁是否被是否(这个过程比较…...

单片机复位电路分析
来分析一下这个电路: 首先这里面有电容,所以是一个动态电路。哈哈哈 假设左上角的电压源是5V的代号为VOLT。 可以知道电容capacitor C1左边的电压也是5V,电容中间隔着一个绝缘体,所以不导电, 这个时候电流无法通过…...

公文写作技巧:“三面镜子”写作提纲60例
写作技巧:“三面镜子”写作提纲60例 1. 用好“三面镜子” 推深做实警示教育 勤用“反光镜”以案为鉴。 善用“显微镜”以案明纪。 巧用“聚光镜”以案促改。 2. 年轻干部要用好“三面镜子” 用好“反光镜”,照亮基层中的“暗点” 用好“显微镜”&am…...
useEffect中的函数会执行2次原因
一、useEffect介绍 useEffect是React18的新特性,表示React的生命周期Hooks组件。等价于Claas组件的componentDidMount、componentDidUpdate,useEffect的返回函数等价于componentWillUnmount。(组件卸载、重新挂载都会触发这个函数,…...

更新k8s环境支付系统支付证书
目录 一、背景 二、更新支付系统银行证书 三、备份旧的secret信息 四、更新支付应用的证书信息 五、重启支付系统的应用 六、验证应用实例挂载的秘钥已更新 一、背景 支付系统是基于k8s容器化部署的微服务,支付系统使用的支付证书以及和银行有关的证书都是保存…...
C#的yield
在 C# 中,yield 关键字用于定义迭代器方法(Iterator Methods),并使其返回一个可枚举的序列。通过使用 yield 关键字,可以简化迭代器的实现,使其更加直观和易于理解。 使用 yield 关键字定义的方法被称为迭…...

外卖多门店小程序开源版开发
外卖多门店小程序开源版开发 外卖多门店小程序开源版的开发可以按照以下步骤进行: 确定需求:明确外卖多门店小程序的功能和特点,包括用户注册登录、浏览菜单、下单支付、订单管理等。技术选型:选择适合开发小程序的技术框架&…...
打印图案、
描述 请编写一个程序,打印下面的图案: 输入 无 输出 打印上述图案 输入样例 1 无 输出样例 1 * * * * * * * * * * * * * * * * * * * * * * * * * 代码一(如下):直接输出 #include <iostream> usin…...

# Windows 环境下载 Android 12源码
前言 Android 官网(该方式不适合 Windows 平台):https://source.android.com/source/downloading.html (备注自 2021 年 6 月 22 日起,安卓操作系统不再支持在 Windows 或 MacOS 上进行构建,如果要编译源码推荐先安装…...
【运维面试】Docker技术面试题总结
【运维面试】Docker技术面试题总结 一、Docker的基础概念1.1 什么是Docker?它可以为我们提供哪些便利?1.2 Docker的优点是什么?1.3 Docker的镜像是什么?1.4 Docker的数据卷是什么?1.5 Docker Compose是什么?1.6 Docker Swarm是什么?1.7 Docker Hub是什么?有哪些用途?1…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...