类与对象实例应用——日期类的实现

引入

此前,我们对日期类已经有了一定的认识,接下来的一节,我们将通过完整的编写一个日期类来巩固之前所学的类与对象的知识并从其中拓展出一些零碎的知识点。

日期类的实现

日期类的内部有年月日三个成员变量,通过分析可得到,日期类的默认成员只需要写构造函数,其他使用系统自动生成的即可。

我们现在头文件中明确需要写的对象及方法:

class Date {
private:
	int _year;
	int _month;
	int _day;
public:
	Date(int year=0, int month=0, int day=0);
	bool operator==(const Date& a) ;
	bool operator<(const Date& a)  ;
	bool operator<=(const Date& a) ;
	bool operator>(const Date& a) ;
	bool operator>=(const Date& a) ;	
    bool operator!=(const Date& a) ;
	int GetMonth_Day(const Date& a) 
	{
		int arry[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (a._month == 2)
		{
			if ( ( ( a._year % 4 == 0 ) && ( a._year % 100 != 0 ) ) || ( a._year % 400 == 0 ) )
			{
				arry[a._month] = 29;
			}
		}
		return arry[a._month];
	}
	bool CheckInvild() ;
	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& d) const;
	friend ostream& operator<<(ostream& out, const Date& a);
	friend istream& operator>>(istream& in, Date& a);
};


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

构造函数

构造函数很简单没什么需要特别说明的

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

关系运算符重载(==、!=、>、>=、<、<=)

operator==

bool Date :: operator==(const Date& a)
{
	return _year == a._year &&
		_month == a._month && 
		_day == a._day;
}

operator!=

由于我们已经写完了判断相等的运算符重载,判断不相等的运算符重载不再需要从逻辑上重新写,直接复用写过的重载函数:

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

}

operator<

判断小于的逻辑有些许复杂

bool Date :: operator<(const Date& a)
{
	if (_year < a._year)//年小直接返回真
		return true;
	else if (_year == a._year)//若年不小,只关心相等的情况(比起年大于更为复杂)
	{
		if (_month < a._month)//以此类推,选出所有返回真的情况
			return true;
		else if (_month == a._month)
		{
			return _day < a._day;
		}
	}
	return false;//排除完所有真的情况,其余全为假。
}

有了<和等于,其他的关系运算符都能通过复用得到,不在详细解析:

operator<=

bool Date:: operator<=(const Date& a)
{
    return *this < a || *this == a;
}

operator>=

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

operator>

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

算术操作符的重载(+,-,+=,-=,前置/后置++、前置/后置--)

operator+

在日期类“+”我们主要用来“+”天数,我们不妨来想想日期加减天数是怎么进行的?

我们发现日期的加法还是比较复杂,因为年份有平年润年区分,平年润年的二月天数也不一样,因此我们不如写一个能获取当年年份所在月份的天数的函数,我们用数组来存储一年十二个月的天数(回忆C语言的写法):

