C++从入门到入土(四)--日期类的实现

目录

前言

日期类的实现

日期的获取

日期的比较

const成员函数

日期的加减

日期的加等

日期的减等

日期的加减

日期的加加减减

日期的相减

流插入和提取的重载

友元

友元的特点

日期类代码

总结


前言

前面我们介绍了C++中类和对象的相关知识和六个默认成员函数,在此基础上我们可以用C++实现一个日期类,这样可以帮助我们更加深入理解C++中的知识点,如果文章中有不懂的可以参考之前的文章

C++从入门到入土(三)--6个默认成员函数

C++从入门到入土(二)------初步认识类与对象

日期类的实现

我们在创建一个项目之前首先要知道这个项目要完成什么功能,以日期类为例,我们要实现日期的比较,日期的加减,以及日期的获取等功能,所以根据之前学的运算符重载等方面的知识我们可以很轻松地在Date.h文件中声明这个类所要实现的各种功能,如下所示

cpp 复制代码
#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 = 1900, int month = 1, int day = 1);
	int GetMonthDay(int year, int month)
	{
		int GetMonthDayArry[13] = { 0,31,30,31,30,31,30,31,31,30,31,30,31 };
		if (2 == month && (year % 4 == 0 && year % 100 != 0 )|| year % 400 == 0)
		{
			return 29;
		}
		else
		{
			return GetMonthDayArry[month];
		}
	}
	bool CheckDate();
	void Print()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+=(int day);
	Date operator+(int day)const;
	Date& operator-=(int day);

	int operator-(const Date& d)const;

	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

下面我将逐一分析实现相关的功能。

日期的获取

我们创建好一个日期类后,首先要获取里面的日期,于是我们创建了一个GetMonthDay的成员函数,可以看到,它的返回值是int,在函数里面我们创建了一个名为GetMonthDayArry的数组,这个数组里面存放了每个月的天数,为了方便通过传月份来获取日期我们给这个数组的数据个数定为13,但是我们此时面临一个问题:遇到闰年的2月该怎么办呢?那么此时我们就要特殊情况特殊处理,闰年的判断规则是:四年一润,百年不润,四百年再润,于是我们便可以轻松写下如下代码:

cpp 复制代码
int GetMonthDay(int year, int month)
{
	int GetMonthDayArry[13] = { 0,31,30,31,30,31,30,31,31,30,31,30,31 };
	if (2 == month && (year % 4 == 0 && year % 100 != 0 )|| year % 400 == 0)
	{
		return 29;
	}
	else
	{
		return GetMonthDayArry[month];
	}
}

日期的比较

通过运算符重载的相关知识我们知道,作为类的成员函数重载时,有一个隐藏的this指针,因此我们只需要显示传一个参数,因此我们就有了以下定义:

cpp 复制代码
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;

由于C++的语法,我们在实现上述逻辑时只需要实现其中的两个,其他的直接复用就可以轻松完成以上任务,我们在这里只实现小于和等于两个逻辑,其余直接复用即可,请参考以下代码:

cpp 复制代码
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 (*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);
}

const成员函数

我们发现,在以上几个日期的比较函数的后面我们都加上了const,这是为什么呢? 实际上我们将成员函数后面加const的函数称为const成员函数,从表面上看const是在修饰类的成员函数,实际上是在修饰该成员函数隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改,即:只要不修改成员的就可以在后面加上const

日期的加减

日期的加等

我们首先来介绍日期的加等,日期的加等原则就是进位法,即:将天数全加上去,如果超过该月的天数就将该月的天数减去,此时月数进一,如果超过12个月那么年数进一,于是我们就有了以下代码:

cpp 复制代码
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;
}

注意:我们看到,此时我们是用引用返回 ,在前面我们知道,返回对象生命周期到了会析构我们就传值返回;返回对象生命周期到了不会析构我们就传引用返回。而此时我们要获取的是加完之后的值,因此我们要传引用返回

日期的减等

日期减等的逻辑与加等类似,即:将天数全部减去后,如果天数小于0那么就将前一个月的天数加上去,如果月份为0那么就置为12,此时年份退一,于是我们便有了以下代码:

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

日期的加减

