在上节课我们提交的代码中,还有着一些不足:
如果我们想要运行下面的函数:
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
{
// 通过拷贝构造创建一个tmp
Date 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;
}