[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…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...
