您现在的位置是:首页 >技术杂谈 >learn C++ NO.5 ——类和对象(3)网站首页技术杂谈

learn C++ NO.5 ——类和对象(3)

玩铁的sinZz 2024-06-17 11:25:18
简介learn C++ NO.5 ——类和对象(3)

日期类的实现

在前面类和对象的学习中,由于知识多比较多和碎,需要一个能够将之前所学知识融会贯通的东西。下面就通过实现日期类来对类和对象已经所学的知识进行巩固。

日期类的基本功能(.h文件)

//Date.h//头文件内容
#include<iostream>
#include<assert.h>

using namespace std;

class Date
{
    friend ostream& operator <<(ostream& out, const Date& d);
    friend istream& operator >>(istream& in, Date& d);
public:

    // 获取某年某月的天数
    int GetMonthDay(int year, int month);

    // 全缺省的构造函数
    Date(int year = 2023, int month = 5, int day = 16);

    // 日期+=天数
    Date& operator+=(int day);

    // 日期+天数
    Date operator+(int day)const;

    // 日期-天数
    Date operator-(int day)const;

    // 日期-=天数
    Date& operator-=(int day);

    // 前置++
    Date& operator++();

    // 后置++
    Date operator++(int);

    // 后置--
    Date operator--(int);

    // 前置--
    Date& operator--();

    // >运算符重载
    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;

    // 日期-日期 返回天数
    int operator-(const Date& d)const;

    void Print()const
    {
        std::cout << _year << " "
            << _month << " "
            << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

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

默认构造函数的实现

由于实现的是日期类的成员对象都是内置类型,所以构造函数、析构函数、拷贝构造、赋值符重载这些都可以使用编译器默认生成的。这里我就实现一个带缺省参数的构造函数即可。

带缺省参数构造的实现

其实日期类这种都是内置类型的类并不需要我们自己写构造函数,但是为了复习与回顾知识点还是写上。采取声明定义分离的方式来实现。

//Date.h中放声明
    Date(int year = 2023, int month = 5, int day = 16);

// Date.c中实现
// 全缺省的构造函数
//Date:: 用于指明类域
Date::Date(int year, int month, int day)
{
    _year = year;
    _month = month;
    _day = day;
}

需要注意的是当声明和定义分离时,我们只能将缺省参数放在声明中,定义内就不能出现缺省参数。不然会和编译器的默认构造函数起冲突。

比较运算符重载的实现

在日常生活中不免的会对另个日期进行比较,并且在后面的模块中还会需要日期类的比较功能。下面我们就来实现一下比较运算功能。

等于运算符重载的实现

对于两个日期类等于的判断条件无非就是年、月和日都相等。

//()后的const修饰的是this指针,不改变成员变量,可以用const修饰this指针
bool Date::operator==(const Date& d)const
{
    return _year == d._year
    && _month == d._month
    && _day == d._day;
}

小于运算符重载的实现

此次比较年月日,只要比第一个操作数小,就返回真,否则返回假。

bool Date::operator<(const Date& d)const
{
    if(_year < d._year)
        return true;
    else if(_month < d._month)
        return true;
    else if(_day < d._day)
        return true;
    else
        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);
}

//重载大于
bool Date::operator>(const Date& d)const
{
    return !(*this <= d);
}

日期的加减功能的实现

首先,我们需要明确的一点是运算符重载必须要重载有意义的运算符。所以像日期+日期这类并没有实际意义的就没必要进行运算符重载。

每月天数获取接口

在实现日期加减运算符重载难免需要频繁获取月份所对应的天数,所以这里我直接把它封装成一个接口。接口的实现思路如下:开辟静态数组存储月份对应的日期,利用数组下标与月份天数的绝对映射来判断每月的天数。当月份为二月时,判断是否为闰年,若是闰年就返回29天。将该函数用static修饰成静态成员函数可以制定类域访问该成员函数,这个在下面会有使用场景。

static int Date::GetMonthDay(int year, int month)
{
	//使用静态数组,可以避免频繁创建数组,提升效率。
    static int DaysArr[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;
    else
        return DaysArr[month];
}

日期+=天数的实现

实现思路如下:首先,日期加的天数。然后,如果日期天数大于月份天数循环让天数减去该月天数。直到天数小于等于该月天数。

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

日期+天数的实现

日期+天数,原来的日期不能变。这就是+和+=的区别。日期加天数的实现这里只需要复用一下+=即可。

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

日期-=天数和日期类-天数的实现

日期-=天数实现思路如下:先让日期类天数-=天数,此时如果日期类的天数依旧大于0,则直接返回日期类。若日期类的天数小于等于0,则让月份先–,然后判断月份有效性,最后让日期类天数+=该月份天数直至日期类天数大于0。日期类-天数的实现只需要创建临时变量并拷贝构造为*this,然后让tmp-=天数,返回tmp即可。

// 日期-=天数
Date& Date::operator-=(int day)
{
    _day -= day;
    while(_day <= 0)
    {
        _month--;
        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;
}

优化+= 和 -=运算符重载

由于天数有可能为负数。所以,我们需要对+=和-=的函数重载进行优化。当天数为负数时,+=应该去调用-=,-=应该去调用+=。优化后代码如下

// 日期-=天数
Date& Date::operator-=(int day)
{
	if(day < 0)
	{
		return *this += (-day);
	}
	
    _day -= day;
    while(_day <= 0)
    {
        _month--;
        if(_month == 0)
        {
            _month = 12;
            _year--;
        }
        
        _day += GetMonthDay(_year, _month);

    }
    return *this;
}

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 == 13)
        {
            _month = 1;
            _year++;
        }
    }
    
