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、流提取 四、日期类所有代码汇总 总结 前言 路…...
6:点云处理—QT三维窗口显示
1.编写halcon显示程序 导出C格式 *读取三维点云 dev_get_window(WindowHandle)dev_open_window(0, 0, 512, 512, black, WindowHandle1)*512, 512 CamParam : [0.01,0,7e-6,7e-6,512/2, 512/2,512, 512] DisPose:[30.427, -1.05274, 97.8798, 8.29326, 356.416, 99.016, 0]*彩…...
css3伸缩盒模型第三章(伸缩相关)
伸缩盒模型第三章(伸缩) 介绍了伸缩性的伸缩提现到哪些方面,以及flex 居中的小技巧,视觉差异等 flex 水平垂直居中伸缩性flex 复合式属性项目排序侧轴单独对齐 flex 水平居中 我们可以使用flex 使元素居中对齐,当然也可以用 margin 等进行…...
【Ubuntu】安裝向日葵远程控制
前言 在Ubuntu 24.04.2下安装向日葵远程控制出错,少了一些依赖,需要安装一些依赖。 1.安装gconf2-common wget http://mirrors.kernel.org/ubuntu/pool/universe/g/gconf/gconf2-common_3.2.6-6ubuntu1_all.deb sudo dpkg -i gconf2-common_3.2.6-6ub…...
Screeps Arena基础入门
本文主要内容 JavaSsript语法使用VScode编译环境Screeps Arena游戏规则 JavaSsript语法使用 基本数据类型 // String, Numker,Boolean,null, undefined const username "John"; const age 30; const rate 4.5; const iscool true; const x null; #表示值为…...
docker 安装 sqlserver2022 和注意点
一、前言 1、可以直接参考微软官方文档 快速入门:使用 Docker 运行 SQL Server Linux 容器映像,这里主要是说一些注意点和坑 二、安装 1、拉取镜像 docker pull mcr.microsoft.com/mssql/server:2022-latest2、创建挂载目录,这里只是比官方…...
Centos系统详解架构详解
CentOS 全面详解 一、CentOS 概述 CentOS(Community Enterprise Operating System) 是基于 Red Hat Enterprise Linux(RHEL) 源代码构建的免费开源操作系统,专注于稳定性、安全性和长期支持,广泛应用于服…...
Git实战经验分享:深入掌握git commit --amend的进阶技巧
一、工具简介 git commit --amend是Git版本控制系统的核心补救命令,主要用于修正最近一次提交的元数据。该命令不会产生新的提交记录,而是通过覆盖原提交实现版本历史的整洁性,特别适合在本地仓库进行提交优化。 二、核心应用场景 提交信息…...
基于LSTM与SHAP可解释性分析的神经网络回归预测模型【MATLAB】
基于LSTM与SHAP可解释性分析的神经网络回归预测模型【MATLAB】 一、引言 在数据驱动的智能时代,时间序列预测已成为许多领域(如金融、气象、工业监测等)中的关键任务。长短期记忆网络(LSTM)因其在捕捉时间序列长期依…...
C++卡特兰数讲解
前情提要,参考资料:卡特兰数 - OI Wiki 一、定义 卡特兰数(Catalan number)是一个在组合数学中经常出现的数列,应用范围很广,例如括号匹配问题、出栈顺序问题、多边形三角剖分问题等。在 C 中,可以使用多种…...
【数据融合实战手册·应用篇】“数字孪生+视频融合”让智慧城市拥有空间感知
一、视频融合技术如何破局城市治理? #从"碎片监控"到"上帝视角" 传统视频监控系统画面分散,监管人员需要观看多个分镜头画面,难以将零散的分镜头视频与其实际地理位置对应,容易产生信息孤岛,同时…...
[数据库之十一] 数据库索引之联合索引
执行数据库查询时,通常查询条件是多对个属性进行判断和约束,对于这种类型的查询,如果存在多个索引则使用多个索引,或者使用建立在多属性搜索码上的索引,这样能提高查询效率。 一、使用多个单码索引 假设数据表 instruc…...
【Linux】Linux工具(1)
3.Linux工具(1) 文章目录 3.Linux工具(1)Linux 软件包管理器 yum什么是软件包关于 rzsz查看软件包——yum list命令如何安装软件如何卸载软件补充——yum如何找到要安装软件的下载地址 Linux开发工具Linux编辑器-vim使用1.vim的基…...
微信小程序使用腾讯云COS SDK实现用户头像上传
引入与初始化COS SDK (从官网下载cos-wx-sdk-v5.js文件,本地上传) const COS require(../../../static/utils/cos-wx-sdk-v5.js) as any;//实例化一个cos对象 const cos new COS({SecretId: AK***********************VedSJ, //idSecretK…...
什么是HTML、CSS 和 JavaScript?
HTML、CSS 和 JavaScript 是构建网页的三大核心技术,它们分工明确又紧密协作。接下来我将分别介绍三者的定义、功能,并阐述它们如何共同构成网页,最后推荐学习资源。 一、HTML:网页的骨架与内容基础 HTML(HyperText …...
兼具直连存储与分布式存储优势的混合存储方案
兼具直连存储与分布式存储优势的混合存储方案 一、分层存储架构 热数据本地直存 将高频访问的热数据(如数据库事务日志、实时计算中间结果)存储在DAS本地SSD中利用DAS的低延迟特性提升实时性要求高的业务性能 温冷数据分布式存储 低频访问的温…...
App Store支付新政重构跨境电商生态:eBay卖家的突围之道
2024年4月,美国联邦法院对苹果应用商店支付体系的裁决掀起全球电商海啸。这场源自Epic Games诉讼案的制度变革,正在跨境电商领域掀起超预期的蝴蝶效应。本文将深入剖析政策变革对eBay生态的冲击路径,揭示跨境卖家转型升级的核心策略。 一、支…...
Rust 开发环境搭建与插件
生成完整文章 用户请求写一篇完整的CSDN文章,要求根据之前的建议生成。文章要包含结构清晰、标题、代码示例、常见提示、跨平台Rust工具链安装差异等内容。文章需准确,包括插件和使用提示,CLI程序示例等。主要内容包括:介绍、Rus…...
Axios替代品Alova
介绍alova | Alova.JS Multipart 实体请求 | Axios中文文档 | Axios中文网 1. 极致的轻量与性能 Tree-shaking优化:仅打包使用到的功能模块 零依赖:基础包仅 4KB(Axios 12KB) 2. 智能请求管理(开箱即用࿰…...
【C语言】文件操作(续)
目录 复习: 一⽂件的顺序读写 例子: 前言: 在上篇文章中介绍了文件的类型,文件指针,流,操作的函数。 在本篇文章继续为大家带来文件细节分享,如 顺序读写等等。 复习: fopen是…...
Angular 面试常见问题
1. 请阐述 Angular 的工作原理 Angular 的工作流程涉及多个关键环节,从组件交互到浏览器渲染,以下是其核心流程: 组件交互:当用户触发特定事件(如点击按钮)时,组件会响应这些交互,…...
数据库(MySQL)基础
一、登录数据库 在linux系统中登录数据库的指令 mysql -h 127.48.0.236 -P 3306 -u root -p -h:填写IP地址,指明要连接的主机。如果不加该字段表示本地主机-P:填写端口号,指明进程。 如果不加该字段会使用默认的端口号。-u&…...
【Java ee 初阶】文件操作和IO(上)
一、文件 文件在计算机中,是保存到“硬盘”上的。操作系统,把硬盘操作进行了抽象封装,使得编程的时候,是不会直接操作硬盘的,而是通过“文件”的概念来进行间接操作。 文件有哪些操作?——>打开文件&a…...
微信小程序备案的一些记录
小程序如果没有备案是搜索不到小程序的。 小程序备案需要填写主体负责人的信息,需要主体负责人的手机号验证码, 需要填写管理员的信息,同样也需要验证手机号码, 填写完毕之后,提交进行初审,初审之后会打…...
leetcode0279. 完全平方数-medium
1 题目:完全平方数 官方标定难度:中 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1…...
2018机械行业ERP软件发展趋势
随着互联网经济的发展,实体的经济将来很有发展的优势,管理的信息化工具,也要随着市场需求的改变而改变。 以前的ERP管理系统,管理管控的方向。 1、以物料管理为核心,通过ERP管理系统,将企业的物料管理清楚&…...
限制布局大小,实现文本自适应
实现数字部分自适应 适配后 使用页需绑定ref <div class"setting-bind-text" ref"element" :style"{ transform: scale(${scale}) }">{{ coin }}</div> script部分引入使用 import { useTextScale } from /hooks/useTextScale; c…...
涨薪技术|0到1学会性能测试第52课-Tomcat调优技术
前面的推文我们掌握了Tomcat服务器的3种监控技术知识。今天给大家分享Tomcat调优技术。后续文章都会系统分享干货,带大家从0到1学会性能测试。 在对Tomcat进行调优之前,需要对Tomcat的结构体系有一个清楚的了解,这对调优起到至交重要的作用,Tomcat结构体系图,如图10-20所示…...
Arm核的Ubuntu系统上安装Wireshark
Arm核的Ubuntu系统上安装Wireshark 一、安装wireshark 安装命令: sudo apt-get install wireshark-qt 如下图所示: 安装过程弹出如下界面: 鼠标选择Yes,点回车键确认 安装完成。 二、打开wireshark 输入命令行打开wireshark …...
C++模板【上篇】 —详解模板基础语法
文章目录 前言1. 泛型编程2. 模板的类别2.1 函数模板2.2 类模板 3. 模板的实例化3.1 函数模板的实例化3.1.1 隐式实例化* 编译器实例化原理3.1.2 显示实例化 3.2 类模板的实例化 前言 在这篇文章中,主要介绍一些模板的基础的语法和一些细节,同时了解泛型…...
谈谈Redis缓存和数据库一致性
目录 1、缓存问题 2、更新缓存 3、删除缓存 4、最终方案 5、缓存分类 5.1、缓存穿透 5.2、缓存击穿 5.2、缓存雪崩 6、示例 前言 Redis 作为缓存与数据库之间的通信模式能够显著提升系统性能,减少数据库的压力。 通过合理使用 Redis 进行数据存取ÿ…...
JWT深度解析:现代Web身份验证的通行证-优雅草卓伊凡
# JWT深度解析:现代Web身份验证的通行证 ## 一、JWT的本质与构成 ### 1.1 JWT的定义解析 JWT(JSON Web Token)是一种**开放标准(RFC 7519)**,用于在各方之间安全地传输信息作为JSON对象。这种信息可以被…...
VTK|.obj文件数据处理+Jet/Viridis/CoolToWarm/Grayscale/Rainbow/风格颜色渲染
文章目录 处理OBJ文件Jet渲染风格Viridis渲染风格CoolToWarm渲染风格Grayscale渲染风格Rainbow渲染风格切换风格按钮槽函数(可优化)相关代码github链接 将 .obj 数据进行 Elevation 着色并可视化渲染的完整流程 和.ply文件处理方式一样 处理OBJ文件 vo…...
如何通过服务主体获取 Azure 凭据
本文详细讲解如何通过 Azure 服务主体生成凭据,使应用程序能够安全访问 Azure 资源(如部署 Container Apps)。以下步骤基于 Azure Portal 操作,适用于自动化部署、CI/CD 等场景。 步骤 1:登录 Azure Portal 访问 Azure 门户。使用 Azure 账户(需具备订阅管理员权限)登录…...
Kubernetes探针生产环境实战指南
一、探针的本质:应用健康的智能体检系统 想象你的应用是一个高空走钢丝的演员,Kubernetes探针就像三位安全员: 启动探针:检查演员是否站稳(应用是否完成初始化)就绪探针:确认演员准备好表演&a…...
node.js 实战——express图片保存到本地或服务器(七牛云、腾讯云、阿里云)
本地 ✅ 使用formidable 读取表单内容 npm i formidable ✅ 使用mime-types 获取图片后缀 npm install mime-types✅ js 中提交form表单 document.getElementById(uploadForm).addEventListener(submit, function(e){e.preventDefault();const blob preview._blob;if(!blob)…...
线代第二章矩阵第五、六、七节矩阵的转置、方阵的行列式、方阵的伴随矩阵
文章目录 矩阵的转置转置性质对称矩阵与反对称矩阵 方阵的行列式方阵的伴随矩阵(重要) 矩阵的转置 转置性质 (1) (2) (3) (4)注意这个: 扩展&a…...
经验:从CAN到以太网为主的车载网络架构升级
引言 新能源汽车智能化与网联化的进程中,传统CAN总线已难以满足高带宽、低延迟的通信需求,车载以太网逐步成为新一代电子架构的核心骨干。本文基于工程实践,系统性解析车载以太网的核心技术、协议栈、拓扑设计及工具链升级策略,助…...
基于FPGA婴儿安全监护系统(蓝牙小程序监测)
基于FPGA婴儿安全监护系统 前言一、芯片手册阅读二、代码分析1.温湿度驱动2.转速等级设置模块3.电机转速控制模块 总结视频演示 前言 实时监测车内温湿度数据(DTH11温湿度模块)----实时控制风扇驱动速度(结合温湿度进行控制)----…...
嵌入式 C 语言控制语句
目录 1. 控制语句 2. 分支语句 2.1 if else 2.2 switch 3. 循环语句 3.1 goto 3.2 while 循环 3.3 do while 循环 3.4 for 循环 3.5 例题 3.6 循环控制语句 3.6.1 break 3.6.2 continue 1. 控制语句 控制语句分为:顺序语句,分支语句࿰…...
leaflet-velocity风场粒子效果及数据处理
一,后台给到的数据 {"msg": "success","code": 200,"data": {"startLat": 39.3,"endlat": 41.2,"latdel": 0.099999994,"startLon": 115.3,"endLon": 117.50001,"…...
React 实现 JWT 登录验证的最小可运行示例
下面是一个用 React 实现 JWT 登录验证的最小可运行示例,包含: React 前端:登录、保存 Token、获取用户数据。模拟后端:用 mock API(你也可以接真后端)。 🧱 技术栈 React(使用 Vi…...
MySQL报错解决过程
我在调试datagrip的时候,显示拒绝连接,开始的时候,我以为只是服务没有开启,结果到后来在网上搜索各种解决办法无果后,就选择卸载,卸载之后安装新的MySQL 以下就是我的解决过程。 如果只是在使用外置软件&…...
更多 QVariant 使用案例
以下是 QVariant 的其他典型应用场景及代码示例,涵盖更多实际开发需求: 6. 数据库查询结果处理 处理数据库字段的异构数据类型(如整数、字符串、日期等): QSqlQuery query; query.exec("SELECT name, age, crea…...
WPF中解决数据绑定不匹配的问题
在 WPF 开发中,IValueConverter 和 IMultiValueConverter 接口是非常实用的工具,它们允许你在数据绑定过程中对数据进行转换。 IValueConverter 接口示例 IValueConverter 接口用于单值转换,它包含 Convert 和 ConvertBack 两个方法。Conve…...
学习Cesium Entities
🌐 Cesium中的Entities系统趣味学习 📊 Entities系统架构流程图 #mermaid-svg-Lkue5O3gYOkEVSbD {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Lkue5O3gYOkEVSbD .error-icon{fill:#552222;}#mermaid-svg-Lku…...
Spark处理过程-案例数据清洗
(一)需求说明 准备十条符合包含用户信息的文本文件,每行格式为 姓名,年龄,性别,需要清洗掉年龄为空或者非数字的行。 例如: 张三,25,男 李四,,女 王五,30,男 赵六,a,女 孙七,35,男 周八,40,女 吴九,abc,男 郑十,45,女…...
【AI提示词】马斯洛需求分析专家
提示说明 专业的心理学需求分析专家,熟悉马斯洛需求层次理论及其在不同文化背景下的适用性。 提示词 # Role: 马斯洛需求分析专家## Profile - language: 中文 - description: 专业的心理学需求分析专家,熟悉马斯洛需求层次理论及其在不同文化背景下的…...
【WebRTC-13】是在哪,什么时候,创建编解码器?
Android-RTC系列软重启,改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性,方便形成肌肉记忆。同时不分种类、不分难易程度,在线征集问题切入点。 问题:编解码器的关键实体类是什么?在哪里&什么时候…...
Kuikly 安装环境篇
1、安装版本号为2024.1.1 的Android studio(如使用高版本的Android studio需要更改JDK版本号为17) 2、JDK版本使用17(如需要修改JDK:Android Studio -> Settings -> Build,Execution,Deployment -> Build Tools -> Gr…...