日期类的实现

文章目录

  • 前言
  • 构造、析构、拷贝构造和赋值重载的实现
  • 获得某个月的天数
  • 重载<、<=、>、>=、==和!=
  • 重载+=、+、-=和-
  • 重载前置++、后置++、前置--和后置--
  • 日期类相减的实现
  • 补充:重载<<和>>

前言

有了前面学到的类和对象-1、类和对象-2、类和对象-3的知识,我们就可以尝试写一个功能较为完善的日期类。
首先先给出日期类的声明:

class Date

{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 拷贝构造函数
  // d2(d1)
	Date(const Date& d);
	// 赋值运算符重载
  // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);
	// 析构函数
	~Date();
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();
	// >运算符重载
	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);
	// 日期-日期 返回天数
	int operator-(const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

构造、析构、拷贝构造和赋值重载的实现

我们这里的构造函数为全缺省构造,所以要手动实现一下。当然这并不困难,甚至对于依然身经百战的我们来说这简直就是轻而易举的:

Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

然后由于我们的日期类的属性都是内置类型,所以没必要手动实现析构、拷贝构造和赋值重载了。

~Date() = default;
Date(const Date& d) = default;
Date& operator=(const Date& d) = default;

获得某个月的天数

由于每个月的天数参差不齐和闰年的存在,手码一个函数来或者某个月的天数还是挺有必要的:

int Date::GetMonthDay(int year, int month)
{
	static int arr[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)))
	{
		return 29;
	}
	return arr[month - 1];
}

重载<、<=、>、>=、==和!=

首先我们先写一个<和==的重载函数:

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

这样比起自己手码实现是不是方便了很多呢?当然剩下部分函数的实现也会用到类似的方法哦!

重载+=、+、-=和-

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)
{
	Date tmp = *this;
	tmp = tmp += day;
	return tmp;
}

注意到:

  • +=的返回值是原类的引用,这是方便我们可以连续赋值并且提高效率。
  • +的返回值不能是引用,因为tmp在出函数时会被销毁,如果返回引用那就是经典的野引用。此外,本身+后的返回值就是一个临时变量哦。
  • 我在实现+的实现调用了+=的实现。那么思考一个问题,可不可以实现+然后再实现+=呢?

当然是可以的,但是不支持这样实现。因为重载+的返回值是一个临时变量,如果+=是利用+来实现的又会生成一个临时变量。而如果我们先实现+=的重载就不需要生成临时变量。因此这种实现方式效率较低
有了前面的逻辑,那么后面我们实现-=和-就简单了:

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

重载前置++、后置++、前置–和后置–

在重载前置++和后置++的时候,我们不得不思考一个问题:前置++和后置++的符号、参数都相同所以不能构成函数重载,那要怎样才能区分他们呢?
事实上按照前面的语法这确实区分不了,因此c++规定后置++的参数要多一个int。由此就能区分他们了:

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

由此,我们又需要注意几个问题:

  • 前置++返回的是引用,因此可以连续调用。
  • 后置++返回的是一个临时变量因此不可以连续调用,而且后置++本质上被调用时原类已经被++了。
  • 前置++和后置++的显式调用不同:
Date d1;
d1.operator++();//前置++
d1.operator++(0);//后置++
  • 此外,后置++的实现需要生成一个临时变量。因此,在前置++和后置++都可以的情况下,建议使用前置++

那么接下来我们继续实现前置–和后置–:

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

日期类相减的实现

两个日期类相减并返回其天数。
有了我们前面的基础,这个代码并不难实现:

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

需要注意的小细节有,我们判定循环条件不是max>min而是max!=min。
这是因为!=的实现逻辑更简单,效率也更高。

补充:重载<<和>>

如果我们想要打印日期类,用如下方式是不可行的:

Date d1;
cout<<d1<<endl;

因为cout不能打印自定义类型,那么我们就可以重载<<来实现cout打印自定义类型。
首先要知道cout是ostream的一个类,因此实现如下:

ostream& Date::operator<<(ostream& out)
	{
		out << _year << '/' << _month << '/' << _day << endl;
		return out;
	}
  • 注意返回ostream&,这样才能连续打印。

上面的实现好像没什么大问题,不过嘛。
咳咳,由于this指针的存在,操作符重载默作为成员函数时,第一个参数默认为this,并且不可修改。所以用起来就像这样:

Date d1(2024,3,21);
d1<<cout;

好家伙,本来应该时cout< 解决方案嘛,也很简单,就是把操作符重载写成全局函数就好啦。

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << '/' << d._month << '/' << d._day << endl;
	return out;
}

不过嘛,这样又有一个问题:类的属性一般是私有的,所以我们访问不了。这时候我们就可以通过友元来解决这个问题。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	friend ostream& operator<<(ostream& out, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

这样我们就能cout自定义类型啦!

int main()
{
	Date d1(2024, 3, 21);
	Date d2(2024, 8, 1);
	cout << d1 << d2 << endl;
	return 0;
}

日期类的实现_第1张图片
那么>>的重载也是一样的:

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

那么以上就是本篇文章的全部内容,希望对读者复习巩固类和对象有所帮助。

你可能感兴趣的:(c++,c++)