日期类的实现|运算符重载的复用

前言

通过前面C++入门与类与对象的学习,今天我们将运用所学的知识点完成一个Date类。

本节目标

  1. 运用所学知识完成Date类。
  2. 详细讲解运算符各种重载。
  3. 理解运算符重载的复用。

一、Date类的六个默认成员函数

六个成员函数,Date类只需要自己实现构造函数即可。

解析:

  • 因为成员变量为内置类型,编译器默认生成的构造函数不对内置类型初始化,所以需要自己显式定义构造函数。
  • 因为Date类没有涉及动态资源的申请,编译器生成的析构函数、拷贝函数与赋值运算符重载就可以完成需求,所以不需要自己显式定义。
  • 取地址重载,Date类没有特殊要求,所以不需要自己显式定义。

1、实现构造函数

代码示例:

Date.h文件

class Date
{
public://Date类没有涉及动态资源的申请,所以析构、拷贝构造、赋值重载不用自己实现,使用编译器生成的即可// 只有构造函数需要自己实现//默认构造函数——内置类型需要自己实现,完成初始化Date(int year = 2001, int month = 9, int day = 9);//打印日期void Print()const;private:int _year;//年int _month;//月int _day;//日
};

Date.cpp

Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}//打印日期
void Date::Print()const
{cout << _year << "-" << _month << "-" << _day << endl;
}

解析:

  • 构造函数的参数设计为全缺省。
    • 优点:即可以传参,也可以不传参,使用灵活。
    • 注意:缺省参数不能在声明与定义同时存在,缺省参数应当指定在声明。
  • 类成员函数声明与定义分离时,注在成员函数定义时要使用类限制运算符::指定,该成员函数属于哪个类。

Test.cpp

//测试构造函数
void TestDate1()
{Date d1;d1.Print();const Date d2(2024, 1, 10);d2.Print();const Date d3(2024, 13, 1);d3.Print();
}int main()
{TestDate1();return 0;
}

运行结果:

在这里插入图片描述
通过测试,我们发现构造函数在初始化对象时,有一些非法日期也能成功初始化对象。

优化构造函数:在初始化之前,我们应当先检查月份与天数是否合理,合理则完成对象的初始化,不合理则提示并断言!

优化之后的代码:

//默认构造函数——内置类型需要自己实现,完成初始化
//注意缺省函数不能定义与声明都给缺省值,缺省值应当在函数声明指定
Date::Date(int year, int month, int day)
{//检查月份与天数是否合理,合理才初始化,不合理提示并断言if (month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year,month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}
}//获取月份的天数
int Date::GetMonthDay(const int year,const int month)const
{//使用数组存放每个月的天数//优化:因为该函数要被频繁调用,而局部变量每一次调用都要创建,所以将其优化为静态变量static int daysArr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//判断是否为闰年且为2月//优化:因为闰年的判断麻烦,指令较多,所以先判断是否为2月if (2 == month && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return daysArr[month];}
}

解析:

  • 因为我们日期类有许多模块都需要知道月份的天数,所以我们将其封装成Date类的一个成员函数。
  • 使用数组存放每个月的天数。
    • 因为数组下标从0开始,所以数组的大小我们设为13。
    • 因为该函数可能会被频繁调用,局部变量每一次调用都要创建,所以使用static修饰数组。
  • &&逻辑运算符中的两个操作数,建议将简单的操作数放在左边,因为&&运算符左边为假,右边的无需计算(逻辑操作符的短路特性)。

我们再次运行优化之后的程序:

在这里插入图片描述

二、Date类的运算符重载

自定义类型要使用运算符,需要通过运算符重载重新定义该运算符的含义。

使用运算符重载可以使用的程序更易于编写和阅读。

是否需要重载运算符,具体分析这个运算符对这个类是否有意义。

1、关系运算符重载

对于日期类我们一般可以对日期进行比较,所以日期类需要对关系运算符重载。

任意类,重载关系运算符:

  • 我们可以根据关系运算符之间的互斥,一般只需要先实现<与==这两个运算符的重载,其余关系运算符,只需要根据他们之间的互斥直接复用这两个重载运算符即可。
    • 先实现<与 ==(或>与 ==)的运算符重载函数
    • 复用d1<d2 || d1==d2 ——》d1<=d2
    • 复用 !(d1<=d2) ——》d1>d2
    • 复用 !(d1<d2) ——》d1>=d2
    • 复用 !(d1==d2) ——》d1!=d2
  • 因为关系运算符一般不需要改变运算的对象,所以形参都是常量的引用

代码示例:

Date.h

class Date
{
public://Date类没有涉及动态资源的申请,所以析构、拷贝构造、赋值重载不用自己实现,使用编译器生成的即可// 只有构造函数需要自己实现//默认构造函数——内置类型需要自己实现,完成初始化Date(int year = 2001, int month = 9, int day = 9);//获取月份的天数int GetMonthDay(const int year,const int month)const;//打印日期void Print()const;//关系运算符的重载//1、<运算符重载bool operator<(const Date& d)const;//2、==运算符重载bool operator==(const Date& d)const;//3、<=运算符重载bool operator<=(const Date& d)const;//4、>运算符重载bool operator>(const Date& d)const;//5、>=运算符重载bool operator>=(const Date& d)const;//6、!=运算符重载bool operator!=(const Date& d)const;private:int _year;//年int _month;//月int _day;//日
};

