目录

注意:

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++)相关推荐

  1. date日期相减 java_03时间日期类

    Java8 在 java.time 包中增加了时间日期相关的API,弥补了 Java8 以前对日期.时间处理的不足. 在介绍Java8新的时间日期API前,先看看 java8 以前我们操作日期时间常用 ...

  2. string日期格式化_java面向对象---日期类

    10.日期类 (1)Date Date代表了一个特定的时间,精确到毫秒 方法名 说明 Public Date() 分配一个Date对象并将其初始化,以便它代表它被分配的时间,精确到毫秒. Public ...

  3. 日期类对象与整数之间的加法运算

    日期类对象与整数之间的加法运算 采用友元函数形式, 定义两个友元函数

  4. 蓝桥杯日期计算java_日期类的使用(java)-蓝桥杯

    蓝桥杯日期问题常考,java提供了日期类很方便: //日历类 Calendar c = Calendar.getInstance(); // 获取实例化对象 Date date =c.getTime( ...

  5. java日期类的计算问题_java日期计算(Calendar类)

    昨天学了java日期的格式器,主要是用SimpleDateFormat进行格式化.解析.但这还仅停留在日期的查看(调整显示格式)阶段,更重要的是日期的操作.今天继续学习,记录如下: 今天主要学习的日期 ...

  6. java 8时间操作_Java8 时间日期类操作

    Java8 时间日期类操作 Java8的时间类有两个重要的特性 线程安全 不可变类,返回的都是新的对象 显然,该特性解决了原来java.util.Date类与SimpleDateFormat线程不安全 ...

  7. java当中日期类的相关操作(学习笔记)

    一:引言 Calendar类是日历类,提供操作日历字段的方法,其中有常用操作 get 和 set 方法还有 add方法 详细用法请看码 二:上码 package cn.wyj.one;import j ...

  8. java中的常用日期类_Java中的常用日期类说明

    日期类常用的有三个,Date类,Calendar(日历)类和日期格式转换类(DateFormat) Date类中的大部分的方法都已经过时,一般只会用到构造方法取得系统当前的时间.public clas ...

  9. 标题 日期问题java蓝桥杯,日期类的使用(java)-蓝桥杯

    蓝桥杯日期问题常考,java提供了日期类很方便: //日历类 Calendar c = Calendar.getInstance(); // 获取实例化对象 Date date =c.getTime( ...

  10. 某单位会java_Java核心API -- 4(日期类)

    1. Date类(Java.utilDate) java.util.Date类用于封装日期及时间信息,一般仅用它显示某个日期,不对他作任何操作处理,作处理用Calendar类,计算方便. //创建一个 ...

最新文章

  1. keras 的 example 文件 mnist_denoising_autoencoder.py 解析
  2. [Web 开发] 定制IE下载对话框的按钮(打开/保存)
  3. STM32 进阶教程 17 - ADC注入通道
  4. Bootstrap模态框(modal)显示、隐藏与禁用ESC代码实现
  5. 强烈推荐一位大佬,知名银行风控分析师,学习是一辈子的事!
  6. 无线多串口服务器,多串口通信服务器
  7. 【POJ2018】Best Cow Fences
  8. c语言编程怎么记,新手如何学习c语言
  9. JDK API下载
  10. 二总线 XM2BUS 通信 消防 串口 开发 CMBUS MBUS 中继 电源无极性
  11. UVA1386 Cellular Automaton
  12. 自噬决定免疫细胞分化及功能执行
  13. 树莓派指定挂载点挂载移动硬盘
  14. OpenCV中threshold自动阈值,类似matlab中的graythresh
  15. 组建WEP加密无线局域网络
  16. Meanshift聚类算法
  17. 过滤器VS拦截器的4个区别,看完豁然开朗!
  18. 2012年英语专升本英语阅读「Part II 阅读专区」【文章(图片)、答案、词汇记忆】
  19. RT-Thread源码下载介绍
  20. 卡罗拉 (COROLLA) - 灯光开关

热门文章

  1. CodeForces--ZeptoLab Code Rush 2015 (我参加的第一场cf比赛)
  2. 《禁闭岛》与脑前额叶,从白痴到天才看人类的认知
  3. jmeter常用逻辑控制器的使用(如果(if)控制器)
  4. oracle如果查询出的值为null记为0
  5. Linux运维-day44-综合架构-playbook剧本的变量、条件语句及循环语句
  6. 搭了我半年顺风车的同事,把我拉黑了:和任何人走太近,都是一场灾难
  7. Scapy用法官方文档
  8. Oracle 12c 的 hr_main.sql
  9. 怎样训练左右手协调_钢琴的左右手应该怎么协调
  10. 毕业论文降重的有关经验