感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步
文章目录
前言
我们前面讲了类与对象的知识、今天我们就用类与对象的知识来完成一个应用,日期类,可以把它理解成一个日期计算机,可以来计算从今天到昨天或者是到一些指定的天数,需要好久等一些运算。
一、日期类的.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) { assert(month > 0 && month < 13); //为什么要static,因为要频繁调用,每次都要创建,所以直接放到全局去 static int monthDay[13] = { -1.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 monthDay[month]; } } Date(int year = 2024, int month = 1, int day = 1); bool CheckDate(); 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; void print(); Date& operator+=(int day); Date operator+(int day) const; Date& operator-=(int day); Date operator-(int day) const; // d1 - d2 int operator-(const Date& d) const; Date& operator++(); Date operator++(int); Date& operator--(); Date operator--(int); // 流插入 // 不建议,因为Date* this占据了⼀个参数位置,使用d<<cout不符合习惯 //void operator<<(ostream& out); private: int _year; int _month; int _day; };
可以发现类中为什么有一个成员函数,我把它实现了,我没有把它放到另一个文件里面去实现函数,因为这个函数他是获取天数的一个函数,在后期我需要频繁调用这个函数,所以在类里做为内联函数去实现。
1.1GetMonthDay
这个函数他用来干什么的呢?就是用来获得每个月的天数的,因为在这个日期计算机里面有很多年份。有些时候还会有闰年,我们要来判断闰年,1月2月3月每个年份的天数还有差别,所以我们要专门来设置一个这个函数,用脑袋想确实很难,但其实实现还很简单,他的这个函数就是获得联合月要输出对应的天数是多少?所以月份肯定要输正确,前面先来一个断言,用static定义一个数组,为什么要static,因为要频繁调用,每次都要创建,所以直接放到全局去,注意只有一个细节,我第一个变量-1,我后面再往后面放正确的变量,因为数组他是从下标为零开始的,然后就是判断闰年这个讲过很多遍了,如果是闰年,那就返回29,如果不是的话就返回对应下标的那个天数
int GetMonthDay(int year, int month) { assert(month > 0 && month < 13); //为什么要static,因为要频繁调用,每次都要创建,所以直接放到全局去 static int monthDay[13] = { -1.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 monthDay[month]; } }
二.日期类的.cpp文件
2.1Print,CheckDate和构造函数
首先要实现的就是最简单的打印和检查函数,这两个也不需要用到运算符重载,这两个很简单,就不细讲了,打印就是直接打印,检查就是一些月份,天数和年份不正常的就不允许输入
void Date::print() { cout << _year << "-" << _month << "-" << _day << endl; } bool Date::CheckDate() { if (_month > 12 || _month < 0 || _day<0 || _day>GetMonthDay(_year, _month) || _year < 0) { return false; } else return true; } Date::Date(int year, int month, int day) { _year=year; _month=month; _day=day; if (!CheckDate()) { cout << "?期非法" << endl; } }
2.2operator+=,+,-=,-函数
这个函数也是我们第一个实现的运算符重载函数,让我们来看看他是怎么个事,首先根据头文件的定义来看,就发现这四个函数有一些区别
Date& operator+=(int day); Date operator+(int day) const; Date& operator-=(int day); Date operator-(int day) const;
可以发现,有些用了引用返回,有些没有,有些加了const,有些没加,这是为什么???
仔细想一想本质加等最后是不是还是原来的那个对象,而加是不是构造出了一个全新的东西?加等他本身的对象会跟着改变,而加本身的对象不会改变。减与减等同理。又因为成员函数中存在this指针,他在结尾加的const,实际上是加给this指针的,this指针由 Date* const this 变为 constDate* const this,意思就是说,那类里面的值是不能改变的,那上面那个引用返回意思是说我最后这个函数要得到的是我类里面的东西,所以用了引用。
实现+=
先让类中的天数等原来的天数,如果中的天数大于本月份的天数的话,说明天数溢出了,我们让这个大的天数减去原来的天数,然后月份加加,如果这个时候的月份等于13的话,说明月份满了,这年份加加把月份置为一,因为这个是加等,所以最后要返回的是我类中的那个成员变量,用*this引用返回
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)const { Date tmp = *this; tmp += day; return tmp; }
实现-=
减等和加等差不多,只不过思路不一样,用类中的天数去减等参数,如果最终的天数要小于零的话,说明天数少了,要找前面的月份去借1,所以月份减减,如果月份等于零,说明月份已经减完了,然后让年份接着减,月份赋为12,最后再让天数加等上年份月份所对应的那个天数,直到天数大于零为止,最后返回自己*this
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; }
2.3operator < , == , <= , > , >= ,!=函数
其实,看似有6个函数,其实我们只要实现2个就可以了,其他的都可以去复用
我们先观察头文件,可以看出,这六个函数的生命模式都是一样的,为什么呢?
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;
因为比较的运算符存在是不需要改变变量的,所以一个外部的const,是不允许改变内部的变量,参数的const是不能改变外部的变量
小于的比较很简单,年与年比,如果年是对的话,在月份比月份,如果月份也是对的话,就天数去比,那等于就更不用说了
bool Date::operator<(const Date& d) const { if (_year < d._year) { return true; } else if (_year == d._year) { if (_month == d._month) { return _day < d._day; } } }
bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; }
复用,很简单,看一下就懂了,就不啰嗦了
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); }
2.4前置++operator,与后置operator++的区别(——)
我们先观察头文件,可以看出,这2个函数不一样为什么?
Date& operator++(); Date operator++(int); Date& operator--(); Date operator--(int);
int也不管,为什么一个用了引用返回,一个没有用?这其实也能看出那个是前置++,那个是后置++了,因为前置是先++在使用,那我在成员函数里,++完了之后,我直接返回*this,所以我要引用返回,而后置,我还要先拷贝一份,在++然后返回拷贝置,所以前置++会更快捷
//++d Date& Date::operator++() { *this+=1; return *this; } //d++ Date Date::operator++(int x) { Date tmp = *this; *this += 1; return tmp; }
为了区分,构成重载,给后置++,强行增加了一个int形参 /这里不需要写形参名,因为接收值是多少不重要,也不需要用,这个参数仅仅是为了跟前置++构成重载区分
减减同理
Date& Date::operator--() { *this -= 1; return *this; } Date Date::operator--(int x) { Date tmp = *this; *this -= 1; return tmp; }
2.5日期的相减
日期与日期相减,那为什么没有日期与日期的加呢?因为加是没有意义的,日常生活中用不到,而减却很重要,可以算出从你出生到现在,过了多少天。
观察头文件,可以看出,有两个const,说明这两个变量本身是不能改变的
int Date::operator-(const Date& d) const
先要找出最大值,假设法,然后循坏妙解,小的一只加加,知道达到大的
int Date::operator-(const Date& d) const { int n = 0; int flag = 1; Date max = *this; Date min = d; if (*this < d) { Date mim = *this; Date max = d; flag = -1; } while (min != max) { ++min; ++n; } return n * flag; }
2.6流插入和流提取
错误案例:cout<<d
因为Date* this占据了⼀个参数位置,void operator<<(ostream& out);
改正d<<cout,但是这样不符合习惯
所以重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第二个形参位置当类类型对象。
ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "⽉" << d._day << "⽇" << endl; return out; } istream& operator>>(istream& in, Date& d) { cout << "请依次输⼊年月日:>"; in >> d._year >> d._month >> d._day; if (!d.CheckDate()) { cout << "日期非法" << endl; } return in; }
这样就一一对应了,cout<<d,cout->ostream& out,d->const Date& d
三.日期类的.测试文件
void TestDate1() { Date d2 = d1 + 30000; d1.Print(); d2.Print(); Date d3(2024, 4, 14); Date d4 = d3 - 5000; d3.Print(); d4.Print(); Date d5(2024, 4, 14); d5 += -5000; d5.Print(); } void TestDate2() { Date d1(2024, 4, 14); Date d2 = ++d1; d1.Print(); d2.Print(); Date d3 = d1++; d1.Print(); d3.Print(); /*d1.operator++(1); d1.operator++(100); d1.operator++(0); d1.Print();*/ } void TestDate3() { Date d1(2024, 4, 14); Date d2(2034, 4, 14); int n = d1 - d2; cout << n << endl; n = d2 - d1; } void TestDate4() { Date d1(2024, 4, 14); Date d2 = d1 + 30000; // operator<<(cout, d1) cout << d1; cout << d2; cin >> d1 >> d2; cout << d1 << d2; } void TestDate5() { Date d1(2024, 4, 14); d1.Print(); //d1 += 100; d1 + 100; Date d2(2024, 4, 25); d2.Print(); d2 += 100; d1 < d2; d2 < d1; } int main() { return 0; }
总结
这个练习,巩固了之前类与对象的学习,对C++这门编程语言有更高的理解与认知