Date.cpp

//关系运算符的重载
//1、<运算符重载
bool Date::operator<(const Date& d)const
{if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}else{return false;}
}
//2、==运算符重载
bool Date::operator==(const Date& d)const
{return _year == d._year && _month == d._month && _day == d._day;
}
//3、<=运算符重载
bool Date::operator<=(const Date& d)const
{return *this < d || *this == d;
}
//4、>运算符重载
bool Date::operator>(const Date& d)const
{return !(*this <= d);
}
//5、>=运算符重载
bool Date::operator>=(const Date& d)const
{return !(*this < d);
}
//6、!=运算符重载
bool Date::operator!=(const Date& d)const
{return !(*this == d);
}

解析:

  • 由于Date类的成员变量是私有的,所以将关系运算符定义为成员函数。
  • 当重载运算符是成员函数时,其形参看起来比操作数数目少1个,因为成员函数的第一个参数为隐藏的this。
  • const成员函数修饰的是*this,表示在该成员函数中不能对类的任何成员进行修改。(建议:只要成员函数内部不修改成员变量,都应该加const,这样普通对象和const对象都可以调用)

Test.cpp

//测试日期的关系运算符重载
void TestDate2()
{//普通对象const Date d1;//const对象const Date d2(2024, 1, 10);cout << (d1 < d2) << endl;cout << (d1 == d2) << endl;cout << (d1 <= d2) << endl;cout << (d1 > d2) << endl;cout << (d1 >= d2) << endl;cout << (d1 != d2) << endl;}int main()
{TestDate2();return 0;
}

运行结果:

在这里插入图片描述

2、日期+天数

日期+日期是没有意义的,但是日期+天数有意义。

日期+天数,只需要理解日期的“进位制”即可。如图分析:

在这里插入图片描述

如图分析:我们可知日期+天数的进位制:

  • 日期的天数+天数
  • 判断日期的天数是否超过该月的天数(调用GetMonthDay可得到天数)
  • 超过了该月天数,我们就进位
    • 天数超过该月份天数,则减去该月天数,月份进一
    • 注意当月份超过12时,则月份减去12,年份进一
    • 总结:即天满了进月,月满了进年!
  • 重复步骤2与步骤3

日期+天数有+与+=两种运算符重载可以完成。

(1)日期加等天数 operator+=

注意:+=运算符会影响左操作数本身!

代码示例:

Date.cpp

//日期+=天数
Date& Date::operator+=(const int& day)
{//1、日期的天数+天数_day += day;//使用变量存放GetMonthDay函数的返回值,避免重复调用int ret = 0;//while循环——天数满了进月份,月份满了进年while (_day > (ret = GetMonthDay(_year, _month))){//天数满了,减去当月的天数,月份进一_day -= ret;_month++;//判断当月份满了,年份进一if (_month > 12){_month -= 12;_year++;}}//*this出了作用域还存在,所以返回值为引用返回return *this;
}

解析:

  • 如果在函数体中不改变参数的值,建议使用const修饰
  • 因为*this出了作用域还存在,所以可以引用返回。
  • 自定义类型做函数返回值时,为了提高程序效率,能引用返回尽量引用返回
  • 引用做返回值
    • 优点:①减少拷贝提高效率;②可以读写返回值
    • 注意:当返回值出了函数体,不存在了,就不能用引用返回

(2)日期加天数 operator+

注意:+运算符不会影响操作数!

代码示例:

//日期+天数
Date Date::operator+(const int& day)const
{//+运算符不会影响操作数本身,所以定义一个局部变量来保存运算的日期Date temp(*this);temp._day += day;int ret = 0;while (temp._day > (ret = GetMonthDay(temp._year, temp._month))){temp._day -= ret;temp._month++;if (temp._month > 12){temp._month -= 12;temp._year++;}}//返回值为一个局部变量,出了作用域就不存在了,所以不能引用返回return temp;
}

解析:

  • 成员函数内部不改变成员变量,建议加const,这样普通对象与const对象都可以调用。
  • 由于+运算符不会影响对象本身,所以要定义一个临时变量来存放运算的日期
  • 注意:当出了函数体,对象不存在了,则不能使用引用返回。

(3)operator+复用operator+=

我们发现operator+与operator+=有许多代码是重复的,所以我们一般可以先实现operator+=,之后的operator+复用operator+=即可。

为什么复用operator+=,不复用operator+?

答案是:复用operator+=更好一些,因为operator+中需要创建两个额外的临时变量,而operator+=不需要创建。如下图所示:

在这里插入图片描述
如果+复用+=,只有+这里额外需要创建两个对象;如果是+=复用+,+=也需要额外创建两个对象。

代码示例:

//日期+天数复用+=
Date Date::operator+(const int& day)const
{//+运算符不会影响操作数本身,所以定义一个局部变量来保存运算的日期Date temp(*this);temp += day;//返回值为一个局部变量,出了作用域就不存在了,所以不能引用返回return temp;
}

3、日期-天数

日期减天数也是有意义的。

日期加天数是进位,那日期减天数就是借位了。如下图所示:

在这里插入图片描述
如图分析:我们可知日期-天数的借位制:

  • 日期的天数-天数
  • 判断日期是否合理
    • 不合理,即天数不够,向月份借位,即月份减一,之后通过GetMonthDay函数获取天数。
    • 注意:当月份不够时,需要向年借位,即年份减一,月份加12
  • 重复步骤2

(1)日期减等天数 operator-=

注意:-=操作符会影响操作数本身。

代码示例:

//日期-=天数
Date& Date::operator-=(const int& day)
{_day -= day;//while循环——借位:如果天数<=0,则往月份去借。注意:当月份不够了,则往年去借。while (_day <= 0){//1、向月份借_month--;//注意:当月份不够了,则往年借if (_month == 0){_year--;_month += 12;}_day += GetMonthDay(_year, _month);}//*this出了函数体还在,所以可以引用返回return *this;
}

注意:向月份借完位之后,要先判断当月不够了,向年借位之后,才能获取天数。

(2)日期-天数 operator-

注意:-操作符不会影响操作数本身,所以需要创建一个临时变量存放运算的日期。

与operator+一样的思路,operator-复用operator-=比较好。

代码示例:

//日期-天数
Date Date::operator-(const int& day)const
{//-运算符不会影响操作数本身,所以定义一个临时变量存放运算的日期Date temp(*this);temp -= day;//temp是一个局部变量,出了函数体就不存在,所以不能引用返回return temp;
}

(3)测试日期加减天数

Test.cpp

//测试日期+天数与日期-天数
void TestDate3()
{Date d1(2024, 1, 11);Date d2;//测试operator+d2 = d1 + 100;d1.Print();d2.Print();//测试operator+=d1 += 100;d1.Print();//测试operator-d2 = d1 - 100;d1.Print();d2.Print();//测试operator-=d1 -= 100;d1.Print();//特殊:日期加负数d1 += -100;d1.Print();//特殊:日期减负数d1 = d2 - (-100);d1.Print();
}int main()
{TestDate3();return 0;
}

运行结果:

在这里插入图片描述

我们发现当加减一个负数时,计算出错了。

因为我们operator-与operator+都是复用的operator-=与operator+=,所以我们直接定位这两个函数调试即可。

调试我们发现,原来在设计的时候我们没有考虑加减的天数为负数的情况。

当一个数加一个-100的数,实际是减去100;当一个数减-100时,实际是加100。

代码示例:

//日期+=天数
Date& Date::operator+=(const int& day)
{//特殊情况:当加的天数为负数时,实际是减这个数的绝对值if (day < 0){return *this -= -day;}//1、日期的天数+天数_day += day;//使用变量存放GetMonthDay函数的返回值,避免重复调用int ret = 0;//while循环——天数满了进月份,月份满了进年while (_day > (ret = GetMonthDay(_year, _month))){//天数满了,减去当月的天数,月份进一_day -= ret;_month++;//判断当月份满了,年份进一if (_month > 12){_month -= 12;_year++;}}//*this出了作用域还存在,所以返回值为引用返回return *this;
}//日期+天数复用+=
Date Date::operator+(const int& day)const
{//+运算符不会影响操作数本身,所以定义一个局部变量来保存运算的日期Date temp(*this);temp += day;//返回值为一个局部变量,出了作用域就不存在了,所以不能引用返回return temp;
}//日期-=天数
Date& Date::operator-=(const int& day)
{//特殊情况:当减的天数为负数时,实际是加这个数的绝对值if (day < 0){return *this += -day;}_day -= day;//while循环——借位:如果天数<=0,则往月份去借。注意:当月份不够了,则往年去借。while (_day <= 0){//1、向月份借_month--;//注意:当月份不够了,则往年借if (_month == 0){_year--;_month += 12;}_day += GetMonthDay(_year, _month);}//*this出了函数体还在,所以可以引用返回return *this;
}//日期-天数
Date Date::operator-(const int& day)const
{//-运算符不会影响操作数本身,所以定义一个临时变量存放运算的日期Date temp(*this);temp -= day;//temp是一个局部变量,出了函数体就不存在,所以不能引用返回return temp;
}

4、日期递增

在内置类型中++操作符有前置++与后置++,所以运算符重载也应该有前置和后置两个版本。

因为前置与后置函数名都是operator++,并且运算对象的操作数类型与数量也相同,所以想要同时定义前置与后置两个版本必须先解决函数重载问题。

区分前置和后置操作符:

  • 为了区分前置与后置两个版本,后置版本增加一个int形参。
  • 增加的这个int形参一般不被使用,仅仅是占位,与前置++构成重载,所以该形参只需要声明类型,不用写形参名。
  • 当我们使用重载的后置运算符时,编译器会为这个形参提供一个值为0的实参

(1)前置递增 operator++

tip:前置类型返回的是递增后对象的引用。

能不能引用返回取决于,该对象出函数体是否存在,存在则可以引用返回,不存在则不可以。

代码演示:

//1、前置++
//前置++应该返回递增后对象的引用
Date& Date::operator++()
{*this += 1;//*this出了函数体还存在,所以引用返回return *this;
}

tip:日期++就是日期+天数,所以复用operator+=。

(2)后置递增 operator++

tip:后置类型应该返回对象的原值(即递增之前的值),返回的形式是一个值而非引用。

代码示例:

//2、后置++
//后置++应该返回对象的原值(即递增之前的值),返回的形式是一个值而非引用
Date Date::operator++(int)
{//定义一个临时变量存放递增之前的值Date temp(*this);*this += 1;//temp是一个局部变量,出了函数就不存在了,所以返回的是一个值return temp;
}

自定义类型前置比后置好:

  • 因为后置版本是传值返回,前置版本是引用返回,
  • 后置需要额外创建一个临时变量存放递增之前的值

5、日期递减

与递增一样,递减也有前置与后置两个版本。

为了区分前置与后置,递减也是在后置增加了一个int参数。

(1)前置递减 operator–

前置类型返回的递减后对象的引用。

代码示例:

//1、前置--
//前置--应该返回递减后对象的引用
Date& Date::operator--()
{//递减就是日期-天数,所以复用operator-=*this -= 1;//*this出了函数体还存在,所以引用返回return *this;
}

(2)后置递减 operator–

后置类型应该返回对象的原值(即递减之前的值),返回的形式是一个值而非引用。

代码示例:

//2、后置--
//后置--应该返回对象的原值(即递减之前的值),返回的形式是一个值而非引用
Date Date::operator--(int)
{//定义一个临时变量存放递减之前的值Date temp(*this);//递减就是日期-天数,所以复用operator-=*this -= 1;//temp是一个局部变量,出了函数就不存在了,所以返回的是一个值return temp;
}

自定义类型前置比后置好:

  • 因为后置版本是传值返回,前置版本是引用返回,
  • 后置需要额外创建一个临时变量存放递减之前的值

6、日期-日期

日期-日期是有意义的,我们可以通过日期-日期计算两个日期之间的天数。

我们可以用计数器的方式,计算d1与d2两个日期之间的天数:

  • 使用flag法,确定两个日期的大小
    • 定义三个变量,max存放d1,min存放d2,flag表示状态(flag为1表示假设成立,为-1表示假设不成立)
    • 最后再通过if判断假设是否成立,不成立就修改
  • while循环,累加min,直至min与max相等。累加过程中使用count变量计数。

代码演示:

//日期-日期
int Date::operator-(const Date& d)const
{//flag法//1、假设max=*this,min=d,flag=1Date max = *this;Date min = d;int flag = 1;//状态变量,为1表示状态成立,为-1表示状态不成立//2、判断状态是否成立if (*this < d){//状态不成立max = d;min = *this;flag = -1;}//通过计数器的方式,得到两个日期之间的天数int count = 0;while (min != max){//自定义类型建议使用前置++++min;count++;}//返回的日期之差为两个日期之间的天数*flagreturn count * flag;
}

7、输入输出日期

C++IO标准库分别使用>>和<<执行输入和输出操作。如下图所示:

在这里插入图片描述
tip:

  • IO库定义了用其读写内置类型的重载函数
    • 因为库中已经定义了,所以内置类型可以直接使用<<与>>
    • <<与>>可以自动识别类型,是因为函数重载
  • 自定义类型需要程序员自己实现适合其对象的<<与>>

输入输出运算符重载函数必须是非成员函数:

  • 因为成员函数的左操作数是隐含的this,所以输入输出运算符必须是非成员函数。
  • 输入运算符的左操作数对象为istream
  • 输出运算符的左操作数对象为ostream

IO运算符一般需要读写类的非公有数据成员,所以IO运算符一般被声明为友元类。

友元类:

  • 友元类可以直接访问类的私有成员,它是定义在类外面的普通函数(即全局),不属于任何类,但需要在类的内部声明,声明时需要加关键字friend。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 友元函数不能用const修饰。
  • 一个函数可以是多个类的友元函数

友元函数在后期会详细讲解,今天大致知道使用即可。

(1)输出日期 operator<<

tip:

  • 输出运算符的第一个参数是一个非常量ostream对象的引用。
    • ostream是非常量是因为向流写入内容会改变其状态
    • ostream引用是因为我们无法直接复制一个ostream对象
  • 第二个形参一般是一个常量的引用,该常量是我们想要打印的类类型。
    • 引用的原因是避免拷贝,提高效率
    • 该形参可以是常量是因为一般情况下打印对象不会改变对象的内容。
  • <<运算符从左向右结合,可以连续打印,所以operator<<要返回它的ostream形参。

代码示例:

//输出日期
//operator<<不能定义为成员函数,因为成员函数的左操作数被this占据
ostream& operator<<(ostream& out, const Date& d)
{//输出日期out << d._year << "年" << d._month << "月" << d._day;//<<运算符从左向右结合,可以连续打印,所以operator<<要返回ostream形参//ostream形参出了函数体还在,所以引用返回return out;
}

tip: 因为内置类型的输出运算符不考虑格式化操作,尤其不会打印换行符,所以通常,重载输出运算符应该主要负责打印对象的内容而非控制格式,重载输出运算符不应该打印换行符。

(2)输入日期

tip:

  • 输入运算符的第一个参数是istream&将要读取的流的引用
  • 第二个形参是将要读入到(非常量)对象的引用。
  • 与输出运算符一样,输入运算符可以连续读取,所以operator>>要返回某个给定流的引用。

代码示例:

//输入日期
istream& operator>>(istream& in, Date& d)
{//输入运算符必须处理输入可能失败的情况int year, month, day;//不需要初始化,因为我们将先读取数据到year,month, day,之后才使用in >> year >> month >> day;//检查输入是否成功且合理if (in && month > 0 && month < 13 && day > 0 && day <= d.GetMonthDay(year, month)){//输入成功且合理d._year = year;d._month = month;d._day = day;}else{//输入失败或不合理时,提示并断言cout << "非法日期" << endl;assert(false);}//输入运算符可以连续读取,所以operator>>要返回某个给定流的引用。return in;
}

tip:

  • 输入运算符必须处理输入可能失败的情况
  • 输入日期还需要检查是否合理

三、总代码

Date.h

#include<iostream>
#include<assert.h>using namespace std;class Date
{
public://Date类没有涉及动态资源的申请,所以析构、拷贝构造、赋值重载不用自己实现,使用编译器生成的即可// 只有构造函数需要自己实现//默认构造函数——内置类型需要自己实现,完成初始化Date(int year = 2001, int month = 9, int day = 9);//获取月份的天数int GetMonthDay(const int year,const int month)const;//打印日期void Print()const;//关系运算符的重载//1、<运算符重载bool operator<(const Date& d)const;//2、==运算符重载bool operator==(const Date& d)const;//3、<=运算符重载bool operator<=(const Date& d)const;//4、>运算符重载bool operator>(const Date& d)const;//5、>=运算符重载bool operator>=(const Date& d)const;//6、!=运算符重载bool operator!=(const Date& d)const;//日期+=天数Date& operator+=(const int& day);//日期+天数Date operator+(const int& day)const;//日期-=天数Date& operator-=(const int& day);//日期-天数Date operator-(const int& day)const;//递增//区分前置与后置:后置增加了一个int参数,该参数一般不使用,仅仅是占位,与前置区分构成重载//1、前置++//前置++应该返回递增后对象的引用Date& operator++();//2、后置++//后置++应该返回对象的原值(即递增之前的值),返回的形式是一个值而非引用Date operator++(int);//递减//区分前置与后置:后置增加了一个int参数,该参数一般不使用,仅仅是占位,与前置区分构成重载//1、前置--//前置--应该返回递减后对象的引用Date& operator--();//2、后置--//后置--应该返回对象的原值(即递减之前的值),返回的形式是一个值而非引用Date operator--(int);//日期-日期int operator-(const Date& d)const;//输出日期//friend声明友元函数friend ostream& operator<<(ostream& out, const Date& d);//输入日期//friend声明友元函数friend istream& operator>>(istream& in, Date& d);private:int _year;//年int _month;//月int _day;//日
};

Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include"Date.h"//Date::Date(int year, int month, int day)
//{
//	_year = year;
//	_month = month;
//	_day = day;
//}//默认构造函数——内置类型需要自己实现,完成初始化
//注意缺省函数不能定义与声明都给缺省值,缺省值应当在函数声明指定
Date::Date(int year, int month, int day)
{//检查月份与天数是否合理,合理才初始化,不合理提示并断言if (month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year,month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}
}//获取月份的天数
int Date::GetMonthDay(const int year,const int month)const
{//使用数组存放每个月的天数//优化:因为该函数要被频繁调用,而局部变量每一次调用都要创建,所以将其优化为静态变量static int daysArr[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//判断是否为闰年且为2月//优化:因为闰年的判断麻烦,指令较多,所以先判断是否为2月if (2 == month && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return daysArr[month];}
}//打印日期
void Date::Print()const
{cout << _year << "-" << _month << "-" << _day << endl;
}//关系运算符的重载
//1、<运算符重载
bool Date::operator<(const Date& d)const
{if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}else{return false;}
}
//2、==运算符重载
bool Date::operator==(const Date& d)const
{return _year == d._year && _month == d._month && _day == d._day;
}
//3、<=运算符重载
bool Date::operator<=(const Date& d)const
{return *this < d || *this == d;
}
//4、>运算符重载
bool Date::operator>(const Date& d)const
{return !(*this <= d);
}
//5、>=运算符重载
bool Date::operator>=(const Date& d)const
{return !(*this < d);
}
//6、!=运算符重载
bool Date::operator!=(const Date& d)const
{return !(*this == d);
}//日期+=天数
Date& Date::operator+=(const int& day)
{//特殊情况:当加的天数为负数时,实际是减这个数的绝对值if (day < 0){return *this -= -day;}//1、日期的天数+天数_day += day;//使用变量存放GetMonthDay函数的返回值,避免重复调用int ret = 0;//while循环——天数满了进月份,月份满了进年while (_day > (ret = GetMonthDay(_year, _month))){//天数满了,减去当月的天数,月份进一_day -= ret;_month++;//判断当月份满了,年份进一if (_month > 12){_month -= 12;_year++;}}//*this出了作用域还存在,所以返回值为引用返回return *this;
}//日期+天数
//Date Date::operator+(const int& day)const
//{
//	//+运算符不会影响操作数本身,所以定义一个局部变量来保存运算的日期
//	Date temp(*this);
//	temp._day += day;
//	int ret = 0;
//	while (temp._day > (ret = GetMonthDay(temp._year, temp._month)))
//	{
//		temp._day -= ret;
//		temp._month++;
//		if (temp._month > 12)
//		{
//			temp._month -= 12;
//			temp._year++;
//		}
//	}
//	//返回值为一个局部变量,出了作用域就不存在了,所以不能引用返回
//	return temp;
//}//日期+天数复用+=
Date Date::operator+(const int& day)const
{//+运算符不会影响操作数本身,所以定义一个局部变量来保存运算的日期Date temp(*this);temp += day;//返回值为一个局部变量,出了作用域就不存在了,所以不能引用返回return temp;
}//日期-=天数
Date& Date::operator-=(const int& day)
{//特殊情况:当减的天数为负数时,实际是加这个数的绝对值if (day < 0){return *this += -day;}_day -= day;//while循环——借位:如果天数<=0,则往月份去借。注意:当月份不够了,则往年去借。while (_day <= 0){//1、向月份借_month--;//注意:当月份不够了,则往年借if (_month == 0){_year--;_month += 12;}_day += GetMonthDay(_year, _month);}//*this出了函数体还在,所以可以引用返回return *this;
}//日期-天数
Date Date::operator-(const int& day)const
{//-运算符不会影响操作数本身,所以定义一个临时变量存放运算的日期Date temp(*this);temp -= day;//temp是一个局部变量,出了函数体就不存在,所以不能引用返回return temp;
}//递增
//区分前置与后置:后置增加了一个int参数,该参数一般不使用,仅仅是占位,与前置区分构成重载
//1、前置++
//前置++应该返回递增后对象的引用
Date& Date::operator++()
{*this += 1;//*this出了函数体还存在,所以引用返回return *this;
}
//2、后置++
//后置++应该返回对象的原值(即递增之前的值),返回的形式是一个值而非引用
Date Date::operator++(int)
{//定义一个临时变量存放递增之前的值Date temp(*this);*this += 1;//temp是一个局部变量,出了函数就不存在了,所以返回的是一个值return temp;
}//递减
//区分前置与后置:后置增加了一个int参数,该参数一般不使用,仅仅是占位,与前置区分构成重载
//1、前置--
//前置--应该返回递减后对象的引用
Date& Date::operator--()
{//递减就是日期-天数,所以复用operator-=*this -= 1;//*this出了函数体还存在,所以引用返回return *this;
}
//2、后置--
//后置--应该返回对象的原值(即递减之前的值),返回的形式是一个值而非引用
Date Date::operator--(int)
{//定义一个临时变量存放递减之前的值Date temp(*this);//递减就是日期-天数,所以复用operator-=*this -= 1;//temp是一个局部变量,出了函数就不存在了,所以返回的是一个值return temp;
}//日期-日期
int Date::operator-(const Date& d)const
{//flag法//1、假设max=*this,min=d,flag=1Date max = *this;Date min = d;int flag = 1;//状态变量,为1表示状态成立,为-1表示状态不成立//2、判断状态是否成立if (*this < d){//状态不成立max = d;min = *this;flag = -1;}//通过计数器的方式,得到两个日期之间的天数int count = 0;while (min != max){//自定义类型建议使用前置++++min;count++;}//返回的日期之差为两个日期之间的天数*flagreturn count * flag;
}//输出日期
//operator<<不能定义为成员函数,因为成员函数的左操作数被this占据
ostream& operator<<(ostream& out, const Date& d)
{//输出日期out << d._year << "年" << d._month << "月" << d._day;//<<运算符从左向右结合,可以连续打印,所以operator<<要返回ostream形参//ostream形参出了函数体还在,所以引用返回return out;
}//输入日期
istream& operator>>(istream& in, Date& d)
{//输入运算符必须处理输入可能失败的情况int year, month, day;//不需要初始化,因为我们将先读取数据到year,month, day,之后才使用in >> year >> month >> day;//检查输入是否成功且合理if (in && month > 0 && month < 13 && day > 0 && day <= d.GetMonthDay(year, month)){//输入成功且合理d._year = year;d._month = month;d._day = day;}else{//输入失败或不合理时,提示并断言cout << "非法日期" << endl;assert(false);}//输入运算符可以连续读取,所以operator>>要返回某个给定流的引用。return in;
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include"Date.h"//测试构造函数
void TestDate1()
{Date d1;d1.Print();const Date d2(2024, 1, 10);d2.Print();const Date d3(2024, 13, 1);d3.Print();
}//测试日期的关系运算符重载
void TestDate2()
{//普通对象const Date d1;//const对象const Date d2(2024, 1, 10);cout << (d1 < d2) << endl;cout << (d1 == d2) << endl;cout << (d1 <= d2) << endl;cout << (d1 > d2) << endl;cout << (d1 >= d2) << endl;cout << (d1 != d2) << endl;}//测试日期+天数与日期-天数
void TestDate3()
{Date d1(2024, 1, 11);Date d2;//测试operator+d2 = d1 + 100;d1.Print();d2.Print();//测试operator+=d1 += 100;d1.Print();//测试operator-d2 = d1 - 100;d1.Print();d2.Print();//测试operator-=d1 -= 100;d1.Print();//特殊:日期加负数d1 += -100;d1.Print();//特殊:日期减负数d1 = d2 - (-100);d1.Print();
}//测试递增
void TestDate4()
{Date d1;Date d2;d2 = d1++;d1.Print();d2.Print();d2 = ++d1;d1.Print();d2.Print();
}//测试递减
void TestDate5()
{Date d1;Date d2;d2 = d1--;d1.Print();d2.Print();d2 = --d1;d1.Print();d2.Print();
}//测试日期-日期
void TestDate6()
{Date d1(2023, 12, 1);Date d2(2024, 12, 31);cout << d1 - d2 << endl;cout << d2 - d1 << endl;
}//测试输入输出日期
void TestDate7()
{Date d1(2023, 12, 1);//测试operator<<cout << d1 << endl;//测试operator>>cin >> d1;cout << d1 << endl;
}int main()
{//TestDate1();//TestDate2();//TestDate3();//TestDate4();//TestDate5();//TestDate6();TestDate7();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/617244.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新一代工厂融合广播系统,助力工业行业可持续发展

在当今高度竞争的工业环境中&#xff0c;工厂的运营效率和生产安全至关重要。为了实现这一目标&#xff0c;新一代工厂融合广播系统应运而生&#xff0c;将指挥中心、值班中心、融合通信调度主机、厂区终端和防爆话机紧密连接&#xff0c;构建了一个全面、高效的通信网络。 系统…

Linux进程管理、ps命令、kill命令

每一个程序在运行的时候都会被操作系统注册为系统中的一个进程 补充一下操作系统的内容&#xff1a; 进程实体&#xff08;又称进程映像&#xff09;&#xff1a;程序段、相关数据段、PCB三部分构成 进程是进程实体的运行过程&#xff0c;是系统进行资源分配的一个独立单位 …

团结引擎的安装

团结引擎有多种方式可以安装&#xff0c;具体可以参考团结引擎官方文档&#xff0c;这里我们使用最简单的安装方式&#xff0c;通过团结Hub来安装。 1. 安装 Tuanjie Hub 进入团结引擎官网&#xff0c;点击右上角的【下载Unity】&#xff0c;进入下载界面&#xff0c;选择“下载…

C++——冒泡排序

作用&#xff1a;最常用的排序算法&#xff0c;对数组内元素进行排序 1&#xff0c;比较相邻的元素&#xff0c;如果第一个比第二个大&#xff0c;就交换他们两个。 2&#xff0c;对每一对相邻元素做同样的工作&#xff0c;执行完毕后&#xff0c;找到第一个最大值。 3&…

JDK21和 Flowable 7.0.0

JDK21和 Flowable 7.0.0 一.Flowable二.项目搭建1.依赖包2.数据库3.资源文件1.YML配置文件2.Drools kbase3.Drools rule4.DMN 决策表5.BPMN 流文件 4.BPMN 流程图绘制插件5.测试代码1.启动类2.Flowable 配置3.Camel 配置1.Camel 配置2.Camel Router 定义 4.扩展类监听1.外部工作…

docker compose安装gitlab

环境 查看GitLab镜像 docker search gitlab 拉取GitLab镜像 docker pull gitlab/gitlab-ce 准备gitlab-docker.yml文件 version: 3.1 services:gitlab:image: gitlab/gitlab-ce:latestcontainer_name: gitlabrestart: alwaysenvironment:GITLAB_OMNIBUS_CONFIG: |external_url…

在Windows Server 2012中部署war项目

目录 一.安装jdk 二.安装tomcat 三.安装MySQL 四.部署项目 好啦今天就到这了&#xff0c;希望帮到你了哦 前言&#xff1a;具体步骤&#xff1a; 1.安装JDK&#xff1a; 2.安装tomcat&#xff1a; 3.安装MySQL&#xff1a; 4.部署项目&#xff1a; 一.安装jdk 将所需文件放…

苍穹外卖学习----出错记录

1.微信开发者工具遇到的问题&#xff1a; 1.1appid消失报错&#xff1a; {errMsg: login:fail 系统错误,错误码:41002,appid missing [20240112 16:44:02][undefined]} 1.2解决方式&#xff1a; appid可在微信开发者官网 登录账号后在开发栏 找到 复制后按以下步骤粘贴即…

怎么将文件批量重命名为不同名称?

怎么将文件批量重命名为不同名称&#xff1f;有许多情况下可以考虑对文件进行批量重命名为不同名称&#xff0c;文件分类和整理&#xff1a;当您需要对一组文件进行分类、整理或重新组织时&#xff0c;可以考虑将它们批量重命名为不同的名称。这有助于更好地组织文件并使其更易…

提升测试多样性,揭秘Pytest插件pytest-randomly

大家可能知道在Pytest测试生态中&#xff0c;插件扮演着不可或缺的角色&#xff0c;为开发者提供了丰富的功能和工具。其中&#xff0c;pytest-randomly 插件以其能够引入随机性的特性而备受欢迎。本文将深入探讨 pytest-randomly 插件的应用&#xff0c;以及如何通过引入随机性…

在线项目实习分享:股票价格形态聚类与收益分析

01前置课程 数据挖掘基础数据探索数据预处理数据挖掘算法基础Python数据挖掘编程基础Matplotlib可视化Pyecharts绘图 02师傅带练 行业联动与轮动分析 通过分析申银万国行业交易指数的联动与轮动现象&#xff0c;获得有意义的行业轮动关联规则&#xff0c;并在此基础上设计量…

【NI-DAQmx入门】LabVIEW中DAQmx同步

1.同步解释 1.1 同步基础概念 触发器&#xff1a;触发器是控制采集的命令。您可以使用触发器来启动、停止或暂停采集。触发信号可以源自软件或硬件源。 时钟&#xff1a;时钟是用于对数据采集计时的周期性数字信号。根据具体情况&#xff0c;您可以使用时钟信号直接控制数据采…

基于SSM的驾校预约管理系统

基于SSM的驾校预约管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatis工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 详情 管理员界面 摘要 随着社会的不断发展&#xff0c;驾驶技能的需求逐渐增…

什么是 CAS

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

第一波!2024年1月精选6款实用AI人工智能设计工具合集

大家好&#xff0c;这是进入2024年之后的第一波干货合集&#xff01;这次的干货合集还是以 AI 相关的设计干货开头&#xff0c;这次有了在本地无限制帮你清理图片中元素的 AI 工具&#xff0c;有知名免费图库出品的实时 AI 图片生成工具、将截图直接转化为代码的超强工具&#…

公司官网,选全站定制还是模板建站?

最近更新了公司网站&#xff0c;总算了了一件大事。 虽然很久以前也做网站&#xff0c;但随着技术的发展&#xff0c;以前经常用的dreamwaver、table等形式&#xff0c;不知道被升级了多少代。现在前端同事说起的各种架构&#xff0c;对我来说是云里雾里。只能看懂一点点。 这…

API设计:从基础到优秀实践

在这次深入探讨中&#xff0c;我们将深入了解API设计&#xff0c;从基础知识开始&#xff0c;逐步进阶到定义出色API的最佳实践。 作为开发者&#xff0c;你可能对许多这些概念很熟悉&#xff0c;但我将提供详细的解释&#xff0c;以加深你的理解。 API设计&#xff1a;电子商…

【Spring 篇】基于XML的Spring事务控制详解

Spring框架作为Java开发中的瑞士军刀&#xff0c;提供了许多方便而强大的功能&#xff0c;其中之一就是事务管理。事务是数据库操作中的关键概念&#xff0c;它确保一系列操作要么全部成功&#xff0c;要么全部失败。今天我们将深入探讨基于XML配置的Spring事务控制&#xff0c…

STM32 SPI通信协议1——协议基本原理及管脚定义

SPI介绍 SPI全称为Serial Peripheral interface&#xff0c;译为串行外围设备接口。SPI主要应用在EEPROM&#xff0c;FLASH&#xff0c;实时时钟&#xff0c;AD转换器&#xff0c;还有数字信号处理器和数字信号编码器之间。 SPI是一种高速&#xff0c;全双工&#xff0c;同…

PHP面试小结(20240108)

PHP 部分 1. php的包管理工具是如何实现自动加载的 换句话问&#xff1a;composer 实现原理是什么&#xff1f;spl_autoload_register() 首先&#xff0c;Composer 是 PHP 的一个包管理和包依赖管理的工具 &#xff0c; 打开安装之后生成的 "vendor" 文件, 里面有个…