C++第八节课 日期类的补充

在上节课我们提交的代码中,还有着一些不足:

如果我们想要运行下面的函数:

cpp 复制代码
void test4()
{
	Date d1(2023, 5, 5);
	d1 += -50;
	d1.Print();
	Date d2(2023, 5, 5);
	d2 -= -50;
	d2.Print();
}

我们发现之前的代码没有考虑day为负数的情况,可以使用assert断言,但是加上day为负数的情况效果更好!

下面为+=的运算符重载函数:

cpp 复制代码
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
}

下面为-=的运算符重载函数:

cpp 复制代码
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次;

对于内置类型来说,前置++和后置++的差别不大;

但是对于自定义类型来说差别非常大,需要自己通过函数重载来区分前置--和后置--,且返回值为对象,会构造拷贝函数!因此使用前置类型效率高!

接下来我们再补充一个函数:求两个日期之间的天数!

这里我们用一种比较巧妙的方法:

例如:要求这两个日期之间的天数,我们可以先比较出两个日期谁大谁小?

然后小的每次++,直到++到和大的数字一样大为止!

cpp 复制代码
// 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;打印自定义类型,但是需要对<<进行运算符重载!

cpp 复制代码
cout<<d1;

流插入操作符是一个双对象的操作符!(这里一个是日期类对象,还有一个是cout,cout是一个类对象---ostream的类对象!)

ostream是库定义的类!(也就是iostream这个头文件里有ostream类,cout是ostream这个类定义的对象!)

cin是istream这个类定义的对象!

为什么cout能够自动识别变量类型?

实际上就是运算符重载!根据输入参数的类型不同调用不同的重载函数!

运算符重载是为了自定义类型支持运算符!与函数重载没有关系!但是两个运算符重载可以构成函数重载!

总结:

  • 可以直接支持内置类型是因为库里面实现了;
  • 可以直接支持自动识别类型是因为函数重载!

分析下面流输出代码:

cpp 复制代码
void Date::operator<<(ostream& out)  //对<<实现运算符重载!
{
	//这里共有两个对象操作数;
	// out就是这个cout!还隐含了this指针指向d1;
	out << _year << "年" << _month << "月" << "日" << _day << endl;
}

但是这个代码无法运行!

需要下面格式调用才能实现:

因为上面我们实现的函数中,一共还有两个参数,第一个参数是this,第二个参数为out;

默认第一个参数为左操作数,第二个参数为右操作数;

因此我们调用函数的顺序会反过来!

cpp 复制代码
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类型的函数获取变量,但是这个变量只能读不能修改!

方法二:使用友元函数

cpp 复制代码
	friend void operator<<(ostream& out,const Date&d);  //对<<实现运算符重载!

在类中声明,然后在全局域实现函数的定义!

此时可以采用两种调用方法:

cpp 复制代码
cout<<d1;
operator(cout,d1);

总结

在类中定义:

但是调用习惯与我们不相符!

在全局域定义:

但是需要在类中进行友元函数的声明!

注意点:上面的ostream类型不能+const(流插入的实质就是往cout里面写入数据,如果cout被限制,里面的数据就无法修改,即无法被写入!)

对于连续流插入,不同于以往的连续赋值:

流入的顺序是d2先流入cout中,返回一个cout!然后d3的值再流入cout中!即再打印d3,最后打印d1;

因此,要实现连续的流插入,我们需要对函数体进行修改:

cpp 复制代码
ostream& operator<<(ostream& out, const Date& d)  //对<<实现运算符重载!
{
	//这里共有两个对象操作数;
	// out就是这个cout!还隐含了this指针指向d1;
	out << d._year << "年" << d._month << "月" << "日" << d._day << endl;
	return out;
}

此时即可实现连续的流插入!

友元函数的声明不考虑访问限定符!定义在类中的任何位置都可以!

补充二:流插入函数

同时我们来实现连续的流提取的函数!

流内部参在一些状态机制,我们我们对流进行提取插入等都需要改变流,因此不能使用const限定流的对象!

cpp 复制代码
istream& operator>>(istream& in, Date& d)  //对>>实现运算符重载!
{
	in >> d._year >> d._month >> d._day;
	return in;
}

还有一个注意点:

cpp 复制代码
void test8()
{
	Date d1(2023, 14, 5);
	cout << d1;
}

如果我们调用这里的test8,我们会发现还能正常运行:

14月明显不符合实际,因此我们可以对构造函数进行判断:

cpp 复制代码
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这个函数修饰为静态的,只有这样我们才能从类中调用!(详细下节将)

cpp 复制代码
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

cpp 复制代码
#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

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

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;
}
相关推荐
努力变厉害的小超超8 分钟前
ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
笔记·鸿蒙
秃头佛爷10 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
待磨的钝刨11 分钟前
【格式化查看JSON文件】coco的json文件内容都在一行如何按照json格式查看
开发语言·javascript·json
Lary_Rock1 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
浮生如梦_1 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
云飞云共享云桌面3 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法