    return *this;
}

前置++和后置++的实现

c++规定前置++运算符重载是无参数的,而后置++带有类型参数以示区别。前置++现将对象的自身值自增1后,返回对象。后置++先将对象的值保存到临时变量中,对象值自增1后,返回临时变量。


// 前置++
Date& Date::operator++()
{
    *this += 1;
    return *this;
}

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

前置–和后置–的实现

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

// 前置--
Date&  Date::operator--()
{
    return *this -= 1;
}

日期-日期返回天数的实现

实现思路如下:默认左操作数为较大值,并且默认天数为负数。如果右操作数大于左操作数,则较大值为右操作符,且天数为正数。循环++较小的操作数,并记录循环次数。直到两个操作数相等。

// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{
	//默认为负数
    int flag = -1;
    Date max = *this;
    Date min = d;
    if(d > *this)
    {
        max = d;
        min = *this;
        //天数为正数
        flag = 1;
    }
    int n = 0;
    while(min != max)
    {
        min++;
        n++;
    }
    return n*flag;
}

<< 和 >>运算符重载

首先,我们先了解一下cout和cin这两个类对象。
在这里插入图片描述
为什么cout << 内置类型变量,就可以自动识别内置类型变量呢?这是因为c++标准库对<<运算符进行了运算符重载。而多个内置类型的运算符重载又构成了函数重载。

#include<iostream>
using namespace std;
int main
{
	int a = 10;
	double d =20.11
	cout << a;
	cout << d;
}

而重载<<运算符,第一个参数必须是cout。而成员函数默认占用成员函数的第一个参数。所以这里这能进<<运算符重载到全局中。那么第二个问题来了,如何让全局函数突破类域的限制呢?这里就要用到友元关键字来修饰函数了。友元函数可以突破类域的限制。我们将友元函数的声明放在类的内部。这样友元函数就可以突破类域限制,访问类的私有成员变量。

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

关于不存在日期的解决问题

这里为了避免天数月份小于1,月份大于12,天数大于月数的最大天数。所以我们应该在构造函数和>>运算符重载中,对日期进行相应的判断。

Date::Date(int year, int month, int day)
{
    if((month > 12 || month < 1) ||
       (day < 1||day > GetMonthDay(year,month))
       )
    {
        cout<<"输入错误"<<endl;
        assert(false);
    }
    else
    {
        _year = year;
        _month = month;
        _day = day;
        
    }
}

istream& operator >>(istream& in, Date& d)
{
    int year,month,day;
    in >> year >> month >> day;
    
    if((month > 12 || month < 1) ||
       (day < 1||day > d.GetMonthDay(year,month))
       )
    {
        cout<<"输入错误"<<endl;
        assert(false);
    }
    else
    {
        d._year = year;
        d._month = month;
        d._day = day;
        
    }
    
    return in;
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。