当前位置: 首页 > news >正文

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

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

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

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;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Python开发深度学习常见安装包 error 解决
  • 【基于轻量型架构的WEB开发】【章节作业】
  • 上传富文本插入文件时报错:JSON parse error: Unexpected character解决办法
  • 半导体器件制造5G智能工厂数字孪生物联平台,推进制造业数字化转型
  • Paragon NTFS for Mac和Tuxera NTFS for Mac,那么两种工具有什么区别呢?
  • SpringBoot 入门实践
  • ConvexHull-凸包-原理-代码实现
  • 【pandas操作:如何写XLSX文档】
  • 游戏陪玩系统源码搭建教程,如何配置陪玩系统的第三方云储存
  • React + React Image支持图像的各种转换,如圆形、模糊等效果吗?
  • 【网络】TCP/IP 五层网络模型:网络层
  • python基础知识(六)--字典遍历、公共运算符、公共方法、函数、变量分类、参数分类、拆包、引用
  • 101. 对称二叉树【同时遍历两棵树】【C++】
  • 数据库———事务及bug的解决
  • python怎么打开编辑器
  • 0基础学习移动端适配
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • css属性的继承、初识值、计算值、当前值、应用值
  • Phpstorm怎样批量删除空行?
  • Vue ES6 Jade Scss Webpack Gulp
  • vue学习系列(二)vue-cli
  • 机器学习 vs. 深度学习
  • 深度学习中的信息论知识详解
  • 项目实战-Api的解决方案
  • 怎样选择前端框架
  • ionic入门之数据绑定显示-1
  • ​比特币大跌的 2 个原因
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • #微信小程序:微信小程序常见的配置传值
  • $.ajax,axios,fetch三种ajax请求的区别
  • (007)XHTML文档之标题——h1~h6
  • (7)STL算法之交换赋值
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (二)Kafka离线安装 - Zookeeper下载及安装
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (转)C#调用WebService 基础
  • (转)Google的Objective-C编码规范
  • (转)负载均衡,回话保持,cookie
  • (转)关于多人操作数据的处理策略
  • ***监测系统的构建(chkrootkit )
  • .NET C# 配置 Options
  • .net core使用EPPlus设置Excel的页眉和页脚
  • .NET Project Open Day(2011.11.13)
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • []Telit UC864E 拨号上网
  • [ajaxupload] - 上传文件同时附件参数值
  • [COI2007] Sabor
  • [CSS]一文掌握
  • [dts]Device Tree机制
  • [Firefly-Linux] RK3568修改控制台DEBUG为普通串口UART
  • [Godot] 3D拾取
  • [Gradle] 在 Eclipse 下利用 gradle 构建系统
  • [jQuery]10 Things I Learned from the jQuery Source
  • [Json.net]快速入门