【C++深入学习】日期类函数从无到有实现

零、本文思维导图

画板

一、前期准备

1.1 检查构造的日期是否合法

//Date.cpp
bool Date::CheckDate()
{
	if (_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 << "非法日期:" << endl;
		Print();
	}
}

1.2 获取某年的某月的总天数

  • 建议直接写在类里面作为成员函数:定义在类里面的成员函数默认是内联inline,而且该函数不仅短小,还会被频繁调用;
	int GetMonthDay(int year, int month)
	{
        assert(month > 0 && month < 13);//避免出现非法月份??????
		static int GetMonthDayArray[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))
		//先判断是否为2月
		{
			return 29;//闰年
		}
		return GetMonthDayArray[month];
	}

1.3 打印函数

void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

二、日期+天数

2.1 operator+=

  • 进位:时间在向前先直接相加,日期不合法,减天加月,月满加年,直至日期合法。
  • 返回值:返回*this,所以使用引用返回。
  • 注意:这里是+=,而不是+a+1:a本身不变;a+=1:a本身是会变的。
//日期+天数:d1+=100
Date& Date::operator+= (int day)
{
    if(day < 0)
    {
        return *this-=(-day);//day为负数的情况
    }
	//正常+:2024/7/12+10=2024/7/22
	_day += day;
	while (_day > GetMonthDay(_year, _month))//判断日期是否非法
	{
		//时间在前进:
		_day -= GetMonthDay(_year, _month);//先减天
		++_month;//再加月,判断是否满月,满月进年
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

2.2 operator+

  • 使用传值返回
  • 直接写:
//d1 + 100
Date Date::operator+ (int day)
{
	Date tmp = *this;//这里拷贝一份出来
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;//这里就不能使用引用返回了,局部对象,出作用域就会销毁
}
  • 上面的是直接写的,也可以在写了operator+=后,+复用+=
//d1 + 100
Date Date::operator+ (int day)
{
	Date tmp = *this;//这里拷贝一份出来
	tmp += day;//复用+=
    
	return tmp;//这里就不能使用引用返回了,局部对象,出作用域就会销毁
}

三、日期-天数

3.1 operator-=

//d1 -= 100
Date& Date::operator-=(int day)
{
    if(day < 0)
    {
        return *this += (-day);//day是负数的情况
    }
	_day -= day;
	while (_day <= 0)//判断出非法日期
	{
		--_month;//先借月
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);//加上借来的
	}
	return *this;
}

3.2 operator-

  • 直接实现:
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp._day -= day;
	while (tmp._day<=0)
	{
		--tmp._month;
		if (tmp._month == 0)
		{
			tmp._month = 12;
			--tmp._year;
		}
		tmp._day += GetMonthDay(tmp._year, tmp._month);
	}
	return tmp;
}
  • -复用-=
//d1 - 100
Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}

3.3 两种复用对比

  1. -=复用-
Date Date::operator-(int day)
{
	Date tmp = *this;//拷贝1
	tmp._day -= day;
	while (tmp._day<=0)
	{
		--tmp._month;
		if (tmp._month == 0)
		{
			tmp._month = 12;
			--tmp._year;
		}
		tmp._day += GetMonthDay(tmp._year, tmp._month);
	}
	return tmp;//拷贝2
}

Date& Date::operator-=(int day)
{
	/*Date tmp = *this - day;
	*this = tmp;*/

	*this = *this - day;//赋值也是一种拷贝

	return *this;
}
// 拷贝的次数比第一种多
  1. -复用-=(相对较好)
//d1 -= 100
Date Date::operator-(int day)
{
	Date tmp = *this;//拷贝1
	tmp -= day;
	return tmp;//拷贝2
}
//-的拷贝次数都是一样的

Date& Date::operator-=(int day)//无拷贝
{
	_day -= day;
	while (_day <= 0)//判断出非法日期
	{
		--_month;//先借月
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}
		_day += GetMonthDay(_year, _month);//加上借来的
	}
	return *this;//传引用返回
}
  • 两种operator-的拷贝次数一样;
  • 第二种的-=是自己实现的,全程无拷贝;但是第一种-=复用-:前面-的两次拷贝再加上自己本身的一次赋值拷贝。因此第二种相对较好。

