[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…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...
2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...
6.计算机网络核心知识点精要手册
计算机网络核心知识点精要手册 1.协议基础篇 网络协议三要素 语法:数据与控制信息的结构或格式,如同语言中的语法规则语义:控制信息的具体含义和响应方式,规定通信双方"说什么"同步:事件执行的顺序与时序…...
【汇编逆向系列】六、函数调用包含多个参数之多个整型-参数压栈顺序,rcx,rdx,r8,r9寄存器
从本章节开始,进入到函数有多个参数的情况,前面几个章节中介绍了整型和浮点型使用了不同的寄存器在进行函数传参,ECX是整型的第一个参数的寄存器,那么多个参数的情况下函数如何传参,下面展开介绍参数为整型时候的几种情…...
