日期类的实现(C++)
目录
注意:
1.作用域
2.实例化
3.this指针
4.std
1).std是什么?
2).为什么将cout放到名字空间std中?
3).std都是什么时候使用?
5.C语言“/”和“%”运算符详解
6.运算符重载
一、实现日期类需要注意日期的合法性
二、日期类的实现
1.打印日期
2.拷贝构造函数
3.获取每月的天数
4.判断日期是否合法
5.判断闰年
6.构造函数
7. ==运算符重载
8. <运算符重载
9. <=、>、>=、!= 运算符重载
10. +、+=运算符重载
11.-、-=运算符重载
12.前置++、--与后置的++、--
13.&运算符重载
14.const
15.日期相减
16.流插入流提取
16.1 对“<<”和“>>”重载的函数形式如下:
16.2 为什么只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数?
16.3 为什么要声明友元呢?
16.4 为什么这个不写在Data.h里?
三、详细代码
1. Data.cpp
2 Data.h
3.Test.h
注意:
1.作用域
2.实例化
person类是没有空间的,只有person类实例化出来的对象才具有存储类成员变量的实际物理空间 。
3.this指针
this指针的类类型是*const
【C++】std::是什么? - mhq_martin - 博客园 (cnblogs.com)
4.std
1).std是什么?
std:: 是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
对象cout是标准函数库所提供的对象,而标准库在名字空间中被指定为std,所以在使用cout的时候要加上std::。这样编译器就会明白我们调用的cout是名字空间std中的cout。
2).为什么将cout放到名字空间std中?
是因为像cout这样的对象在实际操作中或许会有好几个,比如说你自己也可能会不小心定义了一个对象叫cout,那么这两个cout对象就会产生冲突。
3).std都是什么时候使用?
一般来说,std都是要调用C++标准库时,要写上std;
使用非标准库文件iostream.h,不用写
5.C语言“/”和“%”运算符详解
原文链接:https://blog.csdn.net/admin_maxin/article/details/53330674
除法运算符"/":二元运算符,具有左结合性。参与运算的量均为整型时,结果为整型,舍去小数。如果运算量中有一个为实型,结果为双精度实型。除号的正负取舍和一般的算数一样,符号相同为正,相异为负。例如:5/2=2,1/2=05/2.0=2.5 取模运算符"%":二元运算符,具有左结合性。参与运算的量均为整型。并且参与运算的量可以为负数。取模运算的结果等于两个数相除后的余数。例如:5%2=1,1%2=15%2.0和5.0%2//error C2297: “%”: 非法,右操作数包含“double”类型int a = 23%-3;//a = 2int b = -23%3;//b = -2//注明:求余符号的正负取舍和被除数符号相同当前面的数小于后面的数时,其实求余运算可以看成(如下),如果a<b的话,这样的商为0,余数就是aa%b=a-(int)(a/b)*b 1%2=1 2%5=2 a % b 这个关系表达式a%b == a-(int)(a/b)*b 是这么解释的:先运算(a/b)然后a-((a/b的值)乘以b) 例1. 50%250除以2=25结果为整数,则取值为0 (原因就是50除以2的值是整数,余数为0)
例2. 7%27除以2=3.5则还是用3乘以2=6再用7-6,结果就是余数 1
C语言中,任意一个正整数对1求余结果为0还是1?是0,如24%1=0
6.运算符重载
https://blog.csdn.net/qq_42683011/article/details/102087764
基础应用:
重载运算符最需要考虑的即为参数与返回值问题
(这里以operator = 为例):
参数:
一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用, 如:
MyStr& operator =(const MyStr& str);
加const是因为:
我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
加上const,对于const的和非const的实参,函数就能接受;如果不加,就只能接受非const的实参。
用引用是因为:
这样可以避免在函数调用时对实参的一次拷贝,提高了效率
注意:
上面的规定都只是推荐,可以不加const,也可以没有引用,甚至参数可以不是函数所在的对象。
返回值:
一般地,返回值是被赋值者的引用(但有时返回左值还是右值需要相当的考虑),即*this
MyStr& operator =(const MyStr& str);
这样在函数返回时避免一次拷贝,提高了效率。
更重要的,根据赋值运算符的从左向右的结合律, 可以实现连续赋值,即类似a=b=c
如果返回的是值,则执行连续赋值运算后后头得到的将是一个匿名副本, 为不可更改的右值,就是说是const类型, 再执行=c就会出错。
注意:
这也不是强制的,完全可以将函数返回值声明为void,然后什么也不返回,只不过这样就无法连续赋值,所以具体的返回值与参数的设定完全取决于需求, 是非常值得设计者考量的。
一、实现日期类需要注意日期的合法性
#include<iostream>
using namespace std;
class Data
{
public:
(能被4整除却不能被100整除或能被400整除的年份就是闰年!
取模运算符"%"
除法运算符"/")int isLeapYear(int year){if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)){return 1;}return 0;}int GetMonthDay(int year, int month){(用0是为了让31是1月份)int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && isLeapYear(year) == 1)//平年二月28,闰年29天{return 29;}else{return MonthDayArray[month];//下标与月份是一致的}}Data(int year = 1, int month = 1, int day = 1){(考虑时间的合法性,一年只有12个月)if (year >= 1 && month <= 12 && month >= 1 && day >= 1&& day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}}Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}void print(){cout << _year << "-" << _month << "-" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Data d1(2022, 10, 14);Data d2(2022, 10, 50);return 0;
}
二、日期类的实现
如果我们函数定义与实现分开写一定要注意上面所讲的注意点:
下面将根据我的思路一步步实现日期类:
1.打印日期
void print() const{cout << _year << "-" << _month << "-" << _day << endl;}
2.拷贝构造函数
Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}
3.获取每月的天数
GetMonthDay会被频繁的调用,加上static,防止不停的开辟数组,直接放在静态区,再加上const,更安全,此外定义数组的时候用0让31是1月份,即让下标与月份一致。
int GetMonthDay(int year, int month);
int Data::GetMonthDay(int year, int month)
{assert(year >= 0 && month > 0 && month < 13);const static int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && isLeapYear(year) == 1){return 29;}else{return MonthDayArray[month];}
}
4.判断日期是否合法
bool CheckDate(){if (_year < 1 || _month > 13 || _month < 1 ||_day < 1 || _day > GetMonthDay(_year, _month)){return false;}return true;}
5.判断闰年
bool isLeapYear(int year){if ((year % 400 == 0) || (year % 4 == 0 && year / 100 != 0)){return true;}else{return false;}}
6.构造函数
Data.h(在类里面声明)
Data(int year = 1, int month = 1, int day = 1);
Data.cpp(在类外定义)
//声明给了缺省参数,定义就不用给了
Data::Data(int year, int month, int day)
{if (year >= 1 && month <= 12 && month >= 1 && day >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}
}
7. ==运算符重载
注意隐含的this指针:
bool operator==(const Data& d);
bool Data::operator==(const Data& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
为什么可以访问私有?
因为这个函数虽然在类外面定义,在类里面声明,但是实际他还是在类里面的成员函数,类的成员函数是可以访问私有的,但其实该函数本质上访问的是成员,类里面那个private只是声明,就是说实际上访问的是d1或者d2,注意:变量的声明和定义的区别是是否开辟空间。
8. <运算符重载
建议成员函数中不修改成员变量的成员函数,都可以加上const,这样普通对象和const对象都能调用。
bool Data::operator<(const Data& d) const//声明和定义分离需要注意
{if ((_year < d._year)|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day)){return true;}else{return false;}
}
9. <=、>、>=、!= 运算符重载
<=就是<或者=,举个例子,比如d1<d2,日期类比大小,那么实际上就是*this < d,那么<=的运算符重载,我们直接写成下代码即可。同理别的符号也可以复用。
bool operator<=(const Data& d){return *this < d || *this == d;}
bool operator>=(const Data& d){return !(*this < d);}bool operator!=(const Data& d){return !(*this == d);}bool operator>(const Data& d){return !(*this <= d);}
像以上这种几行的代码,不超过三行,直接定义声明放在类里面即可,因为类里面的成员函数默认是内联函数。
10. +、+=运算符重载
Data operator+(int day) const;
对于d1+4的思路:比如2022.10.27+4,变成2022.11.1,月加1,日27+4=31-30=1,减掉的是10月的天数 。所以:先进行天数的加,在减去当月的天数,再让月加1
_day -= GetMonthDay(_year, _month);_month++;
但是如果d1+40了,2022.10.27 +40,27+40-30=37>max(31),那么进位就不是进1了,所以需要加入while循环,此时变成2022.11.37,那么37-31=6,进1,月份再加1,变成2022.12.6
那如果+70,那么相当于在上基础上还要+30,即2022.12.6+30,6+30变成36,36-31 =5,变成2022.13.5,那么肯定不对,月份最多12月,那么需要加上判断,如果月份等于13了,那么就让年进1,让month回位到1。
Data Data::operator+(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}
但是如果上代码这么写,会同时改变d1和d2,d1 + 100 --》 d1.operator+(day),d1就是this,然后我们返回的也是*this,即返回的是d1,即我让d1+100,又赋值给了新的d1,所以d1和d2同时改变了。这样是不是刚好可以作为+=的运算符重载。但是可以优化一下:
思考:出了作用域*this还在不在?在
可以这样理解:d1还在,,所以加上&,即引用返回会更合适。
//注意d1+=100,自己也会改变,而且有返回值,因为有些地方需要连续的+=
Data& Data::operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}
那么实现+怎么修改了:
我们先拷贝d1,保存不变,再改变拷贝的d1不就可以了吗?Data ret(*this),拷贝构造函数,一个存在的对象去初始化另一个要创建的对象,然后改变ret即可。
注意:d1+100,是将d1的地址传给了this,*this得到的就是d1本身了。即调用成员函数时是将对象的地址作为实参传递给 this,比如Data print(Data const* this);
Data& Data::operator+(int day)
{Data ret(*this);//把*this拷贝给ret,再改变retret._day += day;while (ret._day > GetMonthDay(ret._year, ret._month)){ret._day -= GetMonthDay(ret._year, ret._month);ret._month++;if (ret._month == 13){++ret._year;ret._month = 1;}}return ret;
}
注意传值返回,返回的不是ret,是ret的拷贝,是临时对象,是带有const属性的,传值返回就要调用拷贝构造,做Data(Data& d)拷贝构造的参数,是权限的放大。所以拷贝构造加上了const。
若加上Data&如上代码一样,虽然可以避免拷贝构造,但是还是会出现问题:
Data&作为返回值是不可以的,因为ret是创建的临时变量,出了作用域就栈帧销毁了,如果空间被占用,就会出错,如果没有占用倒是和Data做返回值是一样的。
思考:但是我们已经写了+=了,又写了一边+,代码非常重复,可不可以封装了?可以。
Data& Data::operator+=(int day)
{*this = *this + day;//d1 += 100;//这里复用了+即Data Data::operator+(int day)return *this;
}
于是我们再思考一个问题:我是用+去复用+=好,还是用+=复用+更好了?
以下即是用+=去复用+的代码实现:
Data Data::operator+(int day) const
{Data ret(*this);ret += day;return ret;
}Data& Data::operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}
回答:用+去复用+=好。
因为实现+,我们需要首先拷贝构造一个新的临时变量ret,在返回的时候又需要返回临时变量ret, 返回临时变量是会创建一个临时空间去保存临时变量的,即+有两次拷贝1.Data ret(*this);拷贝构造 2. return ret;传值返回,临时拷贝。
故用+=来调用或者说复用+的时候要调用两次拷贝,因为+=本来是没有拷贝的,又平白多出了两次拷贝,费力不讨好。
11.-、-=运算符重载
结合10对于+与+=运算符重载,这里也用-去复用-=,减少拷贝。
思路也同上:比如2022.2.7-20,就是7+31(1月份或者说上个月的天数,所以这里和+的区别是先对月-1,而+是后对月+1,)-20 = 18,即变成20222.1.18
这样下代码就是+的上个月的天数了。
同样注意,当到1月份,再减少的时候,即若2022.1.18-20,那么月-1变成0,只要变成0,就让月变成12,同样年数也减一,变成2021.12,至于天数还是一样的计算,先18-20=-2,再加上12月的天数31,-2+31=29变成2021.12.29.
Data Data::operator-(int day) const
{Data ret = *this;ret -= day;return ret;
}Data& Data::operator-=(int 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;
}
但是需要注意:如果是比如2022.10.27-(-20)的时候变成2022.10.47,这个日期是不合法的,这其实可以变成10.27+20,那么就可以复用+了,就是只要day<0,那么久变成d+(-(day)),因为day是负数,所以相当于d-day。
12.前置++、--与后置的++、--
前置和后置因为都是一样的,所以用int i的参数来区分,(注意i可以不写,形参不写代表着不用这个值或者说不接收)直接写int也可以 规定无参为前置++ ,这里只是改变了函数名的修饰规则以用来区分。
Data operator++(int i = 0);这样全缺省是错误的,因为i没有实际的意义,只是用来区分前置与后置++,或者说是全缺省和无参的函数在调用时区分不开。
Data& operator++();
Data operator++(int i);
//Data operator++(int);Data& operator--();
Data operator--(int);
而实现其实写了上面的+和-就很简单了,前置++是,先++再赋值,后置++是先赋值后++,返回值都是Data&,因为d还在。 所以后置++,需要先拷贝,再改变d,返回拷贝的d。--就同理可得了。
Data& Data::operator++()
{*this += 1;return *this;
}//d1++
Data Data::operator++(int i)
{Data tmp(*this);*this += 1;return tmp;
}Data& Data::operator--()
{*this -= 1;return *this;
}
Data Data::operator--(int)
{Data tmp(*this);*this -= 1;return tmp;
}
13.&运算符重载
取地址运算符重载,这是默认成员函数,基本上不用自己写就够用了;
return nullptr;//除非你不想让别人看到真实的地址或者想让别人看到你指定的地址。
返回的是地址,所以直接返回this即可,注意用Data*接收就行,此外,注意&运算符重载需要写两个,有const修饰的。
Data* operator&(){return this;}const Data* operator&() const{return this;}
14.const
看下面三段测试用例:
void print()实际上是void print(Data* const this)
对于TestData7中的d1.print()实际上式d.print(&d1),而其类型是Data*,而print上的类型是Data* const,是符合要求的。
但是在Func中的d.print实际上虽然也是&d,但是类型是const Data*, 从const Data*变成Data* const,涉及到权限的放大,所以报错
所以要想不报错就要加上const,但是Data* const this是隐含的,是不能显示的表示的,那么怎么加上const了?
这样就运行完美了!
所以:建议成员函数中不修改成员变量的成员函数,都可以加上const,这样普通对象和const对象都能调用。
笔试题:
假设 AA 是一个类, AA* abc () const 是该类的一个成员函数的原型。若该函数返回 this 值,当用 x.abc ()调用该成员函数后, x 的值是(D)A.可能被改变
B.已经被改变
C. 受到函数调用的影响
D.不变
A.将const修饰的类成员函数称之为const成员函数。const修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。
(1)const 类成员函数中不能修改类的成员变量,const 修饰的是 this 指针指向空间的内容
(2)四个问题:
(1)const对象可以调用非const成员函数吗?(2)非const对象可以调用const成员函数吗?(3)const成员函数内可以调用其它的非const成员函数吗?(4)非const成员函数内可以调用其它的const成员函数吗
B.错误,不能被改变
C.x的值在函数内部不受任何影响
D.正确
15.日期相减
//日期相减,日期减日期返回天数int operator-(const Data& d) const;
这里flag来确定正负,如果未来和现在相减,天数是正的,加上天数即可和大的相等,如果是现在和未来相减,天数是负的。
//日期相减,日期减日期返回天数
思路就是,相减就是有大有小,我们暂时不知道我么需要相减的两个日期谁更大,我们先假设现在的日期d为大的,那么就是Data max = *this,你如是2022.10.27-2022.11.11,传入的d是2022.11.11,而*this就是2022.10.27,如果不是*this大,就换一下max和min即可。这样以后就不用管谁大谁小了。
两个差多少天其实就是min的加多少次可以达到max一样大。注意flag的符号就行。直接++就行,因为会调动运算符重载。
int Data::operator-(const Data& d) const
//Data Data::operator-(int day)构成函数重载,返回值和参数不同
{int flag = 1;Data max = *this;Data min = d;if (*this < d){min = *this;max = d;flag = -1;}int n = 0;while (min != max){++n;++min;}return n * flag;
}
16.流插入流提取
在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include 把头文件包含到本程序文件中。cin>>等价于cin.operator>>(),即调用成员函数operator>>()进行读取数据。
当cin>>从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>>会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin>>不做处理。
而用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。
如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
C++的流插入运算符“<<”(从流中再读取,进行输出)和流提取运算符“>>”(提取后再进行输入)是C++在类库中提供的。
16.1 对“<<”和“>>”重载的函数形式如下:
istream& operator>> (istream&, 自定义类&);
ostream& operator<< (ostream&, 自定义类&);
即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。
重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。
因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。
16.2 为什么只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数?
是因为一般情况下使用<<和>>时,将类写在后面。即:cout<<d1. 而用成员函数重载操作符规则是,符号左边是调用者,右边是参数(d1 + 100,Data operator+(int day) const;如果用成员函数来重载)。即这样d1<<cout,但是这样可以说是不对的;故使用友元函数,<<左边是第一个参数cout,右边是第二个参数d1。
因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。
下面是我找到的一些解释
为什么只能将重载"<<"和">>"的函数作为友元函数或普通函数,而不能将它们定义为成员函数_百度知道 (baidu.com)
<<有两个参数,一个是输出流对象(我们常用的cout),还有就是要输出的东西。
例如:cout<<"haha";也就是说<<的第一个参数必须是输出流对象。在成员函数里实现<<重载,我们知道this会作为第一个参数,即重载双目操作符(即为类的成员函数),就只要设置一个参数作为右侧运算量,而左侧运算量就是对象本身。而 >> 或<< 左侧运算量是 cin或cout 而不是对象本身。(3条消息) 流运算符为什么不能重载为成员函数,只能用友元函数重载_拉轰小郑郑的博客-CSDN博客
如果一定要声明为成员函数,只能成为如下的形式:ostream & operator<<(ostream &output){return output;}所以在运用这个<<运算符时就变为这种形式了:data<<cout;不合符人的习惯。
实际上流操作符左侧必须为cin或cout,即istream或ostream类,不是我们所能修改的类;(operator+=(int day))或者说因为流操作符具有方向性。
这导致我们不能使用成员函数重载,只能使用类外的普通函数重载。又由于我们将类内部的私有成员进行输入和输出,所以重载函数必须有对内部成员访问的权限。这导致我们不能使用普通的函数重载,只能使用友元函数重载。
(3条消息) 为什么c++中重载流操作符要用友元函数_litsun的博客-CSDN博客_为什么流运算符要重载为友元函数
定义为成员函数,那么就隐含this指针,重载其实也是一种函数,那么函数就有调用他的对象,如果是成员函数,那么调用他的对象就肯定是相对应的类对象,但是<<和>>调用的对象只能是cout或者cin,因为cout和cin是全局的对象,他们包含在iostream头文件中,所以我们写C++程序要包#include <iostream>,cin是istream的对象,cout是ostream的对象。那么就不能定义为成员函数了,只有定义成友元,那么就可以把cin,cout作为一个参数传进重载的操作符函数里面。
//友元函数friend std::ostream& operator<<(std::ostream & out, const Data & d);friend std::istream& operator>>(std::istream & out, Data & d);
16.3 为什么要声明友元呢?
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
因为我们在类里面定义的成员变量是私有的,直接访问是不行的。
如果想直接在类外面访问成员变量的话,有三种方法:
一是把私有的成员变量设置为公有,如果设置公有的话,那么就破坏了封装;
二是可以提供访问成员变量的函数;
三是可以定义为 友元 函数,就是告诉编译器这两个函数是Date类的朋友,可以去访问私有的成员变量。
关于友元函数:
友元函数可访问类的私有和保护成员,但不是类的成员函数;
友元函数不能用const修饰;
友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
一个函数可以是多个类的友元函数;
友元函数的调用与普通函数的调用原理相同;
std::ostream& operator<<(std::ostream& out, const Data& d)
{out << d._year << "-" << d._month << "-" << d._day << endl;return out;
}std::istream& operator>>(std::istream& in, Data& d)
{in >> d._year >> d._month >> d._day;assert(d.CheckDate());//判断输入的日期是否合法return in;
}
return out;的作用是什么?
回答:能连续向输出流插入信息。out是ostream类的对象,它是实参cout的引用,也就是cout通过传送地址给out,使它们二者共享同一段存储单元,或者说output是cout的别名。
因此,return out就是return cout,将输出流cout的现状返回,即保留输出流的现状。这样就可以cout << d1 << d2;(从左到右)类似d1 = d2 = d3,但这个是从右往左,就是cout<<d1<<d2,输出d1和d2,我们肯定希望cout<<d1之后是cout,只有这样才可以cout<<d2。
为什么<<参数中有const Data& d?
cout<< 只是把内容打印输出到屏幕上,所以我们加上 const 防止成员在函数内部进行修改。
而 cin>> 我们会在函数内部对成员变量进行赋值。
16.4 为什么这个不写在Data.h里?
因为如果写在里头会报链接错误,因为在Data.cpp和test.cpp里都需要展开Data.h,这样我们就不知道去调用哪里的这个流输入和流提取,但是为什么别的可以,因为别的是在类里面定义,类里面定义的成员函数默认是内联函数,内联函数编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,每当代码调用到内联函数,就在调用处直接插入一段该函数的代码,而这个是在类外面定义的,所以我们直接写在了Data.cpp中。
三、详细代码
1. Data.cpp
#include"Data.h"
/*类的两种定义方式:
1.声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。(以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。)
2.类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加(类名::)
一般情况下,更期望采用第二种方式*/bool Data::operator==(const Data& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}//为什么可以访问私有,因为这个函数虽然在类外面定义,在类里面声明
//但是实际他还是在类里面的成员函数,类的成员函数是可以访问私有的
//但其实本质上访问的是成员,类里面那个private只是声明,就是说实际上访问的是d1或者d2
//注意:变量的声明和定义的区别是是否开辟空间bool Data::operator<(const Data& d) const//声明和定义分离需要注意
{if ((_year < d._year)|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day)){return true;}else{return false;}
}int Data::GetMonthDay(int year, int month)
{assert(year >= 0 && month > 0 && month < 13);const static int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//GetMonthDay会被频繁的调用,加上static,防止不停的开辟数组,直接放在静态区//再加上const,更安全//用0让31是1月份if (month == 2 && isLeapYear(year) == 1){return 29;}else{return MonthDayArray[month];}
}Data::Data(int year, int month, int day)//声明给了缺省参数,定义就不用给了
{if (year >= 1 && month <= 12 && month >= 1 && day >= 1 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}
}//d1 + 100 --》 d1.operator+(day),,d1就是this
//如果如下写,会改变d1和d2,变成一样
//Data Data::operator+(int day)
//{
// _day += day;
// while (_day > GetMonthDay(_year, _month))
// {
// _day -= GetMonthDay(_year, _month);
// _month++;
// if (_month == 13)
// {
// ++_year;
// _month = 1;
// }
// }
// return *this;
//}//上方法:
Data& Data::operator+(int day)这样不对,因为ret所在的空间出了作用域销毁了
我们让他返回ret的别名,如果ret的空间没被占用,还是返回的和下代码一致,但是如果被占用就不一样了
//Data Data::operator+(int day)
//{
// //应对问题于是先拷贝源日期
// Data ret(*this);//把*this拷贝给ret,再改变ret
// ret._day += day;
// while (ret._day > GetMonthDay(ret._year, ret._month))
// {
// ret._day -= GetMonthDay(ret._year, ret._month);
// ret._month++;
// if (ret._month == 13)
// {
// ++ret._year;
// ret._month = 1;
// }
// }
// return ret;//注意传值返回返回的不是ret,是ret的拷贝,是临时对象,是带有const属性的
// //再做Data(Data& d)拷贝构造的参数,是权限的放大。所以拷贝构造加上了const
// //这正好说明了为什么需要Data(const Data& d);
//}注意d1+=100,自己也会改变,而且有返回值,因为有些地方需要连续的+=
//Data& Data::operator+=(int day)
//{
// //有没有办法封装了?重复又要写一遍
// *this = *this + day;//d1 += 100;//这里复用了+即Data Data::operator+(int day)
// return *this;
// /*_day += day;
// while (_day > GetMonthDay(_year, _month))
// {
// _day -= GetMonthDay(_year, _month);
// _month++;
// if (_month == 13)
// {
// ++_year;
// _month = 1;
// }
// }
// return *this;*/
// //思考:出了作用域*this还在不在?在
// //可以这样理解:d1还在,,所以加上&,即引用返回会更合适
//}//思考:分析上下两种复用方式的区别?哪种复用更合适一点?
//对于上方法,+有两次拷贝1.Data ret(*this);拷贝构造 2. return ret;传值返回,临时拷贝
//故用+=来调用或者说复用+的时候要调用两次拷贝,但是+=本来是没有拷贝的
//对于下方法,+=没有拷贝构造,只有+还是有两次拷贝构造,但是+=不用再像上方法那种去调用拷贝了
//下方法:所以下方法更合适
Data Data::operator+(int day) const
{Data ret(*this);ret += day;return ret;
}Data& Data::operator+=(int day)
{_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}Data Data::operator-(int day) const
{Data ret = *this;ret -= day;return ret;
}Data& Data::operator-=(int day)
{//针对测试4,要不就断言,是负数的-=就不进入函数if (day < 0)//要不就让其变成+=负号{return *this += -day;}_day -= day;while (_day <= 0){_month--;if (_month == 0){_month = 12;_year--;}_day += GetMonthDay(_year, _month);}return *this;
}Data& Data::operator++()
{*this += 1;return *this;
}//d1++
Data Data::operator++(int i)
{Data tmp(*this);*this += 1;return tmp;
}Data& Data::operator--()
{*this -= 1;return *this;
}
Data Data::operator--(int)
{Data tmp(*this);*this -= 1;return tmp;
}/*这里flag来确定正负,如果现在和未来比,天数是正的,加上天数即可和大的相等
如果是现在和过去比,天数是负的,现在+天数等于过去的时间*/
//日期相减,日期减日期返回天数
int Data::operator-(const Data& d) const
//Data Data::operator-(int day)构成函数重载,返回值和参数不同
{int flag = 1;Data max = *this;Data min = d;if (*this < d){min = *this;max = d;flag = -1;}int n = 0;while (min != max){++n;++min;}return n * flag;
}std::ostream& operator<<(std::ostream& out, const Data& d)
{out << d._year << "-" << d._month << "-" << d._day << endl;return out;
}
/*return out;的作用是什么?
回答:能连续向输出流插入信息。out是ostream类的对象,它是实参cout的引用,
也就是cout通过传送地址给out,使它们二者共享同一段存储单元,或者说output是cout的别名。
因此,return out就是return cout,将输出流cout的现状返回,即保留输出流的现状。
这样就可以cout << d1 << d2;(从左到右)类似d1 = d2 = d3,但这个是从右往左
就是cout<<d1<<d2,输出d1和d2,我们肯定希望cout<<d1之后是cout,只有这样才可以cout<<d2*//*cout<< 只是把内容打印输出到屏幕上,所以我们加上 const 防止成员在函数内部进行修改。
而 cin>> 我们会在函数内部对成员变量进行赋值。*/
std::istream& operator>>(std::istream& in, Data& d)
{in >> d._year >> d._month >> d._day;assert(d.CheckDate());//判断输入的日期是否合法return in;
}
//至于为什么这个不写在Data.h里
//因为如果写在里头会报链接错误
//因为在Data.cpp和test.cpp里都需要展开Data.h
//这样我们就不知道去调用哪里的这个流输入和流提取
//但是为什么别的可以,因为别的是在类里面定义,类里面定义的成员函数默认是内联函数
//内联函数编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,
//每当代码调用到内联函数,就在调用处直接插入一段该函数的代码
//而这个是在类外面定义的,所以我们直接写在了Data.cpp中/*对“<<”和“>>”重载的函数形式如下:istream& operator>> (istream&, 自定义类&);ostream& operator<< (ostream&, 自定义类&);
即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。
重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。
因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数*///why?
/*定义为成员函数,那么就隐含this指针,
重载其实也是一种函数,那么函数就有调用他的对象,
如果是成员函数,那么调用他的对象就肯定是相对应的类对象,
但是<<和>>调用的对象只能是cout或者cin,那么就不能定义为成员函数了,
只有定义成友元,那么就可以把cin,cout作为一个参数传进重载的操作符函数里面*//*是因为一般情况下使用<<和>>时,将类写在后面。即:cout<<d1.
而用成员函数重载操作符规则是,符号左边是调用者,右边是参数。d1 + 100
Data operator+(int day) const;
如果用成员函数来重载,调用时就得写成:d1<<cout;反而使人更迷惑甚至可以说是不对的;
用友元函数时,<<左边是第一个参数cout,右边是第二个参数d1
//std::ostream& operator<<(std::ostream& out, const Data& d)*/
2 Data.h
#pragma once
#include<assert.h>
#include<iostream>
using std::cout;
using std::cin;
using std::endl;class Data//类里面定义默认是内联
{//友元函数friend std::ostream& operator<<(std::ostream & out, const Data & d);friend std::istream& operator>>(std::istream & out, Data & d);
/*为什么要声明友元呢?因为我们在类里面定义的成员变量是私有的,直接访问是不行的。
如果想直接在类外面访问成员变量的话,有三种方法:
一是把私有的成员变量设置为公有,如果设置公有的话,那么就破坏了封装;
二是可以提供访问成员变量的函数;
三是可以定义为 友元 函数,就是告诉编译器这两个函数是Date类的朋友,可以去访问私有的成员变量。
*/
public:bool isLeapYear(int year)//能被4整除却不能被100整除或能被400整除的年份就是闰年!//取模运算符"%"//除法运算符"/"{if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)){return true;}else{return false;}}//判断日期合法性bool CheckDate(){if (_year < 1 || _month > 13 || _month < 1 ||_day < 1 || _day > GetMonthDay(_year, _month)){return false;}return true;}int GetMonthDay(int year, int month);Data(int year = 1, int month = 1, int day = 1);Data(const Data& d){_year = d._year;_month = d._month;_day = d._day;}void print() const{cout << _year << "-" << _month << "-" << _day << endl;}Data& operator=(const Data& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}bool operator==(const Data& d);bool operator<(const Data& d) const;//建议成员函数中不修改成员变量的成员函数,都可以加上const,这样普通对象和const对象都能调用//d1 <= d2,成员函数要成为inline,最好在类里面定义,类里面定义默认就是inlinebool operator<=(const Data& d){return *this < d || *this == d;}bool operator>=(const Data& d){return !(*this < d);}bool operator!=(const Data& d){return !(*this == d);}bool operator>(const Data& d){return !(*this <= d);}Data operator+(int day) const;Data& operator+=(int day);Data operator-(int day) const;Data& operator-=(int day);Data& operator++();//规定无参为前置++Data operator++(int i);//注意i可以不写,形参不写代表着不用这个值或者说不接收//Data operator++(int);//这里只是改变了函数名的修饰规则以用来区分//Data operator++(int i = 0);//这样全缺省是错误的,因为i没有实际的意义,只是用来区分前置与后置++//因为全缺省和无参的函数在调用时区分不开Data& operator--();Data operator--(int);//日期相减,日期减日期返回天数int operator-(const Data& d) const;//取地址运算符重载,这是默认成员函数,基本上不用自己写就够用了/*Data* operator&(){//return nullptr;//除非你不想让别人看到真实的地址或者想让别人看到你指定的地址return this;}const Data* operator&() const{return this;}*//*C++的流插入运算符“<<”(从流中再读取,进行输出)和流提取运算符“>>”(提取后再进行输入)是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。cin和cout分别是istream类和ostream类的对象。*///流插入运算符<<是ostream类型的对象//标准库在名字空间中被指定为std,所以需要::指明ostream在std中//即std::ostream//这样out就是cout的别名//内置类型自动识别类型,支持流插入与流提取,本质上函数重载,库里面已经写好了//cin是istream类型的对象,cout是ostream类型的对象private:int _year;int _month;int _day;
};
3.Test.h
这里就不展示验证效果了,小伙伴们可以自行验证。测试用例如下:
//写日期类的时候需要注意日期的合法性:
#include"Data.h"
//测试日期比大小
void TestData1()
{Data d1(2022, 5, 18);Data d2(2022, 3, 20);Data d3(2022, 3, 20);cout << (d1 > d2) << endl;//<<流运算符优先级很高//1cout << (d1 < d2) << endl;//0cout << (d1 == d2) << endl;//0cout << (d2 == d3) << endl;//1cout << (d1 >= d2) << endl;//1cout << (d2 <= d3) << endl;//1cout << (d3 <= d1) << endl;//1
}//测试日期加是否正确
void TestData2()
{Data d1(2022, 5, 18);Data d2 = d1 + 15;//先调用了operator+,得到临时返回值ret,再调用拷贝构造给d2//所以Data d3 = d1与Data d3(d1)是等价的,编译器会识别出拷贝构造//这样写就是赋值:Data d3;d3 = d1+15;//两个已经存在的对象的才叫赋值比如d1 = d2,d2赋值给d1d2.print();d1.print();
}//测试日期减
void TestData3()
{Data d1(2022, 5, 18);Data d2 = d1 - 30;d2.print();d1 -= 30;d1.print();
}//测试结果5月118号,肯定不合法
void TestData4()
{Data d1(2022, 5, 18);d1 -= -100;d1.print();
}//前置与后置
void TestData5()
{Data d2(2022, 5, 18);Data ret1 = ++d2;ret1.print();d2.print();Data ret2 = d2++;ret2.print();d2.print();Data ret3 = d2--;ret3.print();d2.print();Data ret4 = --d2;ret4.print();d2.print();
}//日期相减
void TestData6()
{Data d1(2022, 5, 18);Data d2(2020, 2, 4);cout << (d2 - d1) << endl;cout << (d1 - d2) << endl;
}/*“void Data::print(void)”: 不能将“this”指针从“const Data”转换为“Data& ”void print(Data* const this)对于TestData7中的d1.print()实际上式d1.print(&d1)而其类型是Data*,而print上的类型是Data* const,是符合要求的但是在Func中的d.print实际上虽然也是&d,但是类型是const Data*从const Data*变成Data* const,涉及到权限的放大,所以报错所以要想不报错就要加上const,但是Data* const this是隐含的,是不能显示的表示的*//*所以规定这样写void print()const//成员函数后加const相当于在隐含的参数前加上const
即const Data* const this
那是不是所有的成员函数都需要加上const?并不是,const的意义只是让其修饰this指针指向的对象
但是加上const后会让this指向的对象不能够想改,因为this的类型变成了const Data*
所以发现日期类的比较都可加上const,*/
void Func(const Data& d)
{d.print();cout << &d << endl;
}
void TestData7()
{Data d1(2022, 5, 18);d1.print();Func(d1);cout << &d1 << endl;
}void TestData8()
{Data d1(2022, 5, 18);(d1 + 100).print();//这样在linux环境下编译不通过,如果print后不加const的话
}void TestData9()
{Data d1,d2;/*1.在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include 把头文件包含到本程序文件中。2.而用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。3.cout和cin是全局的对象,他们包含在iostream头文件中,所以我们写C++程序要包#include <iostream>.cin是istream的对象,cout是ostream的对象。因此它俩是两个类型,这个类型是库里面的,流插入和流提取的类型。*/cin >> d1 >> d2;cout << d1 << d2;//operator<<(cout, d1);int i = 1;double d = 2.2;cout << i;cout << d;
}int main()
{//TestData1();//TestData2();//TestData3();//TestData4();//TestData5();//TestData6();TestData7();//TestData8();//TestData9();return 0;
}//对于const注意:
/*成员函数是可以调用成员函数的
如果 void print() //没有加上const{cout << _year << "-" << _month << "-" << _day << endl;}
比如该成员函数的定义:
Data Data::operator+(int day) const
{print();//但是这样是会报错//因为这样编译器会默认为this->print();而此时因为加上了const,this是const Data*//而调用成员函数的本质是把this指针再传递过去,而print是非const的,所以权限放大,所以报错//所以const成员函数内不可以调用它的非const成员函数Data ret(*this);ret += day;return ret;
}*/
日期类的实现(C++)相关推荐
- date日期相减 java_03时间日期类
Java8 在 java.time 包中增加了时间日期相关的API,弥补了 Java8 以前对日期.时间处理的不足. 在介绍Java8新的时间日期API前,先看看 java8 以前我们操作日期时间常用 ...
- string日期格式化_java面向对象---日期类
10.日期类 (1)Date Date代表了一个特定的时间,精确到毫秒 方法名 说明 Public Date() 分配一个Date对象并将其初始化,以便它代表它被分配的时间,精确到毫秒. Public ...
- 日期类对象与整数之间的加法运算
日期类对象与整数之间的加法运算 采用友元函数形式, 定义两个友元函数
- 蓝桥杯日期计算java_日期类的使用(java)-蓝桥杯
蓝桥杯日期问题常考,java提供了日期类很方便: //日历类 Calendar c = Calendar.getInstance(); // 获取实例化对象 Date date =c.getTime( ...
- java日期类的计算问题_java日期计算(Calendar类)
昨天学了java日期的格式器,主要是用SimpleDateFormat进行格式化.解析.但这还仅停留在日期的查看(调整显示格式)阶段,更重要的是日期的操作.今天继续学习,记录如下: 今天主要学习的日期 ...
- java 8时间操作_Java8 时间日期类操作
Java8 时间日期类操作 Java8的时间类有两个重要的特性 线程安全 不可变类,返回的都是新的对象 显然,该特性解决了原来java.util.Date类与SimpleDateFormat线程不安全 ...
- java当中日期类的相关操作(学习笔记)
一:引言 Calendar类是日历类,提供操作日历字段的方法,其中有常用操作 get 和 set 方法还有 add方法 详细用法请看码 二:上码 package cn.wyj.one;import j ...
- java中的常用日期类_Java中的常用日期类说明
日期类常用的有三个,Date类,Calendar(日历)类和日期格式转换类(DateFormat) Date类中的大部分的方法都已经过时,一般只会用到构造方法取得系统当前的时间.public clas ...
- 标题 日期问题java蓝桥杯,日期类的使用(java)-蓝桥杯
蓝桥杯日期问题常考,java提供了日期类很方便: //日历类 Calendar c = Calendar.getInstance(); // 获取实例化对象 Date date =c.getTime( ...
- 某单位会java_Java核心API -- 4(日期类)
1. Date类(Java.utilDate) java.util.Date类用于封装日期及时间信息,一般仅用它显示某个日期,不对他作任何操作处理,作处理用Calendar类,计算方便. //创建一个 ...
最新文章
- keras 的 example 文件 mnist_denoising_autoencoder.py 解析
- [Web 开发] 定制IE下载对话框的按钮(打开/保存)
- STM32 进阶教程 17 - ADC注入通道
- Bootstrap模态框(modal)显示、隐藏与禁用ESC代码实现
- 强烈推荐一位大佬,知名银行风控分析师,学习是一辈子的事!
- 无线多串口服务器,多串口通信服务器
- 【POJ2018】Best Cow Fences
- c语言编程怎么记,新手如何学习c语言
- JDK API下载
- 二总线 XM2BUS 通信 消防 串口 开发 CMBUS MBUS 中继 电源无极性
- UVA1386 Cellular Automaton
- 自噬决定免疫细胞分化及功能执行
- 树莓派指定挂载点挂载移动硬盘
- OpenCV中threshold自动阈值,类似matlab中的graythresh
- 组建WEP加密无线局域网络
- Meanshift聚类算法
- 过滤器VS拦截器的4个区别,看完豁然开朗!
- 2012年英语专升本英语阅读「Part II 阅读专区」【文章(图片)、答案、词汇记忆】
- RT-Thread源码下载介绍
- 卡罗拉 (COROLLA) - 灯光开关
热门文章
- CodeForces--ZeptoLab Code Rush 2015 (我参加的第一场cf比赛)
- 《禁闭岛》与脑前额叶,从白痴到天才看人类的认知
- jmeter常用逻辑控制器的使用(如果(if)控制器)
- oracle如果查询出的值为null记为0
- Linux运维-day44-综合架构-playbook剧本的变量、条件语句及循环语句
- 搭了我半年顺风车的同事,把我拉黑了:和任何人走太近,都是一场灾难
- Scapy用法官方文档
- Oracle 12c 的 hr_main.sql
- 怎样训练左右手协调_钢琴的左右手应该怎么协调
- 毕业论文降重的有关经验