int GetMonth_Day(const Date& a)//将需要获取的年月传过来
	{
		int arry[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//用数组存储十二个月的天数
		if (a._month == 2)//当传过来的月份为2月时进行平润年的判断
		{
			if (((a._year % 4 == 0 )&& (a._year % 100 != 0))||(a._year==0))//按需更换天数
			{
				arry[a._month] = 29;
			}
		}
		return arry[a._month];
	}

这个函数在后续的书写我们都会频繁的调用,我们可以将这个函数写在头文件类的内部(当函数声明与定义都在类内部编译器按内联函数处理,这样避免了链接冲突),下面我们就可以看“+”的逻辑了:

天数超过客观天数向月进1,月份超过12向年进1,年份是无穷大的,同时“+”不会改变对象内部的值,所以要拷贝一份相同的对象——我们学到拷贝构造在这就被使用到了

就算上述括号写成等号仍然是拷贝构造,这里我们可以再次体会一下拷贝构造与赋值重载的区别:他俩最大的区别在于是否创建变量,要创建新变量的就是拷贝构造,因此判断调用某个函数不能凭借符号,而要看到问题的本质。

operator+=

有了+,+=就在简单不过了,+=是对象本身加,因此直接使用this指针即可:

Date& Date :: operator+=(int day)
{
	_day += day;
	while (_day > GetMonth_Day(*this))
	{
		_day -= GetMonth_Day(*this);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

复用实现“+”与“+=”

我们发现“+”与“+=”的逻辑很相似,我们能否复用实现呢?

答案是当然可以:

(1)逻辑实现“+”,“+=”用“+”复用

Date Date::operator+(int day)
{
	Date newDate (*this);
	newDate._day += day;
	while (newDate._day > GetMonth_Day(newDate))
	{
		newDate._day -= GetMonth_Day(newDate);
		newDate._month++;
		if (newDate._month > 12)
		{
			newDate._year++;
			newDate._month = 1;
		}
	}
	return newDate;
}


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

(2)逻辑实现“+=”,“+”用“+=”复用

Date& Date :: operator+=(int day)
{
	_day += day;
	while (_day > GetMonth_Day(*this))
	{
		_day -= GetMonth_Day(*this);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
	
}

Date Date::operator+(int day)
{
	Date newDate = *this;
	newDate += day;
	return newDate;
}

那上面两种方法谁更好呢?我们来对比一下:

日期-天数——operator- 与 operator-=

通过上面的分析,我们实现“-=”,然后通过“-=”复用出“-”。

那“-=”如何实现呢?其实代码逻辑与+=很类似,当天数小于0时,我们向月份借1位,当月份等于0时我们向年借一位。

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

前置/后置++与前置/后置--

前置++与前置--很简单,每次自加(减)返回自己即可。

后置++与后置--则自加(减)并返回自加前的值,我们需要拷贝构造一个新的变量;那这时候我们发现前置++与后置++的函数名有了矛盾的地方,我们不可能两个都写成operator++,这里C++的标准制定者规定,前置++无参,后置++则添加一个int参数与前置++构成重载

//前置++
Date& Date :: operator++()
{
	*this += 1;
	return *this;
}
//后置++
Date Date :: operator++(int d)
{
	Date newDate = *this;
	*this += 1;
	return newDate;
}
//前置--
Date& Date :: operator--()
{
	*this -= 1;
	return *this;
}

//后置--
Date Date :: operator--(int d)
{
	Date newDate = *this;
	*this -= 1;
	return newDate;
}

日期-日期——operator-

在日期类中,对我们的日常生活有意义的减法除了减天数,还有减日期得到两个日期相差的天数,此时我们的参数由int(天数)变为Date(日期),函数重载的意义在这里得到了体现。

那怎么实现日期减日期呢?

我们直接用日期减日期之后换算显得有些死板,我能能否有新的思路呢?——用小的日期循环自加,并计数,当小的日期等于大的日期结束循环,返回计数器即为两个日期的差值

int Date :: operator-( Date& d )
{
	int flag = -1;//代表小减大
	Date min = *this;
	Date max = d;
	if (d < *this)
	{
		flag = 1;//代表大减小
		min = d;
		max = *this;
	}
	int count = 0;//计数器
	while ( min != max )//用!=而不用<的原因是!=逻辑更简单
	{
		++min;
		++count;
	}
	return count * flag;
}

输入输出流重载——引入友元声明

和其他运算符一样,我们打印自定义类定是否可以和内置类型一样不需要访问成员函数直接打印呢??

其实 cout 是 ostream 类型的一个成员,cin 是 instream 类型的一个成员。

我们先从输出流cout入手:

我们发现如果这样书写,我们调用输出流就必须这样:

显然,这样不符合我们的实际想法,那究竟是哪出了问题,没错,别忘了成员函数里还隐藏了排首位的this指针;而双操作数的运算符重载,左右操作数必须符合传参先后的顺序,因此cout的流向反了,我们把cout的重载写成全局函数试试:

我们发现放在全局因为Date类的成员变量为私有不能访问到编译器会报错,这时就不得不引出友元声明,有了友元声明(friend),我们全局的重载函数也能访问内内部的私有成员变量。

但目前为止,我们还有一个问题没有解决:内置类型的变量支持连续打印,因此,还需返回一个变量:

如此以来,便实现了我们想要的结果:

输入流同理:

当然,日期类也有使用者输入日期不合法,例如:输入的明明是平年,二月却有29天;输入的月份大于12,年份小于等于0等情况,己尝试实现一下:我们对构造函数与输入重载进行限定检查即可,此处同学们可自己尝试实现一下:

至此呢,日期类的内容基本已经完成,下面还有一个小的知识点:

const成员

C++语法中const能够修饰成员函数,语法如下:

const修饰成员函数实际上是修饰隐藏的this指针,表明在该成员函数中不能对类的任何成员变量进行修改。那我们上面写的日期类,哪些可以加const修饰呢?

成员函数如果需要修改成员变量则不能加const修饰,若成员函数不会修改成员变量,建议添加const,这样const对象与非const对象都能访问。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值