四、日期比较

4.1 operator<

先判断所有<并将其归为一类,均为true

//<运算符的重载
bool Date::operator<(const Date& d)
{
	//true为一类
	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;
			}
		}
	}
	//false为一类
	return false;
}

4.2 operator==

年月日均相等,则为true

//==运算符的重载
bool Date::operator==(const Date& d)
{
	return _year == d._year 
		&& _month == d._month 
		&& _day == d._day;
}

4.3 其他关系比较

在写了operator<(或者operator>)和operator=两个之后,就可以根据去翻等个侯总逻辑关系表示出其他的关系符。

  1. <=运算符的重载
//<=运算符的重载
bool Date::operator<=(const Date& d)
{
	//在写了前面两个之后:
	return *this < d || *this == d;
}
  1. >运算符的重载
//>运算符的重载
bool Date::operator>(const Date& d)
{
	return !(*this <= d);
}

  1. >=运算符的重载
//>=运算符的重载
bool Date::operator>=(const Date& d)
{
	return !(*this < d);
}
  1. !=运算符的重载
//!=运算符的重载
bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

五、++

  • 前置++用的比较多,而且拷贝比较少。
  • 重载++运算符时,有前置++和后置++,运算符重载函数名都是**operator++**,无法很好的区分。C++规定,后置++重载时,增加一个**int**形参,跟前置++构成函数重载,方便区分。

5.1 前置++

//1.前置++:d1.operator++()
Date& Date::operator++()//没有拷贝
{
	//Date tmp = *this;//可省略不写
    //复用 operator+=
	*this += 1;
	return *this;
}

5.2 后置++

//2.后置++:d1.operator++(0)(括号里面只要求整数)
Date Date::operator++(int)//有拷贝
{
	Date tmp = *this;//拷贝构造,用于返回
	*this += 1;
	return tmp;
}

六、-- (和++相似)

6.1 前置–

//1.前置--运算符重载
Date& Date::operator--()
{
	//Date tmp = *this;
    //复用 operator+=
	*this -= 1;
	return *this;
}

6.2 后置–

//2.后置--运算符重载
Date Date::operator--(int)
{
	Date tmp = *this;
    //复用 operator-=
	*this -= 1;
	return tmp;
}

七、日期-日期

计算两个日期之间相差的天数。

  • 法一:直接相减,非常繁琐而且容易出错,借月后从12月份开始而不是从1月份开始;
  • 法二:从年初开始,做年的减法和月日的减法,如下图条框内做减法。

  • 法三:

思路:在生活中,我们有些人算数有一种习惯,例如计算8 + 4,我们就会数:9,10,11,12,数到四个数。这里也是用的这个思路。从小的日期一直数到大的日期,同时用计数器进行计数。

注意:这里我们需要判断两个日期谁大谁小。

//日期-日期 d1 - d2
int Date::operator-(const Date& d)
{
	int flag = 1;//flag为1:表示返回正值
	//假设
	Date max = *this;
	Date min = d;
	if (*this < d)//假设不成立
	{
		max = d;//更正min、max
		min = *this;
		flag = -1;//假设错误,表示返回负值,返回的总天数*flag就可以得到正数的两天差差值
	}
	int n = 0;//相当于一个计数器
	while (min != max)
	{
		++min;//开始从min数到max
		++n;//开始计数
	}
	return n * flag;//返回的正差值天数
}

八、流插入和流提取

scanfprintf不能输入输出自定义类型例如类类型,间接输入输出又不方便,直接确定的是内置类型。

根据下面两图发现:coutostream类型的对象,cinistream类型的对象。内置类型能够直接使用是因为istreamostream里面已经用operator重载好这些操作符了,他们可以自动识别类型(如内置类型)都是因为函数重载。

如果想自主输入日期和提取日期,就可以自己实现重载<<>>操作符。

8.1 流插入运算符重载

