08.5【C++ 初阶】实现一个相对完整的日期类--附带源码

文章目录


前言

本文是对之前学习知识的小小总结实践。


分析

功能分析:

  • 日期类简单的记录我们的日期时间,包括年、月、日
  • 支持日期类对象之间的拷贝赋值
  • 两日期类对象相减得到天数差
  • 日期类对象减特定天数得到新日期
  • 日期类对象支持cout打印
  • 日期类对象支持cin输入赋值
  • 日期类对象之间的各种相互比较的运算符
  • 日期类对象的自增、自减
  • 获得日期类对象某年某月的有多少天
  • 简单打印日志类时间
cpp 复制代码
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);
	//其他的一些重载,TODO..

	//简单打印日期类时间
	void Print() const;

	//日期类之间比较的运算符
	bool operator<(const Date& x) const;
	bool operator==(const Date& x) const;
	bool operator<=(const Date& x) const;
	bool operator>(const Date& x) const;
	bool operator>=(const Date& x) const;
	bool operator!=(const Date& x) const;
	int GetMonthDay(int year, int month);
	
	// 日期类加天数运算符
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	
	//日期类自增自减
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
	
	//日期类减日期类的天数差
	int operator-(const Date& d) const;
private:
	int _year;
	int _month;
	int _day;
};

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

我们实现的是一个简单的日期类,它的不涉及资源的管理,所以一些资源拷贝、复制、销毁的关键成员函数,像:析构、拷贝、赋值重载,我们都无需手动实现,而是交给编译器自动生成,自动生成的浅拷贝已经可以满足。

我们需要手动写的,就是特定的初始化方式,即构造函数,还有一些日期之间的常见运算对应的运算符重载。我们就采用类声明定义分离的方式去实现。


Date.h

cpp 复制代码
#include<iostream>
#include<cassert>
using std::ostream;
using std::istream;
using std::cout;
using std::cin;
using std::endl;

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);
	//其他的一些重载,TODO..

	//简单打印日期类时间
	void Print() const;

	//日期类之间比较的运算符
	bool operator<(const Date& x) const;
	bool operator==(const Date& x) const;
	bool operator<=(const Date& x) const;
	bool operator>(const Date& x) const;
	bool operator>=(const Date& x) const;
	bool operator!=(const Date& x) const;
	int GetMonthDay(int year, int month);

	// 日期类加天数运算符
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;

	//日期类自增自减
	Date& operator++();
	Date operator++(int);
	Date& operator--();
	Date operator--(int);

	//日期类减日期类的天数差
	int operator-(const Date& d) const;   //日期减日期也并不会修改对象本身的值,所以加上const禁止函数内部修改本身的值.
private:
	int _year;
	int _month;
	int _day;
};

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

Date.cpp

cpp 复制代码
#include"Date.h"

//实现一下构造出对象需求的基本成员函数.
Date::Date(int year, int month, int day)
{
	//首先我们需要判断日期的合法性.也因此,我们并不将赋初值的操作放在初始化列表.

	if ((month > 0 && month <= 12) && (day > 0 && day <= GetMonthDay(year, month)))   //根据不同年份,2月的天数略有差距,所以写一个函数,更好的判断我们的日期天数,当给的数值合法,我们才正常的构造.
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期:" << "year=" << year << ", month=" << month << ", day=" << day << endl;
		assert(false);
	}
}

int Date::GetMonthDay(int year, int month)
{
	int DayArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = DayArr[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))  //如果是2月并且是闰年,那么将天数加一.
	{
		day++;
	}
	return day;
}

//早早写出打印的函数,可以阶段性的测试我们的代码正确性.
void Date::Print() const
{
	cout << "该对象数据:" << "_year=" << _year << ", _month=" << _month << ", _day=" << _day << endl;
}

//2.然后先实现operator==和operator<,为什么?---后面的运算符重载可以复用逻辑.
bool Date::operator==(const Date& x) const      //首先这个函数逻辑的内部不可以去改动内部的变量,其次我们的传入用作比较的Date对象也不可以被改动,所以均是const的.
{
	//if (x._day == _day && x._month == _month && x._year == _year)
	//{
	//	return true;
	//}
	//return false;
	return x._day == _day && x._month == _month && x._year == _year;
}

bool Date::operator<(const Date& x) const      //同样,这个函数逻辑的内部不可以去改动内部的变量,其次我们的传入用作比较的Date对象也不可以被改动,所以均是const的.
{
	if (_year < x._year)
	{
		return true;
	}
	else if(_year == x._year && _month < x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _day < x._day)
	{
		return true;
	}
	return false;
}


//3.然后复用已经实现的逻辑,去实现其他的一些运算符重载.
bool Date::operator<=(const Date& x) const
{
	//if (*this < x || *this == x) return true;
	//return false;
	return *this < x || *this == x;
}

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

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

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

//4.然后实现日期类加天数的运算符重载函数
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) const
{
	Date tmp(*this);     //这里是用拷贝构造去创建出一个临时的对象,至于为什么我们没有实现还有拷贝构造,因为浅拷贝使用编译器自动生成的就完全够用.

	tmp += day;
	return tmp;      //这里又为什么要重新构造一个临时变量去返回?  --  因为+本来就是创造一个临时变量,像1 + 1,如果我们不用一个变量去接住它,那么答案2我们是拿不到的,
	//这种运算符重载的灵活体现,更加加深了我们类的抽象程度,使得其更加贴近内置类型.
}

