在上节课我们提交的代码中,还有着一些不足:
如果我们想要运行下面的函数:
void test4()
{Date d1(2023, 5, 5);d1 += -50;d1.Print();Date d2(2023, 5, 5);d2 -= -50;d2.Print();
}
我们发现之前的代码没有考虑day为负数的情况,可以使用assert断言,但是加上day为负数的情况效果更好!
下面为+=的运算符重载函数:
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}// 方法一_day += day;while (_day > GetMonthDay(_year,_month)){_day -= GetMonthDay(_year, _month);++_month;while (_month == 13){++_year;_month = 1;}}return *this;// 方法二// *this = *this + day;// return *this
}
下面为-=的运算符重载函数:
Date& Date::operator-=(int day)
{// 考虑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;
}
对于++和--也有一些价值和应用场景,python中将++和--删去:
例如这里n--执行10次;--n会执行9次;
对于内置类型来说,前置++和后置++的差别不大;
但是对于自定义类型来说差别非常大,需要自己通过函数重载来区分前置--和后置--,且返回值为对象,会构造拷贝函数!因此使用前置类型效率高!
接下来我们再补充一个函数:求两个日期之间的天数!
这里我们用一种比较巧妙的方法:
例如:要求这两个日期之间的天数,我们可以先比较出两个日期谁大谁小?
然后小的每次++,直到++到和大的数字一样大为止!
// d1 -d2
int Date::operator-(const Date& d) // 求两个日期之间的天数!
{// 假设d1的值为最大值Date max = *this;Date min = d;int flag = 1; // 此时代码第一个日期大if (*this < d){Date min = *this;Date max = d;flag = -1;}int day = 0;while (min!=max){++min;day++;}return day * flag;
}
通过返回day*flag巧妙地解决了返回值的正负问题!
补充点:
对于C语言来说,printf只能打印内置类型!无法打印自定义类型!
补充一:流提取函数
C++可以使用cout<<d1;打印自定义类型,但是需要对<<进行运算符重载!
cout<<d1;
流插入操作符是一个双对象的操作符!(这里一个是日期类对象,还有一个是cout,cout是一个类对象---ostream的类对象!)
ostream是库定义的类!(也就是iostream这个头文件里有ostream类,cout是ostream这个类定义的对象!)
cin是istream这个类定义的对象!
为什么cout能够自动识别变量类型?
实际上就是运算符重载!根据输入参数的类型不同调用不同的重载函数!
运算符重载是为了自定义类型支持运算符!与函数重载没有关系!但是两个运算符重载可以构成函数重载!
总结:
- 可以直接支持内置类型是因为库里面实现了;
- 可以直接支持自动识别类型是因为函数重载!
分析下面流输出代码:
void Date::operator<<(ostream& out) //对<<实现运算符重载!
{//这里共有两个对象操作数;// out就是这个cout!还隐含了this指针指向d1;out << _year << "年" << _month << "月" << "日" << _day << endl;
}
但是这个代码无法运行!
需要下面格式调用才能实现:
因为上面我们实现的函数中,一共还有两个参数,第一个参数是this,第二个参数为out;
默认第一个参数为左操作数,第二个参数为右操作数;
因此我们调用函数的顺序会反过来!
void test6()
{Date d1(2023, 5, 5);Date d2(2023, 6, 5);//cout << d1; //d1.operator<<(cout);d1 << cout; //d1.operator<<(cout);d1.operator<<(cout);
}
这两种调用形式都可以,但是看着非常奇怪!
问题: 流插入能不能写成成员函数?
答案:不能,因为这里的Date对象默认占用第一个参数,就是做了左操作数!
写出来就一定是下面的形式!不符合我们的使用习惯!
怎么解决?
将该函数写成全局函数,但是类中private修饰的变量我们没变法获取!
此时我们可以采用下面两种方法:
方法一:通过写public类型的函数获取变量,但是这个变量只能读不能修改!
方法二:使用友元函数
friend void operator<<(ostream& out,const Date&d); //对<<实现运算符重载!
在类中声明,然后在全局域实现函数的定义!
此时可以采用两种调用方法:
cout<<d1;
operator(cout,d1);
总结
在类中定义:
但是调用习惯与我们不相符!
在全局域定义:
但是需要在类中进行友元函数的声明!
注意点:上面的ostream类型不能+const!(流插入的实质就是往cout里面写入数据,如果cout被限制,里面的数据就无法修改,即无法被写入!)
对于连续流插入,不同于以往的连续赋值:
流入的顺序是d2先流入cout中,返回一个cout!然后d3的值再流入cout中!即再打印d3,最后打印d1;
因此,要实现连续的流插入,我们需要对函数体进行修改:
ostream& operator<<(ostream& out, const Date& d) //对<<实现运算符重载!
{//这里共有两个对象操作数;// out就是这个cout!还隐含了this指针指向d1;out << d._year << "年" << d._month << "月" << "日" << d._day << endl;return out;
}
此时即可实现连续的流插入!
友元函数的声明不考虑访问限定符!定义在类中的任何位置都可以!
补充二:流插入函数
同时我们来实现连续的流提取的函数!
流内部参在一些状态机制,我们我们对流进行提取插入等都需要改变流,因此不能使用const限定流的对象!
istream& operator>>(istream& in, Date& d) //对>>实现运算符重载!
{in >> d._year >> d._month >> d._day;return in;
}
还有一个注意点:
void test8()
{Date d1(2023, 14, 5);cout << d1;
}
如果我们调用这里的test8,我们会发现还能正常运行:
14月明显不符合实际,因此我们可以对构造函数进行判断:
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);}
}
同理,我们对输入>>重定向也需要做改动,防止输入非法日期:
且需要将GetMonthDay这个函数修饰为静态的,只有这样我们才能从类中调用!(详细下节将)
istream& operator>>(istream& in, Date& d) //对>>实现运算符重载!
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13 && day >0 && day <= Date::GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}
现在我们如果输入的日期不符合格式,就会由于断言直接报错!
问题:为什么这里的d1可以调用Print函数,但是d2加上const修饰后无法调用print函数?
这是因为d1传递的时候就是Date*,d2传递的时候因为前面有const修饰,所以传递的就是const Date*,但是调用成员函数的时候谜面默认参数是Date* this,从const Date*到Date*发生了权限的放大!
怎么解决呢?
只需要向成员函数中传入const Date*this即可!
但是成员函数中的this是隐含的,我们无法修改它!
因此C++规定在成员函数的后面+const表示修饰this指针!
成员函数后面加上const之后,普通对象和const修饰的对象都可以调用!
问题:能不能所有成员函数都加上const?
原则1:不是!要修改成员变量的函数不能+const!
因为this修饰为const,则*this无法修改,this指向的对象内容无法修改!
但是对于d1 + 100,此时d1的值没有被修改,因此运算符重载+函数可以上const来修饰!
还有下面一种情况:
d1为普通的对象;d2为const被修饰的对象;
因此如果是d2<d1,那么d2(d2不能对内容进行修改)传入对应于Date&(默认this可以对内容进行修改),此时出现权限的放大,因此出错!
但是如果bool函数中参数不+const,那么第一个都编译不过去!
因此对于参数中尽量加上const,此时普通对象可以传,修饰的对象也可以传!
原则2:只要成员函数内部不修饰成员变量,都应该加上const进行修饰,这样子const对象和普通对象都可以调用!
最终版的日期类代码:
date.h
#pragma once
#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:Date(int year = 1, int month = 1, int day = 1);void Print(){cout << _year << "_" << _month << "_" << _day << endl;}bool operator<(const Date& x)const;bool operator==(const Date& x)const;bool operator<=(const Date& x)const;bool operator>(const Date& x)const;bool operator>=(const Date& x)const;bool operator!=(const Date& x)const;// 日期 + 时间的返回值还是一个日期// 实现获取当前月份的天数static int GetMonthDay(int year, int month);// 实现 日期 + 时间 返回日期Date& operator+=(int day);Date operator+(int day)const;Date& operator-=(int day);Date operator-(int day)const;Date operator++(); // 前置++Date operator++(int); // 后置++Date operator--(); // 前置--Date operator--(int); // 前置++int operator-(const Date& d)const; // 求两个日期之间的天数!//int operator-(const Date& d); // 求两个日期之间的天数!private:int _year;int _month;int _day;
};
date.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"data.h"// 分开写拷贝构造函数
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);}
}
bool Date::operator<(const Date& x)const
{if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;
}bool Date::operator==(const Date& x)const
{return _year == x._year&& _month == x._month&& _day == x._day;
}// d1<d2
// this = d1
// x = d2
bool Date::operator<=(const Date& x)const
{return *this < x || *this == x;
}// 大于就是小于等于取反!
bool Date::operator>(const Date& x)const
{return !(*this <= x);
}bool Date::operator>=(const Date& x)const
{return !(*this < x);
}bool Date::operator!=(const Date& x)const
{return !(*this == x);
}
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)
{if (day < 0){return *this -= -day;}// 方法一_day += day;while (_day > GetMonthDay(_year,_month)){_day -= GetMonthDay(_year, _month);++_month;while (_month == 13){++_year;_month = 1;}}return *this;// 方法二// *this = *this + day;// return *this
}Date Date::operator+(int day)const
{// 通过拷贝构造创建一个tmpDate tmp(*this);// 方法二:复用+=tmp += day; // 方法一//tmp._day += day;//while (tmp._day > GetMonthDay(tmp._year, tmp._month))//{// tmp._day -= GetMonthDay(tmp._year, tmp._month);// ++tmp._month;// while (tmp._month == 13)// {// ++tmp._year;// tmp._month = 1;// }//}return tmp;
}Date& Date::operator-=(int day)
{// 考虑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)const
{Date tmp = *this;tmp -= day;return tmp;}Date Date::operator++() // 前置++
{*this += 1;return *this;
}Date Date::operator++(int) // 后置++
{Date tmp = Date(*this);*this += 1;return tmp;
}Date Date::operator--() // 前置--
{*this -= 1;return *this;
}
Date Date::operator--(int) // 后置--
{Date tmp = Date(*this);*this -= 1;return tmp;
}// d1 -d2
int Date::operator-(const Date& d)const // 求两个日期之间的天数!
{// 假设d1的值为最大值Date max = *this;Date min = d;int flag = 1; // 此时代码第一个日期大if (*this < d){Date min = *this;Date max = d;flag = -1;}int day = 0;while (min!=max){++min;day++;}return day * flag;
}
// cout<<d1
//void Date::operator<<(ostream& out, const Date& d) //对<<实现运算符重载!
//{
// //这里共有两个对象操作数;
// // out就是这个cout!还隐含了this指针指向d1;
// out << _year << "年" << _month << "月" << "日" << _day << endl;
//}ostream& operator<<(ostream& out, const Date& d) //对<<实现运算符重载!
{//这里共有两个对象操作数;// out就是这个cout!还隐含了this指针指向d1;out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}istream& operator>>(istream& in, Date& d) //对>>实现运算符重载!
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13 && day >0 && day <= Date::GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS
// 运算符重载有一个自定义类型即可
// 不用全部类型都是自定义类型,例如:日期(自定义类西) + 天数(int)
#include"data.h"
void test1()
{Date d1(2023, 11, 20);d1 + 100;d1.Print();//Date d2(d1+100);Date d2 = d1 + 100; // 上面两种调用方法都可以!d2.Print();d1 += 200;d1.Print();}
void test2()
{Date d1(2023, 11, 20);++d1;d1++;
}void test3()
{Date d1(2023, 5, 5);d1 -= 50;d1.Print();
}
void test4()
{Date d1(2023, 5, 5);Date ret = d1++; // d1.operator++(&d1,0)d1 += -50;d1.Print();Date d2(2023, 5, 5);Date ret2 = ++d2; // d2.operator++(&d2)d2 -= -50; d2.Print();
}
void test5()
{Date d1(2023, 5, 5);Date d2(2023, 6, 5);int a = d2 - d1;cout << a << endl;
}
void test6()
{Date d1(2023, 5, 5);Date d2(2023, 6, 5);Date d3(2023, 7, 5);//cout << d1; //d1.operator<<(cout);//d1 << cout; //d1.operator<<(cout);//d1.operator<<(cout);cout << d1<<d2<<d3;
}void test7()
{Date d1(2023, 5, 5);Date d2(2023, 6, 5);Date d3(2023, 7, 5);//cout << d1; //d1.operator<<(cout);//d1 << cout; //d1.operator<<(cout);//d1.operator<<(cout);cin >> d1 >> d2;cout << d1 << d2 << d3;
}
void test8()
{Date d1(2023, 14, 5);cout << d1;
}
void test9()
{Date d1(2023, 10, 5);cin >> d1;
}
int main()
{test9();return 0;
}