下面我将来实现日期的加减,这里的加减指:不改变原有日期,在加或减相关日期后,返回我们所需要的日期,此时由于出了作用域要析构,所以是用传值返回,同时由于我们前面实现了加等与减等,在这里我们只需要复用上述逻辑即可轻松完成,代码如下:

cpp 复制代码
Date Date::operator+(int day)const
{
	Date tmp = *this;
	tmp += day;
	return tmp;
}
Date Date::operator-(int day)const
{
	Date tmp = *this;
	tmp -= day;
	return tmp;
}

日期的加加减减

由于函数重载的规则,我们在重载++或--操作符时,为了区分是前置还是后置,C++规定:后置++或后置--多了一个int的操作数加以区分。由于前置++或前置--返回的是++或--之后的值,因此我们要传引用返回;而后置则返回之前的值,所以传值返回,于是我们可以写下以下代码:

cpp 复制代码
//前置++
Date& Date::operator++()
{
	return *this += 1;
}
//后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	return tmp += 1;
}
//前置--
Date& Date::operator--()
{
	return *this -= 1;
}
//后置--
Date Date::operator--(int)
{
	Date tmp = *this;
	return tmp -= 1;
}

日期的相减

日期的相减返回的是两日期之前的差值,所以用int返回,我们可以写下以下代码:

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

至此,一个完整的日期类就呈现了出来。

流插入和提取的重载

但是我们在Date.h文件中还有以下两个没有实现,那这两个是什么东西呢?

cpp 复制代码
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

实际上,这是流插入和流提取操作符的重载,那么为什么要用流插入和流提取呢?实际上是因为:C++中的流插入和提取存在缺陷。我们发现与C语言中的printf和scanf不同的是,cout和cin可以自动识别变量的类型,但这仅仅只能识别内置类型,自定义类型的输入和输出时不能直接使用cout和cin,那么我们该怎么解决这个问题呢?于是C++语法上支持了<<和>>的重载。但是为什么我们选择将其定义在类的外面呢?实际上是为了可读性所采取的操作,但是我们将其定义在类外无法访问私有,为了解决这个问题我们引入了友元这个概念。

友元

什么是友元呢?友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜过多使用。

我们还是回归上述问题,为什么将其重载为成员函数会降低可读性呢?我们现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对 象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用 中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办 法访问成员,此时就需要友元来解决。operator>>同理。

友元的特点

1.友元函数可访问类的私有和保护成员,但不是类的成员函数 友元函数不能用const修饰

2.友元函数可以在类定义的任何地方声明,不受类访问限定符限制

3.一个函数可以是多个类的友元函数

4.友元函数的调用与普通函数的调用原理相同

在有了上述知识的铺垫后,我们可以将流插入流提取操作符的重载函数写出,代码如下:

cpp 复制代码
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:";
	in >> d._year >> d._month >> d._day;

	return in;
}

日期类代码

整个Date.c的代码如下:

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;
	}
}
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)
	{
		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 (*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)
{
	_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)const
{
	Date tmp = *this;
	tmp += day;
	return tmp;
}
Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		_day += GetMonthDay(_year, _month);
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}
	}
	return *this;
}

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

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

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:";
	in >> d._year >> d._month >> d._day;

	return in;
}

总结

感谢您能在百忙之中抽出时间来看鄙人的文章,相信看完文章的你会对C++的类与对象有一个更清楚的认识,受限于本人的水平,可能文章中会存在一些问题,欢迎各位指正,如果本篇文章对您有所帮助的话,希望能给我点赞评论加关注,您的支持就是对我创作的最大鼓励。

相关推荐
Qter_Sean22 分钟前
自己动手写Qt Creator插件
开发语言·qt
何曾参静谧26 分钟前
「QT」文件类 之 QIODevice 输入输出设备类
开发语言·qt
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
小白学大数据3 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
冰芒猓4 小时前
SpringMVC数据校验、数据格式化处理、国际化设置
开发语言·maven
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
杜杜的man5 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
java小吕布5 小时前
Java中Properties的使用详解
java·开发语言·后端
versatile_zpc6 小时前
C++初阶:类和对象(上)
开发语言·c++