【哇! C++】类和对象(六) - 日期计算器

目录

​编辑

一、前言

二、日期计算器的功能需求

三、日期计算器的分步实现

3.1 日期有效性确定

 3.2 日期之间的大小比较

3.3 当前日期加减天数

3.3.1 日期加天数

3.3.2 日期减天数

3.3.3 日期前置++

3.3.4 日期后置++

3.4  日期之差

3.5 自定义打印

四、日期计算器的程序

4.1 Test.cpp

4.2 Date.h

4.3 Date.cpp


一、前言

        前边基于对类和对象的认识,本章将以日期计算器为例,通过C++程序一步一步操作实现。如图所示:日期计算器要实现计算日期差推算具体日期两种功能。

二、日期计算器的功能需求

  1. 检查日期是否合理;
  2. 两个日期之间的大小比较;
  3. 实现当前日期加减天数后的日期计算功能;
  4. 实现两个日期之间相差多少天的计算功能;
  5. 自定义打印。

三、日期计算器的分步实现

        假设程序实现在Date类类型中,且private中定义了int _year,int _month,int _day。基于上述需求:我们可以分步骤依次实现:

3.1 日期有效性确定

int GetMonthDay(int _year, int _month)
{
    assert(_month < 0 && _month > 13);

    const int _GetMonthDay[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    
    if(_month == 2)
    {
        if(((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0))
        {
            return 29;
        }
    }

    return _GetMonthDay[_month];
}
bool CheckInvalid()
{
	if (_year <= 0
        || _month < 1 || _month > 12
		|| _day < 1 || _day > GetMonthDay(_year, _month))
	{
		return false;
	}
	else
	{
		return true;
	}
}

 3.2 日期之间的大小比较

        日期之间的大小比较分为一下几种情况:(1)Date1 > Date2;(2)Date1 == Date2;(3)Date1 < Date2。因此,自定义类型需要运算符重载:

bool operator>(const Date& d)
{
    if(_year < d._year)
    {
        return false;
    }

    else if(_year == d._year)
    {
        if(_month < d._month)
        {
            return false;
        }
        else if(_month == d._month)
        {
            if(_day < d._day)
            {
                return flase;
            }
        }
    }

    return true;
}
bool operator<(const Date& d)
{
    return !(*this > d);
}
bool operator==(const Date& d)
{
    return _year == d._day
        && _month == d._month
        && _day == d._day;
}
bool operator!=(const Date& d)
{
    return !(*this == d);
}

 

3.3 当前日期加减天数

3.3.1 日期加天数

Date operator+(int day)
{
    Date tmp(*this);
    tmp._day += day;
    while(tmp._day > GetMonthDay(_year, _month))
    {
        tmp._day -= GetMonthDay(_year, _month);
        tmp._month++;
        if(tmp._month == 13)
        {
            tmp._year++;
            tmp._month = 1;
        }
    }
    
    return tmp;
}

        tmp出了作用域就销毁,所以不能使用引用返回,必须使用传值返回

        此外,当把程序Date tmp(*this);改为Date tmp = *this;,此时,这个代码是拷贝构造还是赋值? - 拷贝构造

        原因在于:赋值针对的是两个已经存在的对象,这里虽然有赋值符号,但是这里的tmp并不是已经存在的对象,而是用一个已经存在的对象*this也就是d1,去初始化另一个正在构造的对象tmp,所以此处更符合拷贝构造的概念,而不能被形式迷惑了。

        基于上述程序,我们可以做一个沿伸:

        (1)通过operator+,实现operator+=:

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

         (2)通过operator+=,实现operator+:

Date& operator+=(int day)
{
    _day += day;
    while(_day > GetMonthDay[_year, _month])
    {
        _day -= GetMonthDay[_month];
        _month++;
        if(_month > 12)
        {
            _year++;
            _month = 1;
        }
    }

    return *this;//d1出了作用域还要存在,所以要用引用返回
}
Date operator+(int day)
{
    Date tmp(*this);
    
    return tmp += day;
}

        综上:引用返回可以减少拷贝,这里返回的就是*this,即d1。而传值返回,返回的是tmp的拷贝,不会返回tmp,因为出了作用域会销毁。也就是引用返回效率更高,但具体要看返回值出了作用域还在不在了。

        哪些是出了作用域还在的呢?全局对象、静态对象。但实践当中,比如*this不是全局对象,也不是静态对象,就是d1,只是说d1的生命周期不在+=里边,是在+=外边一层。

        比较上述两组程序哪个更好? - 右边

        如图所示:我们可以对应看,即+=对应+=,+对应+。

+对应+:

       右边:复用+=的逻辑,因为是引用返回,所以复用是没有代价的,即没有产生对象;左边:自己实现+的逻辑。所以当前来看是等价的,效果一样。 所以没有效率上的差别,左右两边都产生了tmp。

+=对应+=:

        右边:没有产生临时对象;左边:+=中的*this + day复用+,会产生tmp临时对象,此外传值返回会再产生一个临时对象。左边的+=复用+,+里边要创建对象。

3.3.2 日期减天数

        基于上述结论,我们在-中复用-=来实现日期减天数:

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

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

3.3.3 日期前置++

        operator为单操作符,这样编译器可能无法有效区分前置++和后置++,逻辑不自洽,所以要特殊处理。

        前置++,返回的是递增后的日期对象的引用,如:++d1;。通过调用运算符重载,先将当前日期+1,然后返回自增后的对象,可用传引用返回。

//++d -> d.operator++()

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

3.3.4 日期后置++

        为了跟前置++区分,强行增加一个int形参,构成重载区分。

        后置++,返回的是递增前的日期对象,如:d1++;。通过调用运算符重载,先拷贝一份临时的日期对象tmp,将当前日期+1,然后返回拷贝的临时日期对象tmp,当函数生命周期结束时,tmp会销毁,所以只能用传值返回。

//d++ -> d.operator++(0)//传的值是随机的,只是为了匹配小括号中的int

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

        所以在自定义类型中,前置++效率高后置++一方面创建临时变量,另一方面传值返回要调用一次拷贝构造

3.4  日期之差

        日期减日期(d1 - d2)得到天数,正值代表当前日期与之前相差多少天,负值代表当前日期与之后相差多少天。那么如何使d1拿到大日期,d2拿到小日期呢?此处不妨直接假设d1 > d2,复用运算符重载>,如果当d1 < d2时,调换对象d1和d2中的内容。

        此外,日期之差不仅是_day相减,也包括_year,_month。直接相减过于复杂,不妨换个思路,让小日期补充n天后等于大日期,此时,n就是日期之差。

        我们可以在此复用前置++。所以,日期之差的程序如下:

int operator-(const Date& d)
{
    int flag = 1;
    Date max = *this;
    Date min = d;
    if(*this < d)
    {
        max = d;
        min = *this;
        flag = -1;
    }

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

    int ret = flag * n;

    return ret;
}

3.5 自定义打印

        C++使用流插入是要解决所有类型的打印问题。printf虽然可以支持int float等内置类型,但无法支持自定义类型。printf("%d\n", i);。

        C++规定,自定义类型想用运算符,都要运算符重载。所以,内置类型都可以走流插入,自定义类型不可以。

cout << d;//err

        流插入运算符有两个操作数:1.cout;2.d1作为成员函数重载,this指针占据第一个参数,Date d1必须是左操作数了。此时:

void operator<<(ostream& out);

/////////////////////////////////////////

cout << d1;//err,报错了,如果是双操作数,参数类型要匹配

        也就意味着,调用运算符重载,其实是下边的形式:

d1.operator<<(cout);

///////////////////////////////////////////////////

d1 << cout;//变成了控制台流入到日期类对象中去了。

        因为写成成员函数,就控制不了第一个位置。如果我们要让cout传给ostream占据第一个参数,这时operator就不能写成成员函数,而是要写成全局的。全局函数不能在.h中定义,可以内联,也可以声明和定义分离。

        基于此,cin >> d2 >> d1;和cout << d1 << d2;的程序如下:

注意:        

        此处结合性,从左向右,先cout << d1,这个表达式实际上是一个函数调用,有一个返回值,返回值作为左操作数再进行流插入。所以把返回类型由void改为ostream&。

ostream& operator<<(ostream& out, const Date& d)//传引用,out就是cout的别名
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日: >";
		in >> d._year >> d._month >> d._day;

		if (!d.CheckInvalid())
		{
			cout << "输入了无效日期,请重新输入" << endl;
		}
		else
		{
			break;
		}
	}


	return in;//支持连续的流提取,所以必须要有一个返回值
}

