日期类(C++)


  • 点击表格内对应链接跳转对应内容⬇️⬇️⬇️
作者主页 吃透C语言专栏 数据结构 Gitee仓库 C++ Linux 算法

文章目录

  • 一,Date.h
  • 二,Date.cpp
  • 三,解析
    • [1 构造函数和打印函数](#1 构造函数和打印函数)
    • [2 类比较大小](#2 类比较大小)
    • [3 `+=` 运算符重载函数](#3 += 运算符重载函数)
      • [`+` 运算符重载函数](#+ 运算符重载函数)
    • [4 `-=`运算符重载函数](#4 -=运算符重载函数)
    • [5 前置++和后置++运算符重载函数](#5 前置++和后置++运算符重载函数)
    • [6 前置 -- 和后置 -- 运算符重载函数](#6 前置 -- 和后置 -- 运算符重载函数)
    • [7 日期减日期](#7 日期减日期)
    • [8 GetMonthDay](#8 GetMonthDay)
    • [9 重载 流插入<< 和 流提取>>](#9 重载 流插入<< 和 流提取>>)

一,Date.h

c 复制代码
#include<iostream>
#include<assert.h>

class Date
{
	//全局函数声明友元才能访问成员变量
	friend std::ostream& operator<<(std::ostream& out, const Date& d);
	friend std::istream& operator>>(std::istream& in, Date& d);
public:
	Date(int year = 1970, int month = 1, int day = 1);//构造函数

	void Print();//打印

	//直接定义类里面,默认是inline,频繁调用,设置为内联
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
	    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;
		}
		else
		{
			return monthDayArray[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);

	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);

	int operator-(const Date& date);



private:
	int _year;
	int _month;
	int _day;
};

//运算符重载函数,函数重载
std::ostream& operator<<(std::ostream& out, const Date& d);
std::istream& operator>>(std::istream& in, Date& d);

二,Date.cpp

c 复制代码
#include"Date.h"
bool Date::CheckDate()
{
    if(_month >12 || _month<1 && 
		_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)
	{
		std::cout << "日期错误" << std::endl;
	}
}
void Date::Print()
{
	std::cout << _year << " " << _month << " " << _day << std::endl;
}

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)
		{
			return _day < d._day;
		}
	}

	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);
}

bool Date::operator!=(const Date& d)
{
	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)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}

Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp += day;

	return tmp;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

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

	return tmp;
}

Date& Date::operator++()
{
     return *this += 1;
}

Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

Date& Date::operator--()
{
	return *this -= 1;
}

Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	
	return tmp;
}

int Date::operator-(const Date& date)
{
	Date max = *this;
	Date min = date;
	int flag = 1;
	if (*this < date)
	{
		max = date;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (max != min)
	{
		++min;
		++n;
	}

	return flag * n;
}

std::ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
	return out;
}

std::istream& operator>>(std::istream& in, Date& d)
{
	if (!d.CheckDate())
	{
		std::cout << "日期错误" << std::endl;
	}
	in >> d._year >> d._month >> d._day;
	return in;
}

三,解析

1 构造函数和打印函数

c 复制代码
.h文件
Date(int year = 1970, int month = 1, int day = 1);//构造函数
void Print();//打印

.c文件
Date::Date(int year, int month, int day )
{
	_year = year;
	_month = month;
	_day = day;

	if (!CheckDate)
	{
		std::cout << "日期错误" << std::endl;
	}
}
void Date::Print()
{
	std::cout << _year << " " << _month << " " << _day << std::endl;
}
  • ❗❗❗唯一要注意的地方就是全缺省函数声明和定义分离的时候,缺省值只能写在声明处

2 类比较大小

c 复制代码
  .h文件
  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);
  
  .c文件
  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)
		{
			return _day < d._day;
		}
	}

	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);
}

bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}
  

  • ❗❗❗类比较大小应用的是运算符重载函数,而> >= < <= == != 六个运算符中,只需要写出> 和 == 或者 < 和==`,因为其他的运算符重载函数内部通过这两个运算符重载函数组合,都可以实现

<运算符重载函数

c 复制代码
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)
		{
			return _day < d._day;
		}
	}

	return false;

}
  • 两个操作数,第一个操作数就是调用成员函数的对象,形参是隐藏的this,第二个操作数就是传进来的参数,if else 判断的逻辑是,如果_year小则小,则返回true,否则,如果_year相等的情况下,如果_month小则小,则返回true,最后如果在_month相等的情况下,直接返回_day比较的结果,因为最后只剩下_day了,在前面都相等的前提下_day的结果就是整体的结果,但是前面的不能直接返回比较结果,要一个个判断,比如不能直接return _month < _month 因为_month后面还有_day,如果_month 相等的情况下还要比较_day,而return 会直接renturn false就不会去考虑_day了,最后_year 和 _month 判断的都是返回ture的情况,如果不符合判断条件则直接跳出if else 去 return false

==运算符重载函数

c 复制代码
bool Date::operator==(const Date& d)
{
	return _year == d._year 
		&& _month == d._month 
		&& _day == d._day;
}
  • 全等的话则必须_year 和 _month 和 _day 都要同时全等

> 和 == 组合成其他运算符重载函数

c 复制代码
<=
return *this < d || *this == d;
>
return !(*this <= d);
>=
return !(*this < d);
!=
return !(*this == d);
  • 其余的运算符重载函数都可以通过调用>或者==运算符重载函数组合实现

3 += 运算符重载函数

c 复制代码
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)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
  • +=和+的区别就是+=会改变操作数的大小,而+中的操作数只是用来运算,日期类+法的逻辑是先把需要+的天数加上,然后在while循环中不断将对应_month中对应的_day转化成_month进位,_month满12进位到_year,然后_month重置为1即可,GetMonthDay函数可以通过_year和_month计算出_month对应的天数,所以只要_day不匹配当前的_month的_day也就是>就一直循环执行-对应的_day进位到_month然后判断_month是否要进位到_year并重置_month,最后return对象*this

  • if (day < 0)判断如果输入负数的情况,+ -1等价于- 1所以直接return *this -= (-day)即可-1中-等价于-=而1等价于-day

+ 运算符重载函数

c 复制代码
Date Date::operator+(int day)
{
	Date tmp = *this;
	tmp += day;

	return tmp;
}
  • +的逻辑就是不改变操作数的大小,但是结果要是改变好的,我们做法是把调用成员的实例化对象拷贝到Dare tmp 然后用tmp调用+=运算符重载,最后return tmp

4 -=运算符重载函数

c 复制代码
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}
	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
  • -=的逻辑是借位,先直接-掉day得到_day,while循环判断_day是否<= 0,如果<= 0则进入while循环中循环执行_month-1然后直接判断_month是否== 0,如果_month == 0则需要借位重置为12,然后_year --,这样的话就可以最开始的_month--通过if语句判断过后就可以得到正确的月份,然后将这个_month的_day全部加到当前_day中,最后一直循环这些步骤,最后直到_day为正符合正常_day即可。注意顺序不能乱,因为必须通过if判断才能得到正确的_month得到正确的_month才能得到正确的_day

  • if(day<0)的话,-(-1) == + 1所以直接调用+= (-day) 其中+=等于+,1等于(-day)

-运算符重载函数

c 复制代码
Date Date::operator-(int day)
{
    Date tmp = *this;
	tmp -= day;

	return tmp;
}
  • -运算符和-=的区别在于-运算符不改变操作数,所以先把*this拷贝到Date tmp中,然后Date tmp调用+=运算符重载函数,最后return tmp

5 前置++和后置++运算符重载函数

c 复制代码
Date& operator++();//前置++声明

Date& Date::operator++()//前置++定义
{
     return *this += 1;
}

Date operator++(int);//后置++声明

Date Date::operator++(int)//后置++定义
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}
  • 前置++和 后置++ 是两个功能不同的函数,但是前置++ 和 后置++ 这两个运算符重载函数的函数名是相同的,都是operator++都是单操作数。为了区分这两个功能不同但是名字完全相同的运算符函数,让两个函数构成函数重载,规定了后置++ 函数定义和函数声明强行增加了一个int形参,和前置++运算符重载函数构造函数重载

  • 前置++函数体中直接用*this += 1即可,因为前置++效果是先++然后返回++后的结果。返回值类型可以应用
  • 后置++函数体中需要将*this拷贝到Date tmp中,将*this+=1然后return tmp因为后置++的效果是把当前值返回之后,再进行++

  • d++就是调用后置++,++d就是调用前置++,如果显示调用的话d.operator++()就是调用前置++,d.operator++(1)就是调用后置++

  • 不需要去在意形参int只是为了让前置++和后置++这两个同名的运算符重载函数构造函数重载

6 前置 -- 和后置 -- 运算符重载函数

c 复制代码
Date& operator--();//前置--声明

Date& Date::operator--()//前置--定义
{
	return *this -= 1;
}

Date& operator--(int);//后置--声明

Date Date::operator--(int)//后置--定义
{
	Date tmp = *this;
	*this -= 1;
	
	return tmp;
}
  • 和前置++ 后置++一样的说明和规定和用法

7 日期减日期

c 复制代码
int Date::operator-(const Date& date)
{
	Date max = *this;
	Date min = date;
	int flag = 1;
	if (*this < date)
	{
		max = date;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (max != min)
	{
		++min;
		++n;
	}

	return flag * n;
}
  • 日期 - 日期的主要逻辑是,通过判断找出小的哪个日期,然后在while中设置计算器n在小日期追赶大日期的时候用计算器n计算差值。所以需要先拷贝Date max = *this和Date min = date因为不能改变原本的日期,然后先假设一个max和min,然后用if语句进行筛选,再次进行假设,设置和第一次假设相反的判断条件,如果符合i f的假设就进入if修改max 和 min同时把flag取反,因为我们原本设置的是*this 大于 date也就是第一个操作数大于第二个操作数最后差值为正,但是如果进入if之后则说明*this小于date,第一个操作数小于第二个操作数,差值为-,所以flag取反,最后在while中min追赶max同时计数器n计数,由于计算器n得出来的数一定是正数,所以return flag * n

8 GetMonthDay

c 复制代码
int GetMonthDay(int year, int month)
{
	assert(month > 0 && month < 13);
    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;
	}
	else
	{
		return monthDayArray[month];
	}

}
  • 通过日期类实例化对象的_year和_month来计算对应的天数需要考虑的因素有闰年的2月29天,先设置monthDayArray数组把对应_month对应的天数设置进去,最后用下标引用操作符把month设置为操作数,这样就可以实现对应的month返回对应的天数了。但是我们要单独判断闰年的2月,所以 if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))判断闰年,month == 2需要放在前面,因为只有2月的天数才受到闰年的影响。

  • 类中定义的函数默认是内联,GetMonthDay经常被调用,设置成内联减少开栈帧的消耗,直接在调用处就可以拿到定义了,内联函数声明和定义不能分离到不同的文件中,因为内联函数没有函数栈帧,不进符号表,分离会导致链接错误

  • static修饰变量会让变量到静态区这样就不需要频繁的创建数组

9 重载 流插入<< 和 流提取>>

  • <<流插入和>>流提取本质上是运算符重载函数,而调用这个运算符重载函数的,是全局对象cout和全局对象cin这两个全局对象调用它们类中的成员函数operator<<和operator >>,而这两个全局对象cout和cin是类ostream和类istream实例化的全局对象,流插入和流提取是类中的成员函数,而流插入operator<<()和流提取operator>>()这两个运算符重载函数实现的都是参数为内置类型的函数重载,所以我们使用cout和cin的时候才可以写任意类型,不用想printf和scanf函数一样指定类型,因为写任意类型的时候,会调用对应的运算符重载函数,因为不同参数的流插入和流提取运算符重载函数构成了函数重载

c 复制代码
ostream& ostream::operator<<(const Date& d){}
  • 这种写法无法实现,因为这种写法再一次重载了流插入运算符重载函数,把参数调整为自定义类型,但是如果依然是对类ostream的全局对象cout的成员函数operator<<()进行函数重载的话,就必须把在类ostream中进行实现,只有在类ostream中实现的成员函数,才能由类ostream实例化的对象cout调用,但是这样要修改类ostream,修改源文件是不可能的

  • 错误写法:👇🏻👇🏻👇🏻
c 复制代码
函数声明:
Date& operator<<(ostream& out);

函数定义:
Date& Date::operator<<(ostream& out)
{
   out<<_year<<"年"<<_month<<"月"<<_day<<"日";
   return *this;
}

函数调用:d<<out 或者 d.operator<<(cout)

d.operator<<(cout)).operator<<(x);
  • 这种写法和做法是在Date类中声明定义成员函数operator<<,形参为ostream类实例化对象cout,把cout作为参数传进成员函数中,打印自定义类型Date中的内置类型,因为打印自定义类型的本质就是打印自定义类型中的内置类型,因为自定义类型本质就是由内置类型组成。最后return *this是为了可以重复调用函数,返回值为return *this的话,函数的返回值依然是日期类的实例化对象,调用完第一次运算符重载函数之后,可以继续调用运算符重载函数,因为只要对象才能调用类中的成员函数,而返回值一直都是对象,所以可以连续调用

  • 这种写法不好的地方在于,运算符重载函数使用的时候,调用成员函数的对象要在左边,参数要写在右边,操作数的顺序就是形参的顺序,因为肯定是对象调用成员函数,这样就会导致d << cout的形式,这样的写法不符合我们日常使用cout的习惯

  • 经过上面的种种错误写法,都是将operatro<<运算符重载函数重载为成员函数(成员函数才能访问类中的私有内置类型成员变量),而且本质都是必须将ostream类的实例化对象进行函数体中,因为最终自定义类型中的打印操作还是靠的是cout打印内置类型,因为自定义类型中最终都是由内置类型组成的,所以我们正确的写法是重载成全局函数,但是这样的话我们就无法访问私有内置类型成员变量,由此我们引入了友元的概念

  • 全局函数无法访问类中私有的成员,但是如果把全局函数在类中进行友元声明,就可以让全局函数访问到类中的私有成员变量,语法形式就是friend + 函数声明在类中就是友元声明
c 复制代码
流插入运算符重载函数函数重载形式声明
std::ostream& operator<<(std::ostream& cout,const Date& d);

流插入运算符重载函数函数重载形式定义
std::ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << std::endl;
	return out;
}

流提取运算符重载函数函数重载形式声明
std::istream& operator>>(std::istream& in, Date& d);

流提取运算符重载函数函数重载形式定义
std::istream& operator>>(std::istream& in, Date& d)
{
	if (!d.CheckDate())
	{
		std::cout << "日期错误" << std::endl;
	}
	in >> d._year >> d._month >> d._day;
	return in;
}
  • 运算符重载函数中操作数的左右顺序就是形参的左右顺序,所以cout << d的形式的话,形参左右顺序为ostream& out 和 const Date& d,renturn out让函数可以复用,cout<<d1<<d2从左到右执行运算符重载函数,第一个运算符重载函数return的结果是cout就可以继续和d2构成运算符重载函数的两个形参,而通过友元,就可以访问到类中的私有成员了。

  • 点击表格内对应链接跳转对应内容⬇️⬇️⬇️
作者主页 吃透C语言专栏 数据结构 Gitee仓库 C++ Linux 算法

相关推荐
谢亮_vipxieliang3 小时前
Go map与结构体的正确使用
开发语言·golang·哈希算法
H.莓飛3 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
xxwl5853 小时前
数据结构知识点和代码实现总结(C语言实现)
c语言·开发语言·数据结构
汉克老师4 小时前
GESP2026年9月认证C++八级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
郝学胜_神的一滴5 小时前
C++ Templates 06:搞懂模板代码的三种组织方式
c++·visual studio
C++ 老炮儿的技术栈5 小时前
sizeof操作符
c语言·c++·人工智能·mfc·c
沫璃染墨5 小时前
《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》
linux·c++·安全·设计模式·策略模式
wuminyu6 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++
code2cat6 小时前
Java进阶篇之AtomicMarkableReference:引用还在,标记已经改变
java·开发语言·cas·并发编程·逻辑删除