8.1.1 实现自主输出

如果想要自己重载函数实现下面的效果:输出d1到控制台。

cout << d1;//日期类对象流入控制台(I/O流)
  1. 第一种写法(错误写法):写成成员函数
//cout是ostream类型的对象
void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}
  • 报错结果:
//test.cpp
void TestDate()
{
	Date d1, d2;

	cout << d1;//error:这里为什么用不了?
    //error: message : 尝试匹配参数列表“(std::ostream, Date)”时。
}
  • 分析报错原因:参数无法匹配:cout本来应该匹配ostream类,但是成员函数第一个隐含的参数是this指针也就是日期类,d1本来应该匹配日期类,现在匹配的是ostream类。
  • 改进:根据上面的分析,做了些许test.cpp文件的改进。虽然可以运行但是现在这段代码和我们本来的意义背道而驰。而且this指针又因const保护无法变动,所以舍弃成员函数的方法。
void TestDate5()
{
	Date d1, d2;

	//cout << d1;

	d1 << cout;
	d1.operator<<(cout);
}
  1. 第二种写法:
  • 写成全局函数,为了访问Date类里面的成员变量,我们可以将private换成public 或者 提供get函数。
//测试全局函数operator<<
void TestDate6()
{
	Date d1, d2;

	cout << d1;
	operator<<(cout, d1);
}
  • 在类外面访问类里面的内容,最好的办法就是加一个友元函数声明**friend**,位置随意。
//Date.h
//在类里面加入friend,友元函数声明
friend void operator<<(ostream& out, const Date& d);//d加const是因为d不能被改变
//在类外面
void operator<<(ostream& out, const Date& d);
//这样就可以在类外面访问类里面的私有成员了

//Date.cpp
void operator<<(ostream& out, const Date& d)//全局函数
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;	
}

8.1.2 实现自主连续输出

如果想要实现连续输出,就像下面一样的效果:

cout << d1 << d2;//需要从左往右结合
// cout << d1,返回左操作数cout,cout << d2,整体表达式的返回值就是左操作数cout
// 所以全局函数的返回值用 ostream& 来接收

根据前面的代码和逻辑,可以类比地写出本段代码如下:

//Date.h
//在类里面加入friend,友元函数声明
friend ostream& operator<<(ostream& out, const Date& d);
 //在类外面
ostream& operator<<(ostream& o2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ut, const Date& d);
//这样就可以在类外面访问类里面的私有成员了

//Date.cpp
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;	
	return out;
}
//本段代码的思路总结:
//写多少个operator就是多少个全局函数调用,
//全局函数里面需要调用类里面的私有成员变量,
//因此我们就是用了友元函数声明

8.2 流提取运算符重载

//流提取运算符重载
//Date.h
//在类里面加入friend,友元函数声明
friend istream& operator>>(istream& in, Date& d);//d不加const:流对象不支持拷贝构造
//在类外面
istream& operator>>(istream& in, Date& d);

//Date.cpp
istream& operator>>(istream& in, Date& d)
{
    //改进:检查是否为非法日期
    while(1)//进入循环,先检查日期是否非法,若非法,则重新输入
    {
        cout << "请依次输入年月日:";//流提取里面使用cout这样是不会乱的
    	in >> d._year >> d._month >> d._day;//不用cin,避免流错乱        
         if(!d.CheckDate())
         {
             cout<<"输入日期非法:";
             d.Print();
             cout<<"请重新输入!!!"<<endl;
         }   
        else
         {
             break;
         }
    }
    return in;
}

在此之前,我们就知道cincout是有绑定关系的,因为C++兼容C语言,缓冲区就会保持同步,所以在进行刷新的时候会把printf的缓冲区进行刷新,coutprintf多次混用就不会乱。所以:如果printf没有进行刷新,那么cout就在刷新自己之前先刷新printf

cincout也是同理,在cin进行I/O操作之前会将cout的缓冲区进行刷新。

但是刷新缓冲区会牺牲效率,刷新缓冲区也会有一定的要求,例如遇到换行符会主动进行刷新或者程序结束时会刷新。