还有两处需要注意:

  1. 写为全局函数访问不了私有private,可以增加友元声明;
  2. 流提取Date不能加const,因为提取的值要放在日期类中。

四、日期计算器的程序

4.1 Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

#include"Date.h"

int main()
{
	Date d1(2025, 2, 28);
	Date d2 = d1 + 20;

	cin >> d1 >> d2;
	cout << d1 << d2 << endl;

	return 0;
}

4.2 Date.h

#pragma once
#include<iostream>
#include<assert.h>

using namespace std;

class Date
{
public:
    bool CheckInvalid();
	Date(int year = 1, int month = 1, int day = 1);

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

	int operator-(const Date& d);

	//本质是inline
	int GetMonthDay(int year, int month)//函数写成内联,即不声明定义分离
	{
		assert(month > 0 && month < 13);
		static int monthDays[13] = { 0,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;
		}

		return monthDays[month];
	}

	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year;
	int _month;
	int _day;

};

4.3 Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

Date::Date(int year, int month, int day)//直接在类中定义默认就是内联函数,即使不加inline也是内联
{
	_year = year;
	_month = month;
	_day = day;

	if (!CheckInvalid())
	{
		cout << "构造的日期非法" << 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)
			if (_day < d._day)
			{
				return true;
			}
	}

	return false;
}

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

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

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		++tmp._month;
		if (tmp._month == 13)
		{
			++tmp._year;
			tmp._month = 1;
		}
	}

	return tmp;
}

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

	return *this;
}

//++d -> d.operator++()
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

//d++ -> d.operator++(0)//传的值是随机的,只是为了匹配小括号中的int
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

int Date::operator-(const Date& d)
{
	int flag = 1;
	Date max = *this;
	Date min = d;

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

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

	return n * flag;
}

bool Date::CheckInvalid()
{
	if (_year <= 0
		|| _month < 1 || _month > 12
		|| _day < 1 || _day > GetMonthDay(_year, _month))
	{
		return false;
	}
	else
	{
		return true;
	}
}

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

istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日: >";
		in >> d._year >> d._month >> d._day;

		if (!d.CheckInvalid())
		{
			cout << "输入了无效日期,请重新输入" << endl;
		}
		else
		{
			break;
		}
	}

	return in;//支持连续的流提取,所以必须要有一个返回值
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值