净化工程 技术支持 东莞网站建设,约么同城实时定位搜索引擎,用wordpress搭建网站,wordpress最新编辑器怎么还原文章目录前言1. 日期的合法性判断2. 日期天数#xff08;/#xff09;2.1 和的重载2.2 对于两者复用的讨论3. 前置和后置重载4. 日期-天数#xff08;-/-#xff09;5. 前置- -和后置- -的重载6. 日期-日期7. 流插入重载8. 流提取重载9. 总结10. 源码展示前…
文章目录前言1. 日期的合法性判断2. 日期天数/2.1 和的重载2.2 对于两者复用的讨论3. 前置和后置重载4. 日期-天数-/-5. 前置- -和后置- -的重载6. 日期-日期7. 流插入重载8. 流提取重载9. 总结10. 源码展示前言 在上一篇文章我们学习类和对象的过程中我们不是写了一个日期类嘛。 但是我们之前实现的日期类并不是很完整我们只是借助它来帮大家学习类和对象的知识。 那这篇文章呢我们就在之前的基础上再增添一些功能实现一个比较完整的日期类作为一个练习来帮助我们更好的理解我们之前学过的知识。 这是我们之前一起写的不太完整的日期类
class Date
{
public://构造函数Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}//拷贝构造函数Date(const Date d){_year d._year;_month d._month;_day d._day;}bool operator(const Date d){if (_year d._year)return true;else if (_year d._year _month d._month)return true;else if (_year d._year _month d._month _day d._day)return true;return false;}bool operator(const Date d){return *this d || *this d;}bool operator(const Date d){return !(*this d);}bool operator(const Date d){return !(*this d);}bool operator!(const Date d){return !(*this d);}void Print(){cout _year - _month - _day endl;}bool operator(const Date d){return _year d._year _month d._month _day d._day;}//赋值重载(对于Date类用默认生成的就行)//d1d2(this就是d1d就是d2)/*Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}*/
private:int _year;int _month;int _day;
};那这些实现过的函数我们就不再一一讲解了大家不熟悉的话可以回顾一下上一篇文章。 另外呢我们最终实现的是一个完整的日期类那方便对代码进行维护和管理以及对实现好的日期类进行测试我们还是像之前写数据结构一样放在多个文件中。 1. 日期的合法性判断
我们之前呢已经给该日期类写好了构造函数
Date(int year 1, int month 1, int day 1)
{_year year;_month month;_day day;
}并且还指定了缺省参数那这样的话在实例化一个对象时我们就可以自己给对象指定初值我们输入的日期是啥该对象就会被初始化为对应的日期。 那现在有一个问题如果我们实例化对象时给的日期不合法呢比如
void Datetest1()
{Date d1(2023, 2, 30);d1.Print();
}不合法是不是也判断不出来啊。 那我们就对原来的构造函数做一些补充好吧让它在给对象初始化的时候可以去判断一下对应的日期合不合法。 那要怎么判断呢 给我们一个年月日要判断是否合法是不是要判断月在不在【112】之内以及天数有没有超过当前月的总天数啊。 但是某个月的天数是不是不好确定啊不同月的天数不一样而且要考虑平闰年。 那我们先来写一个获取某年某月天数的函数
int Date::GetMonthDay(int year, int month)
{assert(month 0 month 13);int MonthArr[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 MonthArr[month];}
}不过多解释了相信大家都能看懂。 那有了这个函数我们就可以在构造函数中去判断了
Date::Date(int year, int month, int day)
{if (month 0 month 13 day0 day GetMonthDay(year, month)){_year year;_month month;_day day;}else{cout 日期非法 endl;_year 1;_month 1;_day 1;}
}如果合法了我们再去赋值不合法就还全搞成1要不然就是随机值了。 那这时我们再用非法日期去初始化对象 这样输入的日期不合法就提示了。 2. 日期天数/ 我们说日期日期是不是没啥意义啊但是日期一个天数是不是还有点意义可以理解成这么多天之后是几几年几月几日。 2.1 和的重载
所以接下来我们要实现一个功能就是计算一个日期加了一个天数之后得到的日期 那具体实现的思路呢可以这样搞 首先我们想让自定义类型Date的对象直接和整型相加这里肯定要对进行运算符重载。 我们拿到一个日期一个天数之后先把天数加到日上然后判断此时的日是否超过了当前月的总天数获取月份天数的函数我们之前已经实现过了超过的话就减去当前月的天数然后月份那月份有可能会超过12啊一旦超过就让年然后月置成1。 那当然这肯定是一个循环当我们的日day不再大于当前月的天数则此时得到的就是最终的日期。 我们来写一下代码
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;
}这里*this出了作用域还在所以可以返回引用。 那我们来测试一下 我们看这个计算出来的日期确实是没问题的但是d1100这里是而不是所以d1是不是不应该变啊我们只是把d1100得到的日期赋给了d2但是现在d1也变了。 所以我们这样写其实实现的是啥是不是啊。 所以我们这里重载的应该是
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;
}那还需要有返回值吗最好还是带返回值带返回值的话就可以支持这种情况d2 d1 100; 那刚才实现的是那要这么搞 是不是借助一个临时对象就可以搞定了啊我们只要不改变*this就行了嘛。 Date Date::operator(int day)
{Date tmp(*this);tmp._day day;while (tmp._day GetMonthDay(tmp._year, tmp._month)){tmp._day - GetMonthDay(tmp._year, tmp._month);tmp._month;if (tmp._month 13){tmp._year;tmp._month 1;}}return tmp;
}那就这样测试一下 这次d1没有被改变。 答案也没问题。 那对于的重载 大家有没有注意到我们没有返回引用为什么 因为我们返回的是啥是不是tmp而tmp是一个局部的对象出了作用域就销毁了所以我们不能返回引用。 那有时候呢有的人为了这个地方能够返回引用对tmp加了一个static修饰然后就返回引用。 大家思考一下这样可以吗 我们试一下会发现 第一次没问题但我们第二次在调用是不是就错了啊。 第二次我们还是给d11000天但结果却是2000天之后的。 为什么会这样 原因就在于我们用了static。 我们创建的局部对象tmp被static修饰后就不存在栈区了而是放在静态区了所以静态局部变量出作用域不会被销毁而是保留在静态区因此我们确实可以返回引用了。 但是静态局部变量的初值是在编译期间就指定的所以运行期间不管我们再去调用几次函数tmp都不会被重新初始化而是保留上次的值所以我们第二次的结果才会变成2000天之后。 之前有篇文章详解static的作用大家可以看——链接: link 所以呢这个地方我们不要为了能够返回引用而去随便加static。 2.2 对于两者复用的讨论
那除此之外 大家看的重载与的重载除了多一个临时的局部对象其它的逻辑是不是一样啊所以里面是不是可以复用啊。 Date Date::operator(int day)
{Date tmp(*this);tmp day;return tmp;
}这样是不是就行了啊 那既然可以复用我们是不是也可以考虑先实现然后复用呢 当然可以。 Date Date::operator(int day)
{Date tmp(*this);tmp._day day;while (tmp._day GetMonthDay(tmp._year, tmp._month)){tmp._day - GetMonthDay(tmp._year, tmp._month);tmp._month;if (tmp._month 13){tmp._year;tmp._month 1;}}return tmp;
}那这是我们自己写的现在我们来复用。 Date Date::operator(int day)
{*this *this day;return *this;
}这样不就行了。 那再来思考 到底是复用好呢还是复用好呢 那这里呢其实是我们的第一种即复用更好一点。 因为里面创建临时对象有一次拷贝返回的是值而不是引用又是一次拷贝。 那如果是复用的话就只有里有拷贝但如果是复用的话是不是和里都有拷贝了。 3. 前置和后置重载
刚重载了和那是不是还有前置和后置啊那我们也来实现一下。
先来前置吧 来回忆一下前置的特性是什么 是不是先后使用啊即返回的是之后的值。 举个例子
int a5;
int ba;首先不管前置还是后置a的值肯定都会1那现在a前置先后使用返回之后的值所以b也是6。 那我们来重载一下
//前置
Date Date::operator()
{*this 1;return *this;
}很简单直接复用。 那我们再来重载后置 后置呢是先使用后即返回之前的值。那我们还是可以借助一个临时对象。 但是呢 这里会发现有一个问题 前置和后置没法区分啊它们的参数和函数名是不是一样啊。 欸那这怎么解决啊 那当然是有办法的。 前置和后置都是一元运算符为了让前置与后置形成能正确重载。C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递它的作用就是为了构成重载编译器自动传递。 所以呢这样搞就行了。 我们来实现一下
//后置
Date Date::operator(int)
{Date tmp(*this);*this 1;return tmp;
}我们就搞定了。 那实现完了大家看一下 前置和后置那个效率会高一点。 是不是前置啊因为与后置相比前置没有拷贝啊对不对。 所以对于内置类型来说大家可以认为前置后置没有区别但是对于自定义类型来说能用前置尽量用前置。 我们的前置和后置就搞定了那在调用的地方呢我们正常去用就行了编译器会自动识别匹配。 遇到前置它就会自动去调用前置遇到后置编译器会自动传一个整型参数去调用后置。 4. 日期-天数-/- 上面我们通过重载进而实现计算一个日期一个天数之后是什么日期那是不是还可以搞一下日期-天数计算它的前多少天是什么日期。 那我们先来重载- 那思路和上面加一个天数也是类似的我们先用日减去传过来的天数如果减完之后0说明当前日期是不合法的那怎么办就去加上一个月的天数然后月–当然要注意判断会不会减到上一年然后还0的话就继续知道日0循环结束。 Date Date::operator-(int day)
{_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this;
}测试一下 没问题。 然后我们再重载一下- 那是不是简单啊直接复用-。 Date Date::operator-(int day)
{Date tmp(*this);tmp - day;return tmp;
}测试 一下
看起来好像没什么问题了但是如果这样呢 这里减一个负数会发现结果就错了。 为什么呢大家可以调试观察 会发现这里根本不会进入循环直接就返回了。 所以针对负数的情况我们要单独处理一下
Date Date::operator-(int day)
{if (day 0){*this -day;return *this;}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this;
}我们-是复用-的所以这里在-里面处理。 -一个-100就相当于一个100嘛。 那同样对于和我们也要处理一下 那我们来处理一下
Date Date::operator(int day)
{if (day 0){*this - -day;return *this;}_day day;while (_day GetMonthDay(_year, _month)){_day - GetMonthDay(_year, _month);_month;if (_month 13){_year;_month 1;}}return *this;
}这样是不是就行了一个-100就相当于-一个100嘛。 5. 前置- -和后置- -的重载
那有了上面的练习再实现前置- -和后置- -不是soeasy嘛。
前置- -
Date Date::operator--()
{*this - 1;return *this;
}先- -后使用返回- -之后的值。 后置- -
Date Date::operator--(int)
{Date tmp(*this);*this - 1;return tmp;
}先使用后- -返回- -之前的值。 6. 日期-日期
上面我们搞了日期加减天数那两个日期相减是不是也有意义啊可以得出这两个日期之间差了多少天。
那如何计算两个日期之间相差的天数呢 那这里给大家提供一种比较好实现的思路 我们拿到两个日期之后先把较小的那个日期找出来然后用一个计数器统计较小日期的次数当两个日期相等时停止的次数就是相差的天数。 另外如果是第一个日期大于第二个结果应该是整数如果第二个大于第一个应该是负数。 我们来实现一下
//日期-日期
int Date::operator-(Date d)
{Date max *this;Date min d;int flag 1;if (*this d){max d;min *this;flag -1;}int n 0;while (min ! max){min;n;}return n * flag;
}没有问题。 7. 流插入重载 那我们现在打印一个日期类对象的时候是不是都是去调用我们写的Print函数啊那我们能不能想个办法打印日期类也能直接像这样打印 使用我们之前学的cout去打印。 大家记不记得 我们之前文章里学习C输入输出的时候其实只是浅浅的提了一下如何去用并没有对cout、和进行过多讲解。 因为以我们那时的知识储备还不能很好的理解 那我们现在就可以再尝试多理解一点东西 我们发现我们的自定义类型想用这些运算符是不是都要重载啊除了赋值运算符以及取地址运算符因为赋值重载和取地址重载是类的6个默认成员函数之中的我们不写编译器会自动生成一个。但是在有些情况下根据实际情况我们还是需要自己写。 这是我们上一篇学习的知识不用过多说明了。 然后呢我们还说C里这样输入输出可以自动识别类型。 那为啥可以自动识别类型其实是因为它这里对进行了函数重载。 为什么我们的内置类型可以直接用因为库里面已经对这些类型都进行了重载所以可以自动识别类型是根据操作数的不同类型进行匹配。 那我们现在自定义类型想用怎么办是不是需要自己重载啊。 那我们接下来就重载一下好吧
void operator(ostream out);那想用是不是得使用cout两个操作数一个cout一个是我们要打印得对象cout是啥 上面说了它是一个ostream 类型得对象所以我们把cout作为参数传过去。 那这里out就用来接收coutthis指针指向得就是我们要打印得类对象。 来实现一下
void Date::operator(ostream out)
{out _year _month _day endl;
}这样是不是就行了啊它的成员变量都是内置类型我们就可以使用直接打印了。 我们测试一下 但是我们使用的时候发现报错了。 这里操作数是不是写反了为什么 对于函数重载来说两个参数的话第一个参数是左操作数第二个参数是右操作数。 但是对于类成员函数来说第一个参数是不是隐藏的this指针啊它指向我们调用该函数的对象所以这里第一个参数是Date对象的地址。 那我们要调用的话应该这样写 但是这样写是不是好别扭啊。 怎么让它像内置类型那样使用啊。 那我们就要想办法让cout成为第一个参数怎么做呢 把函数重载写到类外面不就行了是吧。 没有说运算符重载不能写到类外面啊我们写到类里面是为了啥是不是只是为了可以访问private修饰的成员变量啊 那我们现在重载到外面 那现在面临的问题是啥 是在类外不能访问私有的成员变量那怎么办 可以把成员变量变成共有的public但这样是不是就不能很好的保证封装性了 或者可以提供Get方法但C一般不喜欢这样搞。 那还有什么方法呢 就是用友元函数。 那怎么做呢 我们把函数声明再放到类里面一份但在前面加一个friend就行了 那这下我们就可以正常使用了
但是呢 这里不支持连续的流插入。为什么呢 因为我们重载的没有返回值 那应该返回啥 是不是返回cout呀让它能够继续打印后面的。 我们看其实库里面就是这么搞的 ostream operator(ostream out, Date d)
{out d._year - d._month - d._day endl;return out;
}这样就行了。 8. 流提取重载
那紧接着我们再来重载一下流提取
istream operator(istream in, Date d)
{in d._year d._month d._day;return in;
}测试一下 就完成了。
9. 总结
那最后呢还要给大家说明一点 我们在之前的类和对象第一篇其实就提到了 就是类的成员函数如果直接定义在类里面编译器是会将其当成内联函数的不管你有没有加inline关键字。 那我们在学习内联函数的时候也说了 一般建议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数实现成内联函数。 所以说类里面的成员函数如果规模较小适合做内联函数的直接定义在类里面。 不过我们今天写的这个日期类里面我是所有成员函数的声明和定义都分离了大家以后可以根据实际情况有些成员函数直接定义在类里面。 但是我们说了内联函数不是只是对编译器的一个建议嘛如果规模较大的函数就算我们实现成内联函数编译器也不一定按照内联函数处理。 那在类里面也是这样就算我们把全部的类成员函数都直接定义在类里面也不一定会全部当做内联函数处理编译器也还是会看它具体适不适合做内联。 另外还有一点 上一篇文章我们不是还学习了const成员函数嘛大家还可以看一看我们日期类的这么多成员函数哪一个在函数内部不需要改变调用它的对象是不是把它实现成const成员函数也是比较好的。 10. 源码展示
最后把完整的源码给大家展示一下
Date.h
#pragma once
#include iostream
#include stdbool.h
#include assert.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);//构造函数Date(int year 1, int month 1, int day 1);//拷贝构造函数//Date(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator(const Date d);bool operator!(const Date d);void Print();bool operator(const Date d);Date operator(int day);Date operator(int day);//前置Date operator();//后置Date operator(int);Date operator-(int day);Date operator-(int day);Date operator--();Date operator--(int);//日期-日期int operator-(Date d);//赋值重载(对于Date类用默认生成的就行)//d1d2(this就是d1d就是d2)/*Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}*/
private:int _year;int _month;int _day;
};
//
ostream operator(ostream out, const Date d);
istream operator(istream in, Date d);Date.cpp
#define _CRT_SECURE_NO_WARNINGS
#include Date.hint Date::GetMonthDay(int year, int month)
{assert(month 0 month 13);int MonthArr[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 MonthArr[month];}
}
//构造函数
Date::Date(int year, int month, int day)
{if (month 0 month 13 day0 day GetMonthDay(year, month)){_year year;_month month;_day day;}else{cout 日期非法 endl;_year 1;_month 1;_day 1;}
}
//拷贝构造函数
//Date::Date(const Date d)
//{
// _year d._year;
// _month d._month;
// _day d._day;
//}
bool Date::operator(const Date d)
{if (_year d._year)return true;else if (_year d._year _month d._month)return true;else if (_year d._year _month d._month _day d._day)return true;return false;
}
bool Date::operator(const Date d)
{return *this d || *this d;
}
bool Date::operator(const Date d)
{return !(*this d);
}
bool Date::operator(const Date d)
{return !(*this d);
}
bool Date::operator!(const Date d)
{return !(*this d);
}
void Date::Print()
{cout _year - _month - _day endl;
}
bool Date::operator(const Date d)
{return _year d._year _month d._month _day d._day;
}//Date Date::operator(int day)
//{
// Date tmp(*this);
// tmp._day day;
// while (tmp._day GetMonthDay(tmp._year, tmp._month))
// {
// tmp._day - GetMonthDay(tmp._year, tmp._month);
// tmp._month;
// if (tmp._month 13)
// {
// tmp._year;
// tmp._month 1;
// }
// }
// return tmp;
//}
//
//Date Date::operator(int day)
//{
// *this *this day;
// return *this;
//}Date Date::operator(int day)
{if (day 0){*this - -day;return *this;}_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)
{Date tmp(*this);tmp day;return tmp;
}//前置
Date Date::operator()
{*this 1;return *this;
}
//后置
Date Date::operator(int)
{Date tmp(*this);*this 1;return tmp;
}
Date Date::operator-(int day)
{if (day 0){*this -day;return *this;}_day - day;while (_day 0){--_month;if (_month 0){--_year;_month 12;}_day GetMonthDay(_year, _month);}return *this;
}
Date Date::operator-(int day)
{Date tmp(*this);tmp - day;return tmp;
}Date Date::operator--()
{*this - 1;return *this;
}
Date Date::operator--(int)
{Date tmp(*this);*this - 1;return tmp;
}
//日期-日期
int Date::operator-(Date d)
{Date max *this;Date min d;int flag 1;if (*this d){max d;min *this;flag -1;}int n 0;while (min ! max){min;n;}return n * flag;
}
//
ostream operator(ostream out, const Date d)
{out d._year - d._month - d._day endl;return out;
}
istream operator(istream in, Date d)
{in d._year d._month d._day;return in;
}Test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include Date.hvoid Datetest1()
{Date d1(2023, 2, 15);d1.Print();Date d2 d1 (-100);d2.Print();/*Date d3 d1;d3.Print();*/
}
void Datetest2()
{Date d1(2023, 2, 16);d1.Print();Date d2 d1--;d1.Print();d2.Print();}
void Datetest3()
{Date d1;cin d1;cout d1 endl;}
int main()
{//Datetest3();return 0;
}那这篇文章就到这里欢迎大家指正 下一篇文章我们会对类和对象再做一些补充和收尾