C++ - 类和对象 #日期类的实现
文章目录
前言
一、导言
二、构造
三、比较大小
1、实现大于
2、等于
3、大于等于
4、小于
5、小于等于
6、不等于
二、加减
1、加与加等
2、减与减等
3、++、--
4、日期-日期
三、流提取、流插入
1、流插入
2、流提取
四、日期类所有代码汇总
总结
前言
路漫漫其修远兮,吾将上下而求索;
一、导言
首先,要实现日期类,构造函数是一定是要我们显式实现的,但因为日期类的成员变量均为内置类型且不涉及资源,所以其拷贝构造函数、赋值运算符重载函数、析构函数均无需我们显式实现,编译器自动生成的便够用;以及还需要我们实现日期类相关的功能,例如:日期相减、日期加天数等;
注:
1、类中成员函数的声明和定义分离时,在定义该函数的时候需要指定类域
2、缺省参数只能在声明给,在定义中不给
此处日期类的实现,我们会分文件,将类的主体放在Date.h 之中,将日期类中成员函数的定义放到Date.cpp 中;
二、构造
在Date.h 中代码如下:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);private:int _year;int _month;int _day;
};
在Date.cpp 中代码如下:
#include"Date.h"Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
此外我们再增加一个打印函数:
Date.h 中代码如下:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print();
private:int _year;int _month;int _day;
};
Date.cpp 中代码如下:
#include"Date.h"Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}void Date::Print()
{cout << _year << '/' << _month << '/' << _day << endl;
}
三、比较大小
我们要实现大于、大于等于、小于、小于等于、等于、不等于的运算符重载函数
Date.h 中代码如下:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print();//比较大小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);private:int _year;int _month;int _day;
};
1、实现大于
依次比较年、月、日
年大日期就大,当年相等时比较月,月大日期就大,当月相等就比较日,日大日期就大;
代码如下了:
//*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){if (_day > d._day){return true;}else{return false;}}else{return false;}}return false;
}
简化代码:
bool Date::operator>(const Date& d)
{if (_year > d._year){return true;}else if (_year == d._year && _month > d._month){return true;}else if (_year == d._year && _month == d._month && _day > d._day){return true;}return false;
}
2、等于
对于比较大小的逻辑,向来时先实现大于、等于或者小于、等于;那么剩下的比较逻辑就可以复用这两个先实现的;
代码如下:
bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
3、大于等于
直接复用大于和等于的逻辑
代码如下:
bool Date::operator>=(const Date& d)
{return (*this) > d || (*this) == d;
}
4、小于
小于就是大于等于的相反逻辑
代码如下:
bool Date::operator<(const Date& d)
{return !(*this >= d);//利用逻辑取反
}
5、小于等于
复用小于和等于
代码如下:
bool Date::operator<=(const Date& d)
{return (*this < d) || (*this == d);
}
6、不等于
等于的相反逻辑
代码如下:
bool Date::operator!=(const Date& d)
{return !(*this == d);
}
二、加减
1、加与加等
此处的加与加等只能日期+天数,日期+=天数,因为日期加日期是没有意义的;
Date.h 中的代码如下:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print();//比较大小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);//加与加等Date operator+(int day);Date& operator+=(int day);private:int _year;int _month;int _day;
};
Q1:日期加一个天数该如何相加;
- 相加未超过该月天数,直接相加
- 相加超过该月天数:进位(天满了往月上进,月满了往年上进)
日期+天数的本质:加法的进位
想要进位,首先就得知道当前月份有多少天,需要单独写一个函数结合年月日来获得该月的天数,为这个函数命名为 GetMonthDay ;
GetMonthDay的实现:
- 方法一:使用switch case , 也可以使用 if else 来划分月数,唯独2月的时候需要判断当前年是否为闰年
- 方法二:定义一个数组,利用数组下标去映射月份,改下标对应的空间就是该月份的天数,需要对二月进行特殊处理:判断当前年是否为闰年;
注:闰年二月29天,平年二月28天;
GetMonthDay的参考代码如下:
//inline 在类中定义的函数默认为内联函数int GetMonthDay(int year, int month, int day){static int monthDayArray[13] = { -1 , 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 monthDayArray[month];}
函数GetMonthDay 直接放到Date.h 中类Date 的Public中便可,因为函数GetMonthDay会被频繁使用并且代码行数不多,写成内联挺友好的;
Q:为什么要在数组monthDayArray 前面添加一个 static ?
- 因为函数GetMonthDay会被频繁调用,而每次均会使用该数组而开辟空间(有时间上的消耗),故而完全可以将该数组放到静态区中;
例如 d1 + 20 是不会改变d1 中的数据所以可以加上const 来修饰this指针 ,在实现的过程中需要创建跟d1 一样的局部对象,要返回结果,所以返回类型为Date;
我们先实现出来,代码如下:
//加
Date Date::operator+(int day)
{//创建局部对象Date tmp(*this);tmp._day += day;//要让_day 符合当前月份的天数while(tmp._day > GetMonthDay(tmp._year, tmp._month)){//大于,tmp._day -当前月份天数 , 然后月份+1tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;//还要判断月份是否越界if (tmp._month > 12){++tmp._year;tmp._month = 1;}}return tmp;
}
//加等
Date& Date::operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month > 12){++_year;_month = 1;}}return *this;
}
简单测试一下:
这个小测试没有问题,但是并不代表这个代码没有问题,可以多测试几次,分析此处容易出bug 的地方,跨闰年、跨许多月份;
但是我们也不知道从2025年5月5日过5000天便是2039年1月12日是否正确,如何解决这个问题呢?
- 方法一:一次性不加这么多,逐步叠加,需要手算
- 方法二:日期相加是一个很简单、成熟的程序,可以借助别人的程序加以验证(上网搜)
可见,我们的代码主体逻辑上是没有问题的;
倘若加负数呢?
加上负数就出现了问题,在代码实现的时,首先应该判断day 是正数还是负数;
代码如下:
//加
Date Date::operator+(int day)
{//创建局部对象Date tmp(*this);if (day < 0)return tmp -= (-day);//复用减等tmp._day += day;//要让_day 符合当前月份的天数while(tmp._day > GetMonthDay(tmp._year, tmp._month)){//大于,tmp._day -当前月份天数 , 然后月份+1tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;//还要判断月份是否越界if (tmp._month > 12){++tmp._year;tmp._month = 1;}}return tmp;
}//加等
Date& Date::operator+=(int day)
{if (day < 0)return *this -= (-day);//复用减等_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month > 12){++_year;_month = 1;}}return *this;
}
当day 为负数的时候,直接去复用 -= 的函数
其实还可以继续优化,让加复用加等,因为加和加等是一样的逻辑,如下:
//加
Date Date::operator+(int day)
{//创建局部对象Date tmp(*this);//复用加等tmp += day;return tmp;
}
此处就不测试了,实现了 -= 的重载再测试;
对比加与加等:
相似的主逻辑的提取:
实际上就有两种复用方式:
方式一:加复用加等
方式二:加等复用加
Q: 这两种复用方式哪一种更好?
- 加复用加等更好;
2、减与减等
加是进位,减是借位;进位是减去当前月份的天数,然后再让当前月份进位;而借位则就需要加上当前月上一个月的天数,如何实现?
- 先处理月份,然后再处理天数;
减的实现的代码如下:
//减
Date Date::operator-(int day)
{Date tmp(*this);tmp._day -= day;//判断tmp._day 是否合法while(tmp._day <= 0){//是要获取上一个月的天数,所以月份先减--tmp._month;if (tmp._month == 0){--tmp._year;tmp._month = 12;}tmp._day += GetMonthDay(tmp._year, tmp._month);}return tmp;
}
减等实现的代码如下:
//减等
Date& Date::operator-=(int day)
{_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}
测试一下:
用现成的日期计算器验证一下:
同理,我们也可以以复用的形式实现,并且考虑减负数的情况:
减等的参考代码如下:
//减等
Date& Date::operator-=(int day)
{//day 有可能为负数if (day < 0) return (*this) += (-day);_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}
减的参考代码如下:
//减 - 复用减等
Date Date::operator-(int day)
{Date tmp(*this);tmp -= day;return tmp;
}
3、++、--
++、-- 为一元操作符,而一元操作符是不会区分该操作数位于操作符的左边还是位于操作符的右边,只有二元操作符会区分左、右操作数;而++、-- 分为前置++、后置++,前置--、后置--,所以不能通过操作数在++、-- 的左边还是右边来进行区分,++、--只是一元操作符;
C++规定,后置++、-- 重载的时候可以增加一个int 形参,跟前置++、--进行区分;而至于该形参,可以给值,也可以不给值,因为这个形参的存在只是为了支持前置与后置的重载,不会使用到这个形参,即这个形参在其实现的内部逻辑中没有任何作用,所以给不给值都无所谓;
Date.h 中的声明如下:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print();//inline 在类中定义的函数默认为内联函数int GetMonthDay(int year, int month){static int monthDayArray[13] = { -1 , 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 monthDayArray[month];}//比较大小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);//加与加等Date operator+(int day);Date& operator+=(int day);//减与减等Date operator-(int day);Date& operator-=(int day);//++//前置 - 先自增后使用Date& operator++();//后置 - 先使用后自增Date operator++(int);//--//前置 - 先自减后使用Date& operator--();//后置 - 先使用后自减Date operator--(int);private:int _year;int _month;int _day;
};
注:
- 1、虽然C++规定后置需要在形参部分增加一个int 加以区分,实际上还可以使用其他类型,eg.char 、double……
- 2、前置是先自增(自减)然后再返回,所以前置使用引用返回;
- 3、可以复用前面写的加、加等、减、减等
++的参考代码:
//++
//前置 - 先自增后使用
Date& Date::operator++()
{//复用加等(*this) += 1;return *this;
}
//后置 - 先使用后自增
Date Date::operator++(int)
{Date tmp(*this);(*this) += 1;return tmp;
}
测试:
--的参考代码:
//--
//前置 - 先自减后使用
Date& Date::operator--()
{(*this) -= 1;return *this;
}
//后置 - 先使用后自减
Date Date::operator--(int)
{Date tmp(*this);(*this) -= 1;return tmp;
}
测试:
4、日期-日期
日期不可以直接减,因为每个月得天数不一样;
方法一:灵活处理,算该日期与日期之间年月日的差距,可以是先算月和日的差距然后再算年的;
eg. 2025.5.5 到 2004.1.1 相差了多少天
方法二:通过比较(假设法)得到两个日期中的较大的日期与较小的日期,让较小的日期不断地去++,再此过程中还要利用计数器计数,当较小日期等于较大日期的时候,计数器中的值就是这两个日期相差的天数;当然,如果两个日期相差地特别大的时候,该方法的效率便低了些(计算机的计算效率非常快),但终归是一个方法;
需要注意大日期-小日期以及小日期-大日期的情况,大日期-小日期得到的是正数,小日期-大日期得到的是负数,在代码实现的时候可以增加一个变量flag 来表示 this 指向的日期是大日期还是小日期;
在Date.h 中的声明:
#pragma once#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print();//inline 在类中定义的函数默认为内联函数int GetMonthDay(int year, int month){static int monthDayArray[13] = { -1 , 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 monthDayArray[month];}//比较大小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);//加与加等Date operator+(int day);Date& operator+=(int day);//减与减等Date operator-(int day);Date& operator-=(int day);//++//前置 - 先自增后使用Date& operator++();//后置 - 先使用后自增Date operator++(int);//--//前置 - 先自减后使用Date& operator--();//后置 - 先使用后自减Date operator--(int);//日期-日期//*this - dint operator-(const Date& d);private:int _year;int _month;int _day;
};
参考代码:(写于Date.cpp 中)
//日期-日期
//*this - d
int Date::operator-(const Date & d)
{int flag = 1;//先利用假设法求得两个日期中的较大值Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}//计数器记录int count = 0;while (min < max){++min;++count;}return flag*count;
}
利用别人实现的日期计算器验证一下:
Q:为什么使用前置++?
- 早期认为内置类型对象的前置++(--) 效率比后置++(--) 的效率要高,但是现在由于计算机的发展,可以认为内置类型对象的前置++(--) 和后置++(--) 没有区别;但是对于自定义类型,前置++(--) 的效率要比后置++(--) 的效率高,这是因为前置++(--) 的底层没有任何的拷贝,但是后置++(--) 存在两个拷贝;
三、流提取、流插入
- 重载<< 和 >> 的时候,需要重载为全局函数,因为重载为成员函数,this 指针会默认抢占第一个形参的位置,而重载函数要求第一个参数得是左侧得运算对象,调用时则成了 对象<<cout , 不符合我们得使用习惯和可读性;于是重载 << 和 >> 需要放在全局,把 ostream/istream 放在第一个形参的位置上,第二个形参位置上放置当前类类型的对象;
Q1:为什么存在流插入、流提取这么抽象的东西?
- C语言中的printf 和 scanf 由于占位符的存在,是有不足的,它只能输入输出内置类型的数据,而不能输出内置类型的数据;也不能直接输出类Date中的年月日(因为类Date 的成员变量是私有的,只有在类Date 中提供Printf 这样的成员函数才可以直接访问私有成员变量);C++想着把内置类型与自定义类型串在一起,让它们可以一起解决,此时便提出了流插入、流提取;流插入、流提取的存在使得任何类型的数据均可输出,只不过对于内置类型的数据来说 ,系统库已经写好了,我们可以直接用,并且其自动识别类型是因为其构成了函数重载;
Q2:这样做会不会导致效率降低?
- 相比C语言会有一些效率上的降低,但是影响不大,一般情况下我们不用担心这样的问题,因为CPU的速度是非常快的;现在CPU的每秒的计算速度差不多都是几十亿、几百亿次;
像这种使用cout 进行连续的打印,不能用我们实现的Print 函数来混用,所以需要我们自己来实现类Date 的流插入与流提取;
1、流插入
Q:什么是 cout?
cout 其实是库中一个叫ostream 类型的一个对象,ostream实际叫做:basic_ostream<char> , 经过typedef 所以称为ostream--> typedef basic_ostream<char> ostream;
ostream 即 out stream 输出流;
而之所以cout 可以自动识别内置类型的数据,是因为内置类型的数据已经被重载好了,并构成哈数重载;
我们需要思考,运算符<<的重载放在哪里?全局还是Date类中?有几个参数?放在全局的话,如何在外部获得类Date 中的私有成员变量?
我们先简单实现再测试,然后一步一步完善:
注:重载运算符函数的参数应该和该运算符作用的运算符对象的数量一样多。一元运算符有一个操作数,故而其重载的函数只有一个形参,二元运算符有两个操作数,故而该运算符重载函数有两个形参,并且其第一个形参为该运算符的左操作数,第二个形参为该运算符的右操作数;
所以我们可以根据报错得到两点:
- 1、<< 的重载函数第一个形参是ostream类型的对象,其第二个参数是Date 类型的对象(这是不能将<< 的重载函数写做Date 成员函数的主要原因,参数不匹配)
- 2、要在类外获取类Date 中的私有成员变量;
但是如果我们就是要将<< 的重载实现在类Date 中呢?这样就不用担心在类外获取类中私有成员变量的问题了;
测试如下:
运算符重载本身就是为了增强代码的可读性,本来应该写成 cout<<d1; 但是经过重载之后就要写成 d1<<cout; 显然这样会导致代码的可读性下降,并且不支持连续地写;可读性这么差,还不如使用Print ;还有就是,不能交换定义,即定义">>" 和cout 一起使用,写成 d1>>cout; 规定了<< 为流插入操作符,>> 为流提取操作符;就算可以这么做,那么内置类型的数据又怎么办?
所以,流插入的重载不能为成员函数,必须写成全局的函数;因为流插入的左操作数为cout,其运算符重载函数的第一个参数必须是 ostream类型,而成员函数的第一个参数默认为this 指针,为当前类的类型,这是矛盾的;只有放成全局函数,但是放在全局又有一个问题:要在类外访问类中的私有成员变量;
在上一篇类和对象(二)中曾提到,想要访问类中私有的成员变量有三种方式:
在此处,方法三不可行,那么就有两种方式:
- 方法一:友元函数
- 方法二:间接访问(利用GetYear、GetMonth、GetDay)
方法二在此处可行,本文采用友元函数的形式;给类中的函数添加一个友元声明,那么该函数不属于这个类,但是却可以访问到该类中的私有成员变量;
使用如下:
简单测试一下:
有时候我们会连续输出,继续测试:
连续赋值是从右往左结合,与赋值不同的是,连续流插入是从左往右结合:
d1先流插入,插入之后返回一个对象以作为下一次流插入的左操作数,那么显然,我们实现的流插入的重载函数的返回值应该是cout , 让cout 继续去做下一次流插入;
Q:像这种连续的流插入,为什么他能够自动识别类型且能识别不同的类型?
- 本质上是多个函数的调用;
需要在类Date中进行友元声明,加上一个关键字frined 即可:
代码如下:
//流插入 - 写在全局
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
测试:
2、流提取
流提取即在流中去获取数据(输入),cin 是 istream类型的对象;
对于流提取也同理,流提取重载函数要实现在全局,并且要声明为类Date 的友元函数,需要注意的是其两个形参均不能添加const ,一是会可能修改流对象,而是会把流中的数据放入该类对象中;
Q1:C语言中的scanf 要取变量的地址,为什么要取其地址呢?
- 因为scanf 就是标准输入流(stdin) 从控制台(键盘)获取数据,以后要将数据放入地址所对应的空间之中;如果不取地址,eg. scanf("%d",i); 假设有个形参x,将实参i 传给形参(传值调用),改变形参而不会影响实参,那么输入就是无效的,所以需要传址调用,即需要取地址;
而在C++ 中有了引用的概念是不需要传地址的;
需要在类Date中进行友元声明,加上一个关键字frined 即可:
代码:
//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{cout << "请依次输入年、月、日:>";in >> d._year >> d._month >> d._day;return in;
}
测试如下:
注:cin 在输入的时候默认是用空格或者换行符进行分割的;
从底层来看,cout 与 cin 本质上是一样的,均是IO行为,类似于读文件,控制台(屏幕)也是一种文件(内存文件,并非磁盘中的文件)
有一点bug,我们可以输入不错误的日期,如下:
Q:如何解决非法日期的输入呢?
- 需要在输入的时候对日期进行检查;
假设用C语言结构体实现此类,是控制不了的,但是C++可以,可以通过控制构造函数与流插入的实现即可;因为构造函数实质上就是对变量的初始化,而流插入输入数据,只要在数据来源上保证日期合法,并且其他函数的实现均是正确的,那么便不会出现非法日期;倘若我们控制了构造函数和流插入的实现,但还是存在非法日期,那么就是程序中的bug;
日期检查的大体思路:(假设对年不做检查)
声明:
参考代码:
bool Date::CheckDate()
{//月份不小于1,不大于12if (_month < 1 || _month>12|| _day <1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}
}
Q:为什么对年不做检查?
- 对于年来说,一般是公元后或者公元前,公元前不太合理,因为历史上日期的实际上是更改过的,尤其是星期。有些地方是跟不上的。即我们当前4年一闰百年不闰的日期是一千多年前的时候才确定的,古代对于地球公转、自转的观察是不够的,故而历史上有日期会跳过几天以修正,而农历又是根据节气制定的;
Q:为什么4年一闰,百年不闰,而400年又一闰?
- 地球自转一周的时间为一天,地球围绕太阳公转一圈便是一年;但是实际上地球公转的时间为365天5小时48分46秒,那么每年就会多差不多6小时,4年就会多一天,所以4年一闰;但是实际上按6小时算一年还是少了12分钟左右,四年就是48分钟,那么25个四年可以认为多了一天(实际上是多了18~20小时),所以100年不闰;但是每过一百年就又会少4~6小时,所以400又一闰,补上少的这一天;
构造函数的优化,代码如下:
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;//优化:检查输入的日期是否合法if (!CheckDate()){cout << "日期非法:" << *this << endl;}
}
注:在成员函数中访问类中的成员(成员函数、成员变量),会在该成员前默认添加一个this 指针,此 CheckDate 是this 指针调用的,即调用当前构造函数的这个对象调用了CheckDate;
测试一下:
流提取的优化:
//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{while (1){cout << "请依次输入年、月、日:>";in >> d._year >> d._month >> d._day;//优化 - 判断输入的日期是否合法if (d.CheckDate()){break;}else{cout << "输入的日期非法,请重新输入" << endl;}}return in;
}
测试:
四、日期类所有代码汇总
优化:可以在不会改变this 指针执行对象的内容的函数后面加上const
Date.h 中的代码如下:
#pragma once#include<iostream>
using namespace std;class Date
{//友元函数friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in , Date& d);
public:Date(int year = 1, int month = 1, int day = 1);void Print()const;bool CheckDate()const;//inline 在类中定义的函数默认为内联函数int GetMonthDay(int year, int month)const{static int monthDayArray[13] = { -1 , 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 monthDayArray[month];}//比较大小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)const;Date& operator+=(int day);//减与减等Date operator-(int day)const;Date& operator-=(int day);//++//前置 - 先自增后使用Date& operator++();//后置 - 先使用后自增Date operator++(int);//--//前置 - 先自减后使用Date& operator--();//后置 - 先使用后自减Date operator--(int);//日期-日期//*this - dint operator-(const Date& d) const;private:int _year;int _month;int _day;
};
Date.cpp 中的代码如下:
#define _CRT_SECURE_NO_WARNINGS 1#include"Date.h"//检查日期是否合法
bool Date::CheckDate() const
{//月份不小于1,不大于12if (_month < 1 || _month>12|| _day <1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}
}Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;//优化:检查输入的日期是否合法if (!CheckDate()){cout << "日期非法:" << *this << endl;}
}void Date::Print() const
{cout << _year << '/' << _month << '/' << _day << endl;
}//比较大小
bool Date::operator>(const Date& d) const
{if (_year > d._year){return true;}else if (_year == d._year && _month > d._month){return true;}else if (_year == d._year && _month == d._month && _day > d._day){return true;}return false;
}bool Date::operator>=(const Date& d) const
{return (*this) > d || (*this) == d;
}bool Date::operator<(const Date& d) const
{return !(*this >= d);
}bool Date::operator<=(const Date& d) const
{return (*this < d) || (*this == d);
}bool Date::operator==(const Date& d) const
{return _year == d._year&& _month == d._month&& _day == d._day;
}bool Date::operator!=(const Date& d) const
{return !(*this == d);
}//加等
Date& Date::operator+=(int day)
{if (day < 0)return *this -= (-day);//复用减等_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month > 12){++_year;_month = 1;}}return *this;
}//加
Date Date::operator+(int day) const
{//创建局部对象Date tmp(*this);//复用加等tmp += day;return tmp;
}//减等
Date& Date::operator-=(int day)
{//day 有可能为负数if (day < 0) return (*this) += (-day);_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}//减 - 复用减等
Date Date::operator-(int day) const
{Date tmp(*this);tmp -= day;return tmp;
}//++
//前置 - 先自增后使用
Date& Date::operator++()
{//复用加等(*this) += 1;return *this;
}
//后置 - 先使用后自增
Date Date::operator++(int)
{Date tmp(*this);(*this) += 1;return tmp;
}//--
//前置 - 先自减后使用
Date& Date::operator--()
{(*this) -= 1;return *this;
}
//后置 - 先使用后自减
Date Date::operator--(int)
{Date tmp(*this);(*this) -= 1;return tmp;
}//日期-日期
//*this - d
int Date::operator-(const Date & d) const
{int flag = 1;//先利用假设法求得两个日期中的较大值Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}//计数器记录int count = 0;while (min < max){++min;++count;}return flag*count;
}//流插入 - 写在全局
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//流提取 - 写在全局
istream& operator>>(istream& in, Date& d)
{while (1){cout << "请依次输入年、月、日:>";in >> d._year >> d._month >> d._day;//优化 - 判断输入的日期是否合法if (d.CheckDate()){break;}else{cout << "输入的日期非法,请重新输入" << endl;}}return in;
}
总结
可以尝试自己写一下,有利于掌握类和对象的知识,细节挺多的;
相关文章:

C++ - 类和对象 #日期类的实现
文章目录 前言 一、导言 二、构造 三、比较大小 1、实现大于 2、等于 3、大于等于 4、小于 5、小于等于 6、不等于 二、加减 1、加与加等 2、减与减等 3、、-- 4、日期-日期 三、流提取、流插入 1、流插入 2、流提取 四、日期类所有代码汇总 总结 前言 路…...
《 C++ 点滴漫谈: 三十七 》左值?右值?完美转发?C++ 引用的真相超乎你想象!
摘要 本文全面系统地讲解了 C 中的引用机制,涵盖左值引用、右值引用、引用折叠、完美转发等核心概念,并深入探讨其底层实现原理及工程实践应用。通过详细的示例与对比,读者不仅能掌握引用的语法规则和使用技巧,还能理解引用在性能…...

Redis 8.0携新功能,重新开源
01 引言 Redis从7.4版本起,将开源许可证改成 RSALv2(Redis 源代码可用许可证)与 SSPLv1(服务器端公共许可证)的双重授权策略。简单来说,就是不能随意商用。为了抵制Redis,Redis的替代品Valkey、…...
基于卡尔曼滤波的传感器融合技术的多传感器融合技术(附战场环境模拟可视化代码及应用说明)
基于卡尔曼滤波的传感器融合技术的多传感器融合技术(附战场环境模拟可视化代码及应用说明) 1 目标运动状态空间建模1.1 状态向量定义1.2 状态转移方程1.3 观测模型构建2 卡尔曼滤波核心算法实现2.1 初始化2.2 预测步骤2.3 更新步骤3 多传感器融合仿真验证3.1 传感器模型模拟3…...

从MCU到SoC的开发思维转变
目录 1、硬件设计 2、软件开发 3、调试与测试 4、电源管理 微控制器单元(MCU)和系统级芯片(SoC)是嵌入式开发中最常见的两种处理器类型。MCU以其简单、低功耗的特点,广泛应用于特定控制任务;而SoC凭借强…...

Eclipse SWT 1 等比缩放
Eclipse SWT 1 等比缩放 1 布局方式2 测试代码 1 布局方式 布局名称特点说明适合场景AbsoluteLayout绝对定位,控件位置和大小完全由开发者手动设置。特殊定制界面、不规则排版FillLayout简单线性布局,将所有子控件填满容器(水平或垂直方向&a…...

IP 地址、银行卡等多维数据于风险控制的作用
IP 地址、银行卡、手机号、身份证归属地等多维度身份数据,通过构建风险画像数据库,为交易反欺诈、广告营销检测、账户安全防护等提供了强有力的支持。 数据整合构建风险画像数据 IP 地址、银行卡、手机号、身份证归属地等数据来源各异,信息属…...

堆复习(C语言版)
目录 1.树的相关概念: 2.堆的实现 3.TopK问题 4.总结 1.树的相关概念: 1.结点的度:一个结点含有的子树(孩子)个数。 A的度为6 2.叶结点or终端结点:度为0的结点。 J、K、L、H、I 都是叶子结点 3.非终端结…...

Spring AI 与 Groq 的深度集成:解锁高效 AI 推理新体验
Spring AI 与 Groq 的深度集成:解锁高效 AI 推理新体验 前言 在人工智能飞速发展的当下,AI 推理的效率和性能成为开发者关注的焦点。Groq 作为一款基于 LPU™ 的超快速 AI 推理引擎,凭借其强大的性能,能够支持各类 AI 模型&…...

Megatron系列——张量并行
本文整理自bilibili Zomi视频 1、行切分和列切分 注意: (1)A按列切分时,X无需切分,split复制广播到A1和A2对应设备即可。最后Y1和Y2需要拼接下,即All Gather (2)A按行切分时&#…...

学习笔记:黑马程序员JavaWeb开发教程(2025.4.3)
12.1 基础登录功能 EmpService中的login方法,是根据接收到的用户名和密码,查询时emp数据库中的员工信息,会返回一个员工对象。使用了三元运算符来写返回 Login是登录,是一个业务方法,mapper接口是持久层,是…...
DeepSeek的100个应用场景
在春节前夕,浙江杭州的AI企业DeepSeek推出了其开源模型DeepSeek-R1,以仅相当于Open AI最新模型1/30的训练成本,在数学、编程等关键领域展现出媲美GPT-o1的出色性能。发布仅数日,DeepSeek-R1便迅速攀升至中美两国苹果应用商店免费榜…...

[Windows] Honeyview V5.53
[Windows] Honeyview 链接:https://pan.xunlei.com/s/VOQ3BzcINSmMb1YsHO_Pp2tqA1?pwdujkm# Honeyview是一款兼快速与强大于一体的免费图像查看器, 本版本为该软件的最后一个版本,将不再有新的更新。 主要功能 轻量且快速可以显示包括…...
VUE3基础样式调整学习经验
首先创建一个vue项目最好要把不属于自己的样式都删除掉,以面出现css难以调整的情况: 1.assets目录下的main.css、base.css等样式全部删除 2.app.vue下的样式也全部删除 3.使用element plus一定要加入样式包: import element-plus/dist/in…...

Altera系列FPGA实现图像视频采集转HDMI/LCD输出,提供4套Quartus工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目Altera系列FPGA相关方案推荐 3、设计思路框架工程设计原理框图输入Sensor之-->OV7725摄像头输入Sensor之-->OV5640摄像头输入Sensor之-->串口传图输入图像缓…...
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通 😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…...

Leetcode-BFS问题
LeetCode-BFS问题 1.Floodfill问题 1.图像渲染问题 [https://leetcode.cn/problems/flood-fill/description/](https://leetcode.cn/problems/flood-fill/description/) class Solution {public int[][] floodFill(int[][] image, int sr, int sc, int color) {//可以借助另一…...
Web端项目系统访问页面很慢,后台数据返回很快,网络也没问题,是什么导致的呢?
Web端访问缓慢问题诊断指南(测试工程师专项版) ——从浏览器渲染到网络层的全链路排查方案 一、问题定位黄金法则(前端性能四象限) 1. [网络层] 数据返回快 ≠ 资源加载快(检查Content Download时间) 2. [渲染层] DOM复杂度与浏览器重绘(查看FPS指标) 3. [执行层…...
Next.js 知识框架总结
一、核心概念 1. 渲染策略 CSR (Client-Side Rendering): 传统 React 渲染方式 SSR (Server-Side Rendering): 服务端渲染 getServerSideProps SSG (Static Site Generation): 静态生成 getStaticProps getStaticPaths (动态路由) ISR (Incremental Static Regeneration…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】8.4 数据故事化呈现(报告结构设计/业务价值提炼)
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 8.4 数据故事化呈现:从报告结构到业务价值的深度融合一、数据故事化的核心价值体系(一)报告结构设计的黄金框架1. 业务场景锚定ÿ…...

专题二:二叉树的深度搜索(二叉树剪枝)
以leetcode814题为例 题目分析: 也就是当你的子树全为0的时候就可以剪掉 算法原理分析: 首先分析问题,你子树全为0的时候才可以干掉,我们可以设递归到某一层的时候如何处理 然后抽象出三个核心问题 也就是假设我们递归到第2层…...

Hugging Face推出了一款免费AI代理工具,它能像人类一样使用电脑
Hugging Face推出了一款免费AI代理工具,它能像人类一样使用电脑。 这款工具名为Open Computer Agent(开放计算机代理),可模拟真实的电脑操作。 无需安装,在浏览器中即可运行。 以下是一些信息: - Open C…...
Datawhale AI春训营 day
待补充 2025星火杯应用赛入门应用 创空间 魔搭社区 {"default": {"system": "你是星火大模型,一个由科大讯飞研发的人工智能助手。请用简洁、专业、友好的方式回答问题。","description": "默认系统提示词"}…...
Java面试高阶篇:Spring Boot+Quarkus+Redis高并发架构设计与性能优化实战
Java面试高阶篇:Spring BootQuarkusRedis高并发架构设计与性能优化实战 面试官(严肃): Q1: 你项目中如何实现高并发下的缓存优化? 候选人(水货): 我们用了Redis做缓存,…...

生成对抗网络(GAN)深度解析:理论、技术与应用全景
生成对抗网络(Generative Adversarial Networks,GAN)作为深度学习领域的重要突破,通过对抗训练框架实现了强大的生成能力。本文从理论起源、数学建模、网络架构、工程实现到行业应用,系统拆解GAN的核心机制,涵盖基础理…...
CSRF记录
CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达…...
【ROS2】CMakeLists配置信息通俗解释
文件示例 cmake_minimum_required(VERSION 3.8) project(pkg01_helloworld_cpp)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic) endif()# find dependencies find_package(ament_cmake REQU…...

Python集成开发环境之Thonny
前言:今天介绍一款Python的傻瓜IDE(集成开发环境)——Thonny,比较适合初学者进行Python程序的开发和学习,为用户提供了代码编辑、调试、运行等一系列功能。 我应该不止两次提到过这个词了“IDE”(集成开发环境)&#…...

【超详细教程】安卓模拟器如何添加本地文件?音乐/照片/视频一键导入!
作为一名安卓开发者或手游爱好者,安卓模拟器是我们日常工作和娱乐的重要工具。但很多新手在使用过程中常常遇到一个共同问题:**如何将电脑本地的音乐、照片、视频等文件导入到安卓模拟器中?**今天,我将为大家带来一份全网最详细的…...
switch能否作用在byte上,long上,string上
在Java中,switch语句可以用于多种数据类型,但这些类型需要满足特定的条件。以下是switch语句可以作用的数据类型: byte:可以用于switch语句。由于byte可以隐式转换为int,所以可以直接在switch语句中使用。 long&#…...