如果在IO需求比较高的地方,就可以加入三行代码提高C++的IO效率。

#include <iostream>
using namespace std;

int main()
{
	// 在IO需求比较高的地方,比如部分大量输入的竞赛题中,
    // 加上以下的三行代码,就可以提高C++的IO效率。
	ios_base::sync_with_stdio(false);//关闭C语言的环境
	cin.tie(nullptr);//原本默认cin是与cout进行绑定的,nullptr使得cin和谁都不进行绑定
	cout.tie(nullptr);//cout也与谁都不你行绑定了

	return 0;
}

九、所有代码

Date.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
 
class Date
{
    //友元函数声明
    friend ostream& operator<<(ostream& out, coonst Date& d);
    friend istream& operator>>(istream& in, Date& d);

public:
    bool CheckDate() const;
    void Print() const;

    //默认是inline
	//获取具体某一年某一月的天数
	int GetMonthDay(int year, int month) const
	{
		assert(month > 0 && month < 13);
        // 因为该函数会经常调用,但是数组的值一直是不需要变化的,因此可以使用静态数组
		// 好处是在静态区只会创建一份变量
		static int GetMonthDayArray[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)))
		//先判断是否为2月
		{
			return 29;//闰年
		}
		return GetMonthDayArray[month];
	}
	// 构造函数
    //Date(int year = 1900, int month = 1,int day = 1);
	Date(int year, int month, int day);
	// 拷贝构造函数
	// d2(d1)
	Date(const Date& d);
	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)

    //比较大小可以加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=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day) const;

	// 日期-天数
	Date operator-(int day) const;
	// 日期-=天数
	Date& operator-=(int day);

    //不加const,因为需要成员变量本身的改变
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
 
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();
 
	// 日期-日期 返回天数
	int operator-(const Date& d) const;
private:
	int _year;
	int _month;
	int _day;
};
//只要不修改成员变量的都建议加上const
713日讲了

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"

bool Date::CheckDate() const
{
    if(_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<<"非法日期:";
        Print();
    }
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
 
	return *this;
}
 
// >运算符重载
bool Date::operator>(const Date& d) const
{
	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) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}
// >=运算符重载
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);
}
// !=运算符重载
bool Date::operator != (const Date& d) const
{
	return !(*this == d);
}
 
 
// 日期+=天数
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}
// 日期+天数 ---使用前面实现的+=运算符重载实现
//Date Date::operator+(int day) const
//{
//	Date temp(*this);
//	temp += day;
//	return temp;
//}
 
// 日期+天数 ---直接实现
Date Date::operator+(int day) const
{
	Date temp(*this);
	temp._day += day;
	while (temp._day > GetMonthDay(_year, _month))
	{
		temp._day -= GetMonthDay(temp._year, temp._month);
		++temp._month;
		if (temp._month == 13)
		{
			++temp._year;
			temp._month = 1;
		}
	}
	return temp;
}
// 日期-=天数
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) const
{
	//Date temp(*this);
	Date temp(*this);
	temp -= day;
 
	return temp;
}
 
 //日期-天数 ---直接实现
//Date Date::operator-(int day) const
//{
//	//Date temp(*this);
//	Date temp(*this);
//	temp._day -= day;
//	while (temp._day <= 0)
//	{
//		--temp._month;
//		if (temp._month == 0)
//		{
//			--temp._year;
//			temp._month = 12;
//		}
//		temp._day += GetMonthDay(temp._year, temp._month);
//	}
//	return temp;
//}
 
// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
// 后置++
Date Date::operator++(int)
{
	Date temp(*this);
	*this += 1;
	return temp;
}
// 后置--
Date Date::operator--(int)
{
	Date temp(*this);
	*this -= 1;
	return temp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
 
 
// 日期-日期 返回天数
int Date::operator-(const Date& d) const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		flag = -1;
		max = d;
		min = *this;
	}
	int n = 0;
	while (min != max)
	{
		min++;
		n++;
	}
	return n * flag;
}

喜欢的uu记得三连支持一下哦!

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值