Date& Date::operator-=(int day)
{
	if (day < 0)    //同样如果减等于一个负数,相当与加该负数的绝对值.
	{
		return *this += (-day);
	}
	_day -= day;
	while(_day <= 0)
	{
		_month--;
		//_day += GetMonthDay(_year, _month);           //放在这里是错的,因为有可能减到0,减到0,访问的日期就是0.

		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);

	}
	return *this;
}

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

	tmp -= day;
	return tmp;
}


//5.然后实现一些自增自减运算符.
Date& Date::operator++()
{
	*this += 1;
	return *this;       //	前置++表达式的返回值,是++之后的值,所以返回的是对象自己(引用).
}
Date Date::operator++(int) // 后置++,这个 int参数只用于编译器区分类型,不用于任何实际操作。
// 在函数内部,你不需要使用这个参数(通常省略其名字以避免未使用参数的警告)。编译器在遇到 obj++时,会自动传递一个 0(或其他任意整数值)给这个虚拟参数。
{
	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;
}


//6.然后实现日期类减日期类的天数差
int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (max != min)
	{
		n++;
		min++;
	}
	return n * flag;
}


//7.让我们的类支持<<和>>.
ostream& operator<<(ostream& out, const Date& d)
{
	out << "该对象数据:" << "_year=" << d._year << ", _month=" << d._month << ", _day=" << d._day << endl;
	return out;         //为什么重载之后还需要返回out呢?因为有这种使用场景:  cout << d << "你好兄弟";   就相当于: (cout << d) << "你好兄弟";
}
istream& operator>>(istream& in, Date& d)
{
	int year, month, day;
	in >> year >> month >> day;
	if ((month > 0 && month <= 12) && (day > 0 && day <= d.GetMonthDay(year, month)))   //根据不同年份,2月的天数略有差距,所以写一个函数,更好的判断我们的日期天数,当给的数值合法,我们才正常的构造.
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
	{
		cout << "非法日期:" << "year=" << year << ", month=" << month << ", day=" << day << endl;
		assert(false);
	}
	return in;
}

一些测试代码:(main.cpp)

cpp 复制代码
#include"Date.h"

//测试构造函数的逻辑正确性
void test()
{
	Date d(1901, 12, 1);
	//Date d1(1901, 2, 29);
	Date d2(2000, 2, 29);
	Date d3(2000, 2, 29);

	d.Print();
	//d1.Print();
	d2.Print();

}

//测试operator==和operator<
void test1()
{
	Date d(1901, 12, 1);
	//Date d1(1901, 2, 29);
	Date d2(2000, 2, 29);
	Date d3(2000, 2, 29);

	cout << (d2 == d3);
	cout << (d2 < d3);
	cout << (d < d3);
}

//测试其他运算符重载函数
void test2()
{
	Date d(1901, 12, 1);
	//Date d1(1901, 2, 29);
	Date d2(2000, 2, 29);
	Date d3(2000, 2, 29);

	cout << (d2 < d3);
	cout << (d2 <= d3);
	cout << (d > d3);
	cout << (d >= d3);
	cout << (d != d3);
}

//测试我们日期类加天数的运算符重载函数
void test3()
{
	Date d(1901, 12, 1);
	d.Print();

	Date d1 = d + 100;
	d1.Print();

	Date d2 = d - 100;
	d2.Print();

	d.Print();

	d += 666;
	d.Print();

	d -= 666;
	d.Print();

	//d -= 654;   //测试_year的逻辑对不对.
	//d.Print();

	//d -= 654;
	//d.Print();

	//d -= 654;
	//d.Print();
}

//测试自增自减的功能
void test4()
{
	Date d(1901, 12, 1);
	d.Print();

	Date d1;
	d1.Print();

	d1 = d++;
	d1.Print();

	d1 = d--;
	d1.Print();

	d1 = ++d;
	d1.Print();

	d1 = --d;
	d1.Print();
}

//测试日期类减日期类的天数差的功能.
void test5()
{
	Date d(1901, 12, 1);
	Date d1(2000, 2, 29);

	cout << d1 - d << endl;
	cout << d - d1 << endl;
}

//测试我们类适配的cin和cout.
void test6()
{
	Date d(1901, 12, 1);

	cout << d;
	cin >> d;
	cout << d;
	//该对象数据:_year=1901, _month=12, _day=1
	//2000 12 25
	//该对象数据:_year = 2000, _month = 12, _day = 25
}

int main()
{
	test6();
	return 0;
}

全文总结

实践简单类,知行合一有益身体健康。


本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点:)。

相关推荐
莫听穿林打叶声儿3 分钟前
Qt中使用QString显示平方符号(如²)
c++·qt
大锦终8 分钟前
【Linux】文件系统
linux·服务器·c++
金融小师妹9 分钟前
AI多因子模型解析:黄金涨势受阻与美联储9月降息政策预期重构
大数据·人工智能·算法
重启的码农1 小时前
llama.cpp 分布式推理介绍(2) 后端注册机制 (Backend Registration)
c++·人工智能·神经网络
NAGNIP1 小时前
LLaMA 3:离 AGI 更近一步?
算法
shanql1 小时前
Vim笔记:缩进
vim·缩进
重启的码农1 小时前
llama.cpp 分布式推理介绍(1) 远程计算设备 (RPC Device)
c++·人工智能·神经网络
hllqkbb1 小时前
从 SGD 到梯度累积:Epoch、Batch、Step 的关系全解析
开发语言·人工智能·opencv·计算机视觉·batch
PineappleCoder1 小时前
力扣【2348. 全0子数组的数目】——从暴力到最优的思考过程
前端·javascript·算法
风吹落叶32572 小时前
深入解析JVM内存管理与垃圾回收机制
java·开发语言·jvm