文章目录
📝前言🌠 头文件Date.h🌉日期计算函数🌠前后置++🌉前后置- - 🌠两对象日期相减🌉自定义流输入和输出 🌉 代码🌉 头文件Date.h🌠Date.cpp🌉 Test.cpp 🚩总结
📝前言
通过前面学完了C++
的默认成员函数,实践出真知,本小节我们将一起来实现一个简单上手的日期时间计算器,阿森和你一起一步一步的操作实现!
完整代码在文章末尾哦
🌠 头文件Date.h
为了代码的维护性和可观型,我们在设置三个文件头文件Date.h
,源文件Date.cpp
,Test.cpp
我们先把头文件该写的写上:
#pragma once#include<iostream>using namespace std;#include <stdbool.h>#include <assert.h>class Date{public://全缺省构造函数Date(int year = 1, int month = 1, int day = 1);//拷贝构造函数:由于我们知道Date这种类,可写构造也可以不写,可以让编译器自动生成Date(const Date& d);//析构函数;和拷贝构造函数一样,可写也可以不写~Date()//打印函数,检验每一步代码错误void print();Date& operator+(const Date& d);private://内置类型:缺省值可给不给int _year = 1;int _month = 1;int _day = 1;};
此时此刻,我们接下来要源文件Date.c
来实现全缺省的构造函数:
Date::Date(int year, int month, int day){_year = year;_month = month;_day = day;}
注意:这里我们全缺省构造函数进行声明与定义分离时,源文件定义时不需要缺省,也就是不要带值,否则如下图:将会重定义默认参数。
接下来我们实现打印函数:
void Date::print(){cout << _year << "-" << _month << "-" << _day << endl;}
拷贝构造函数
Date::Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}
赋值运算符重载:
Date& Date::operator+(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
此时此刻,头文件大致完成了,我们接下来要来实现简单的大小比较操作:
如这些通用的运算符重载,你可以吧他们加到头文件Date.c
类Date
里的public
中:
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);
这里有六个运算符重载,我们只需实现1组:第一组:<和==
,第二组:>和==
,其他4个可以直接调用:
我们这里实现第一组:<和==
<
的运算符重载 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; } } } // 如果以上条件都不满足,则当前日期不小于传入日期 return false;}
==
运算符重载 bool Date::operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}
两组都是按照年月日顺序进行逻辑判断
接下来就是有意思的调用操作了
小于等于意思是:小于或者等于bool Date::operator<=(const Date& d){return (*this < d) || (*this == d);}
大于意思:不小于等于 bool Date::operator>(const Date& d){return !(*this <= d);}
大于等于:不小于 bool Date::operator>=(const Date& d) {return !(*this < d);}
4.不等于就是等于的反面嘛
bool Date::operator!=(const Date& d){return !(*this == d);}
🌉日期计算函数
我们在一个日期上加天数,但是由于闰年和平年的2
月的天数不同,如果在每次加,减天数,都要判断容易犯错,因此我们可以把它封装成一个函数,进行加天数的比较,我们可以定义一个数组,由于每次调用,可以设置成全局,用静态函数修饰。
int GetMonthDay(int year, int month){assert(month < 13 && month>0);static int dayArray[13] = { -1,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 dayArray[month] + 1;//也可以直接写29}else{return dayArray[month];}}
自身加天数类:
//d1+50Date& operator+=(int day);Date operator+(int day);//d1-50Date& operator-=(int day);Date operator-(int day);
这里分为了两组:
你说得很对,这两个重载运算符的区别在于是否修改了原对象。
operator+=
是修改原对象的: //d1+=50Date& Date::operator+=(int day){ // 将天数加到当前日期上 _day += day; // 如果加上天数后,当前日期超过了当月的最大天数 while (_day > GetMonthDay(_year, _month)) { // 将当前日期减去当月的最大天数 _day -= GetMonthDay(_year, _month); // 月份加1 ++_month; // 如果月份超过了12月,则年份加1,月份重置为1月 if (13 == _month) { ++_year; _month = 1; } } // 返回当前对象的引用 return *this;}
这个函数直接修改了当前对象的成员变量,返回的是当前对象的引用。因此,如果使用 d1 += 50;
,那么 d1
对象本身会发生改变。
实现了加等,实现加就易如反掌了
operator+
是返回一个新对象:Date Date::operator+(int day){ Date temp = *this; temp += day; return temp;}
这个函数首先创建了一个临时对象 temp
,它是当前对象的副本。然后,它调用 operator+=
修改 temp
对象,最后返回 temp
。因此,如果使用 d1 = d1 + 50;
,那么 d1
对象本身不会发生改变,而是会返回一个新的 Date
对象。 上面是加等嵌套在加里面,下面是加嵌套在加等里面
Date Date::operator+(int day){Date temp = *this;temp += day;while (temp._day > GetMonthDay( temp._year, temp._month)){day -= GetMonthDay(temp._year, temp._month);++temp._month;if (13 == temp._month){++temp._year;temp._month = 1;}}return temp;}Date& Date::operator+=(int day){*this = *this + day;return *this;}
两种方法都是要创建新的变量,效果一样,第一种创建变量,拷贝构造,然后复用+=
,返回的要创建临时对象,这种方式的优点是,在调用 operator+
时,不需要重复计算日期的更新逻辑,因为 operator+=
已经实现了这个逻辑。但是第二种,由于*this = *this + day;
在*this+day
中先调用+,然后在+
中拷贝构造,然后返回临时对象,然后还要进行拷贝构造,对比第一种效率降低了,所以使用加复用加等性能更好
日期的加减也是如此:减等和减
Date& Date::operator-=(int day){_day -= day;if (0 == _day){--_month;_day = GetMonthDay(_year, _month);}while (_day <= 0){--_month;_day += GetMonthDay(_year, _month);}return *this;}// d1-50Date Date::operator-(int day){Date temp = *this;temp -= day;return temp;}
🌠前后置++
// 前置递增运算符重载// 该运算符重载函数返回递增后的日期对象的引用Date& Date::operator++(){ // 将当前日期对象加 1 天 *this += 1; // 返回递增后的日期对象的引用 return *this;}
这是前置递增运算符重载函数,它返回递增后的日期对象的引用,因此可以支持连续的前置递增操作,如 ++d1;,实现方式是调用 operator+= 函数将当前日期对象加 1 天,然后返回当前对象的引用。
// 后置递增运算符重载// 该运算符重载函数返回递增前的日期对象Date Date::operator++(int){ // 创建一个临时日期对象,保存当前日期对象的值 Date temp(*this); // 将当前日期对象加 1 天 *this += 1; // 返回递增前的临时日期对象 return temp;}
这是后置递增运算符重载函数。它返回递增前的日期对象,因此可以支持后置递增操作,如 d1++;。
实现方式是:创建一个临时日期对象,保存当前日期对象的值,调用 operator+= 函数将当前日期对象加 1 天,返回保存的临时日期对象。
这两个函数的主要区别在于返回值的不同。前置递增运算符返回递增后的日期对象的引用,而后置递增运算符返回递增前的日期对象。这种差异使得它们在使用时有不同的表现。
前置递增运算符通常更高效,因为它不需要创建临时对象。后置递增运算符需要创建一个临时对象来保存原始值,然后再执行递增操作,因此会稍微慢一些。
🌉前后置- -
// 前置递减运算符重载// 该运算符重载函数返回递减后的日期对象的引用Date& Date::operator--(){ // 将当前日期对象减 1 天 *this -= 1; // 返回递减后的日期对象的引用 return *this;}
这是前置递减运算符重载函数。它返回递减后的日期对象的引用,因此可以支持连续的前置递减操作,如 --d1;实现方式是调用 operator-= 函数将当前日期对象减 1 天,然后返回当前对象的引用。
// 后置递减运算符重载// 该运算符重载函数返回递减前的日期对象Date Date::operator--(int){ // 创建一个临时日期对象,保存当前日期对象的值 Date temp(*this); // 将当前日期对象减 1 天 *this -= 1; // 返回递减前的临时日期对象 return temp;}
这是后置递减运算符重载函数。它返回递减前的日期对象,因此可以支持后置递减操作,如 d1–;。
实现方式是:创建一个临时日期对象,保存当前日期对象的值。调用 operator-= 函数将当前日期对象减 1 天,返回保存的临时日期对象。
前置递减运算符通常更高效,因为它不需要创建临时对象。
🌠两对象日期相减
//d1-d2// 日期差运算符重载// 该运算符重载函数返回两个日期对象之间的天数差int Date::operator-(const Date& d){ // 创建两个临时日期对象,分别保存较大和较小的日期 Date max = *this; Date min = d; // 标记变量,用于记录较大日期是否在前 int flag = 1; // 如果当前日期对象小于传入的日期对象 if (*this < d) { // 交换两个临时日期对象的值,使 max 保存较大的日期 max = d; min = *this; // 将标记变量设为 -1,表示较小日期在前 flag = -1; } // 初始化天数差为 0 int n = 0; // 循环递增较小日期,直到与较大日期相等 while (min != max) { // 递增较小日期 ++min; // 累加天数差 ++n; } // 返回天数差,并根据标记变量的值确定正负 return n * flag;}
首先创建两个临时日期对象 max
和 min
,分别保存较大和较小的日期,然后判断当前日期对象是否小于传入的日期对象,如果是,则交换 max
和 min
的值,并将标记变量 flag
设为 -1
,接下来,使用 while
循环递增 min
日期,直到与 max
日期相等,同时累加天数差 n
,最后,根据标记变量 flag
的值确定返回值的正负,即返回两个日期对象之间的天数差。
🌉自定义流输入和输出
通常我们可以输入的时候是不是想这样输入:cin>>d1
或者输出cout<<d2
,如下面这个流运算符重载,我们知道重载这里有this
指针,顺序是this
,cout
,那么它的传参表示是d1<<cout
,哇,这和我们平时的逻辑相反不可观,而且这只能定义在类里面,外部无法调用那咋办?
void Date::operator<<(ostream& out){out << _year << "年" << _month << "月" << _day << "日" << endl;}
我们可以定义在全局作用域里,
// 重载输出运算符 <<// 该运算符重载函数用于将日期对象输出到输出流中ostream& operator<<(ostream& out, const Date& d){ // 将日期对象的年、月、日输出到输出流中 // 每个数值后跟相应的单位 out << d._year << "年" << d._month << "月" << d._day << "日" << endl; // 返回输出流对象,以支持连续输出 return out;}
这是重载输出运算符 <<
的函数。它接受一个输出流对象 out
和一个常量日期对象 d 作为参数,该函数返回输出流对象 out
,以支持连续输出。
bool Date::CheckDate(){if (_month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}}// 重载输入运算符 >>// 该运算符重载函数用于从输入流中读取日期对象的值istream& operator>>(istream& in, Date& d){ // 提示用户输入年/月/日 cout << "请依次输入年/月/日:->"; // 从输入流中读取年、月、日的值,并存储到日期对象d中 in >> d._year >> d._month >> d._day; if (!d.CheckDate())//避免出现2024 4 0日{cout << "日期非法" << endl;} // 返回输入流对象,以支持连续输入 return in;}
这是重载输入运算符 >>
的函数,它接受一个输入流对象 in
和一个可修改的日期对象 d
作为参数,该函数返回输入流对象 in
,以支持连续输入。注意:CheckDate()
为了防止输入 2024年4月0日
最后问题是在全局两个在全局变量中怎么能使用Date的内置类型呢?
没错!我是你的朋友就好啦!!!友元friend
我的朋友就可以用了嘛,这里可以定义在public
里面也可以不在
#pragma once#include<iostream>using namespace std;#include <stdbool.h>#include <assert.h>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);//拷贝构造函数:由于我们知道Date这种类,可写构造也可以不写,可以让编译器自动生成Date(const Date& d);//析构函数;和拷贝构造函数一样,可写也可以不写~Date()//打印函数,检验每一步代码错误void print();Date& operator+(const Date& d);private://内置类型:缺省值可给不给int _year = 1;int _month = 1;int _day = 1;};
🌉 代码
🌉 头文件Date.h
#pragma once#include<iostream>using namespace std;#include <stdbool.h>#include <assert.h>class Date{//cout << d1friend 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();Date& operator+(const Date& d);//定义到类里,默认是内敛函数int GetMonthDay(int year, int month){assert(month < 13 && month>0);static int dayArray[13] = { -1,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 dayArray[month] + 1;}else{return dayArray[month];}}bool CheckDate();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);//d1+50Date& operator+=(int day);Date operator+(int day);//d1-50Date& operator-=(int day);Date operator-(int day);// ++d1Date& operator++();//d1++Date operator++(int);// --d1Date& operator--();// d1--Date operator--(int);//d1-d2int operator-(const Date& d);private:int _year = 1;int _month = 1;int _day = 1;};//cout << d1ostream& operator<<(ostream& out, const Date& d);istream& operator>>(istream& in, Date& d);
🌠Date.cpp
#pragma once#include "Date.h"// 构造函数,初始化日期对象的年月日Date::Date(int year, int month, int day){ _year = year; _month = month; _day = day;}// 拷贝构造函数,创建一个新的日期对象并初始化为与给定日期对象相同的值Date::Date(const Date& d){ _year = d._year; _month = d._month; _day = d._day;}// 打印日期对象的年月日void Date::print(){ cout << _year << "-" << _month << "-" << _day << endl;}// 重载小于运算符,比较两个日期对象的大小bool Date::operator<(const Date& d){ // 先比较年份,如果年份小于则返回true if (_year < d._year) return true; // 如果年份相同,再比较月份,如果月份小于则返回true else if (_year == d._year && _month < d._month) return true; // 如果年份和月份都相同,再比较日期,如果日期小于则返回true else if (_year == d._year && _month == d._month && _day < d._day) return true; // 其他情况返回false return false;}// 重载小于等于运算符,比较两个日期对象是否相等或者前者小于后者bool Date::operator<=(const Date& d){ return (*this < d) || (*this == d);}// 重载大于运算符,比较两个日期对象的大小bool Date::operator>(const Date& d){ return !(*this <= d);}// 重载大于等于运算符,比较两个日期对象是否相等或者前者大于后者bool Date::operator>=(const Date& d){ return !(*this < d);}// 重载等于运算符,比较两个日期对象的年月日是否相同bool Date::operator==(const Date& d){ return _year == d._year && _month == d._month && _day == d._day;}// 重载不等于运算符,比较两个日期对象是否不相同bool Date::operator!=(const Date& d){ return !(*this == d);}// 重载加法运算符,将两个日期对象的年月日相加Date& Date::operator+(const Date& d){ if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this;}// 重载加等于运算符,将当前日期对象加上指定的天数Date& Date::operator+=(int day){ _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); ++_month; if (13 == _month) { ++_year; _month = 1; } } return *this;}// 重载加法运算符,创建一个新的日期对象并将当前日期对象加上指定的天数Date Date::operator+(int day){ Date temp = *this; temp += day; return temp;}// 重载减等于运算符,将当前日期对象减去指定的天数Date& Date::operator-=(int day){ _day -= day; if (0 == _day) { --_month; _day = GetMonthDay(_year, _month); } while (_day <= 0) { --_month; _day += GetMonthDay(_year, _month); } return *this;}// 重载减法运算符,创建一个新的日期对象并将当前日期对象减去指定的天数Date Date::operator-(int day){ Date temp = *this; temp -= day; return temp;}// 重载前置递增运算符,将当前日期对象加1天Date& Date::operator++(){ *this += 1; return *this;}// 重载后置递增运算符,将当前日期对象加1天并返回原始值Date Date::operator++(int){ Date temp(*this); *this += 1; return temp;}// 重载前置递减运算符,将当前日期对象减1天Date& Date::operator--(){ *this -= 1; return *this;}// 重载后置递减运算符,将当前日期对象减1天并返回原始值Date Date::operator--(int){ Date temp(*this); *this -= 1; return temp;}// 重载减法运算符,计算两个日期对象之间的天数差int Date::operator-(const Date& d){ Date max = *this; Date min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n * flag;}// 重载输出运算符,将日期对象的年月日输出到流中ostream& operator<<(ostream& out, const Date& d){ out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out;}// 检查日期是否合法bool Date::CheckDate(){ if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) return false; else return true;}// 重载输入运算符,从输入流中读取年月日并创建日期对象istream& operator>>(istream& in, Date& d){ cout << "请依次输入年/月/日:->"; in >> d._year >> d._month >> d._day; if (!d.CheckDate()) cout << "日期非法" << endl; return in;}
🌉 Test.cpp
# define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;#include "Date.h"void TestDate1(){Date d1(2024, 4, 14);Date d2 = d1 + 50;d1.print();d2.print();Date d3(2024, 4, 14);Date d4 = d3 - 50;d3.print();d4.print();}void TestDate2(){Date d1(2024, 4, 14);Date d2 = ++d1;d2.print();Date d3(2024, 4, 14);Date d4 = d3++;d4.print();}void TestDate3(){Date d1(2024, 4, 14);Date d2 = --d1;d2.print();Date d3(2024, 4, 14);Date d4 = d3--;d4.print();}void TestDate4(){Date d1(2024, 4, 14);Date d2(2020, 9, 1);int n = d1 - d2;cout << n << endl;}void TestDate5(){Date d1(2024, 4, 24);Date d2 = d1 + 5000;cout << d1;cout << d2;cin >> d1 >> d2;cout << d1 << d2;}int main(){TestDate5();return 0;}