C++类和对象中:运算符重载+const成员函数+日期类的完善

C++类和对象中:运算符重载+const成员函数+日期类的完善

一.为什么C++会有运算符重载这个语法呢?

1.需求说明

有的时候对于某些类来说,我们会有一些需求让我们去实现一些函数,能够便捷快速地对该类的若干成员变量进行数据操作

以日期类为例,有些时候我们想要去判断两个日期谁大谁小,是否相等,计算两个日期之间相差多少天,计算某一个日期加上几天后的日期是多少等等等等的需求

2.实现

有需求,就会有一大堆解决方案.
我们这里以比较日期大小和Date类为例:

class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

其中不太好的解决方案是:

1.不规范的解决方案

1.代码实现
bool Greater(const Date& d1, const Date& d2)
{
    
    
	if (d1._year > d2._year)
	{
    
    
		return true;
	}
	else if (d1._year == d2._year && d1._month > d2._month)
	{
    
    
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
	{
    
    
		return true;
	}
	return false;
}

在这里插入图片描述
这里这么多报错呢?
因为我们的日期类的成员变量的访问权限是private,私有
在类外访问不到

怎么办呢?
这个我们先暂时抛开不谈,我们下面会回过头来解释解决方法的.
这里我们先把成员变量设置为公有
在这里插入图片描述
实现的挺好的啊,怎么啦?

2.缺陷

有一点不太好的地方,例如:
这个函数的名字叫做Greater(起的挺好)
但是:万一有人这么去定义名字呢?

bool dayu(const Date& d1, const Date& d2)
{
    
    //具体实现}
bool compare1(const Date& d1, const Date& d2)
{
    
    //具体实现}
如果是比较是否相等的函数
bool xiangdeng(const Date& d1, const Date& d2)
{
    
    //具体实现}
bool compare2(const Date& d1, const Date& d2)
{
    
    //具体实现}

这是不是不太好啊,如果跟一些外国人一起合作做一些项目开发什么的话
他们不一定能看懂啊:dayu,xiangdeng
而且compare1和compare2对我们中国人也不是很友好,没有人规定1就是大于,2就是小于啊…
所以为了便于代码的跨国际通用性,C++创始人就引入了运算符重载这一语法

3.具体的解决方案:运算符重载

下面我们让开始一起来探索运算符重载的奥秘吧
具体的解决代码在我们学完运算符重载之后我们会给出来的

二.运算符重载的语法形式

受到了数学中运算符的启发,C++创始人就想:

在C语言中运算符只能操作一些内置的数据类型,那么可不可以让它们也能操作一下自定义类型呢?
既然我们都已经决定要去规范一下大家的代码了,那么可不可以把数学符号引入到自定义类型的比较中来呢?
在这里插入图片描述

1.语法形式

bool operator>(const Date& d1, const Date& d2)
{
    
    
	if (d1._year > d2._year)
	{
    
    
		return true;
	}
	else if (d1._year == d2._year && d1._month > d2._month)
	{
    
    
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
	{
    
    
		return true;
	}
	return false;
}

具体调用:

bool ret = operator>(d1, d2);

在这里插入图片描述
这个函数比起Greater来说只是改了一下名字
那么你可能会觉得,不过如此嘛
但是,惊喜在这里呢~:
在这里插入图片描述
具体调用:

bool ret = d1 > d2;

大于运算符真的可以对自定义类型进行比较了!
为什么呢?

C++创始人设计运算符重载时想:我都已经设计好运算符重载这一语法了,可是调用的话还要加个operator,还是有点麻烦啊,要不然就让编译器去把operator优化一下吧

让我们能够真的只用一个>就能比较两个自定义类型

2.private私有成员的解决方案

但是:我们之前提到过,这个类中的私有成员,类外不能访问,那么怎么办呢?

1.封装出get函数,能够在类外读取对应成员变量的数值

在这里插入图片描述
这里为什么会报错呢?因为我们在这里加了const修饰,导致d1和d2不能去调用对应的成员函数,因为编译器怕我们在成员函数中修改成员变量,违背了const的修饰

那我们就去掉const
在这里插入图片描述
成功运行
但是也太麻烦了吧,我还需要再去单独设计三个函数来保证类外可以读取类内部的数据
那如果我都不想让类外部读取到我类内部的数据呢?

2.把运算符重载放到类内部

不就这么简单吗,至于这么大费周章吗,我还以为能有什么高明的手段呢.
你错了:
在这里插入图片描述
然后我们把这个运算符重载移到类内部,
结果发现:这里竟然报错了

为什么会报出参数太多的错误来呢?
因为类的成员函数的参数列表中有一个隐含的this指针!!!

而且运算符重载有一个规定:

操作的数据个数要与参数个数匹配上

这里我们操作的数据个数是2个,可是参数个数有3个,因此报出了参数太多的错误

怎么修改呢?
把一个参数改为this指针
也就是这样:

bool operator>(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;
}

在这里插入图片描述

调用方法:
1.d1>d2;
2.d1.operator>(d2)  本质是:operator(&d1,d2);this指针指向d1

这就是>的运算符重载的版本

3.其他比较运算符重载的代码

下面一起给出==,!=,<=,>=,<的重载版本了
并且对构造函数传入的日期进行了规范化检查
其实只要我们写出>==或者<==
就可以借助于逻辑与或者逻辑或,逻辑取反操作符来进行复用

其中*this就是当前调用这个运算符函数的对象

class Date
{
    
    
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
    
    
		//对传入的日期进行检查
		if ((year < 0) || (month < 1 || month>12) || (day<1 || day>GetMonthDay(year, month)))
		{
    
    
			//assert(false);//粗暴一点的方法
			Print();//本质是:this->Print();
			cout << "输入的日期为非法日期" << endl;
			exit(-1);
		}
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
    
    
		cout << _year << " " << _month << " " << _day << endl;
	}
	bool operator>(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<=(Date& d)
	{
    
    
		return !(*this > d);
	}
	bool operator>=(Date& d)
	{
    
    
		return *this == d || *this > d;
	}
	bool operator<(Date& d)
	{
    
    
		return !(*this >= d);
	}
	bool operator==(Date& d)
	{
    
    
		return _year == d._year && _month == d._month && _day == d._day;
	}
	bool operator!= (Date& d)
	{
    
    
		return !(*this == d);
	}
private:
	int _year;
	int _month;
	int _day;
};

在这里插入图片描述
当我们引出了运算符重载这个知识点并且实现了>,==,等等运算符的重载之后

三.赋值运算符重载

在这里插入图片描述
接上我们类和对象中这个大模块的知识,下面我们来介绍赋值运算符重载这个类的默认成员函数

1.赋值运算符重载为什么能够作为默认成员函数之一呢?

为什么运算符重载中只有赋值运算符会成为类的6大默认成员函数之一呢?

我的理解是:
在这里插入图片描述
在这里插入图片描述

2.赋值运算符的返回值类型

那么赋值运算符重载的返回值类型是什么呢?
这就要看一下=这个运算符本身的返回值类型了
在这里插入图片描述
在这里插入图片描述
=这个运算符本身就可以这样连着写,因此b=c这个表达式返回的就是b本身
因此赋值运算符重载的返回值类型是该类对象的引用

3.赋值运算符重载的具体实现

具体代码:

Date5& operator=(Date5& d)
{
    
    
	//对传入的日期进行检查
	if ((d._year < 0) || (d._month < 1 || d._month>12) || (d._day<1 || d._day>GetMonthDay(d._year, d._month)))
	{
    
    
		//assert(false);//粗暴一点的方法
		cout << "拷贝对象的日期为非法日期: ";
		d.Print();
		cout << "无法进行拷贝" << endl;
		exit(-1);
	}
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

在这里插入图片描述
我们来看一下拷贝构造函数的代码

Date5(Date5& d1)
{
    
    
	//对传入的日期进行检查
	if ((d1._year < 0) || (d1._month < 1 || d1._month>12) || (d1._day<1 || d1._day>GetMonthDay(d1._year, d1._month)))
	{
    
    
		//assert(false);//粗暴一点的方法
		cout << "拷贝对象的日期为非法日期: ";
		d1.Print();
		cout << "无法进行拷贝" << endl;
		exit(-1);
	}
	_year = d1._year;
	_month = d1._month;
	_day = d1._day;
}

4.Stack类的赋值运算符重载实现

Stack& operator=(const Stack& st)
{
    
    
	if (_a != nullptr)
	{
    
    
		free(_a);//先释放原有空间
		_a = nullptr;
	}
	_a = (int*)malloc(sizeof(int) * st._capacity);
	if (nullptr == _a)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
	_capacity = st._capacity;
	memcpy(_a, st._a, sizeof(int) * st._top);
	_top = st._top;
	return *this;
}

5.赋值运算符重载跟拷贝构造函数的区别与联系

在这里插入图片描述
关于拷贝构造函数,大家可以看我的另一篇博客:
C++类和对象中(构造函数,析构函数,拷贝构造函数)详解
里面对拷贝构造函数的来龙去脉进行了详细的讲解

6.注意

在这里插入图片描述
下面给大家调试一下,我们按F11进入函数
在这里插入图片描述
证明完毕
尽管:
Date d2 = d;看起来像是调用运算符重载函数
但是d2是一个尚未被建立的对象,当前正在用d的数据来对d2进行构造,也就是拷贝构造

这也就印证了这句话:

拷贝构造函数:是利用一个已经存在的对象去拷贝一份还未被创建的对象
而赋值运算符重载:是两个已经存在的对象之间进行拷贝赋值

四.+和+=的运算符重载

1.+=的运算符重载

1.+=有没有返回值呢?

对于一个内置类型来说,我们在这里以两个int类型为例,来探讨一下两个int类型进行+=计算有没有返回值
在这里插入图片描述
可见,是有返回值的
这里我们要实现的是让一个日期+=天数

2.+=的实现

因为每个月份的天数都不同,而且二月在闰年是29天,在平年是28天
所以我们有必要实现一个函数来获取当前月份有多少天

int GetMonthDay(int year, int month)
{
    
    
	int MonthArray[13] = {
    
     0,31,28,31,30,31,30,31,30,31,31,30,31 };
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
    
    
		MonthArray[month]++;
	}
	return MonthArray[month];
}
Date operator+=(int day)
{
    
    
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
    
    
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
    
    
			_year++;
			_month = 1;
		}
	}
	//从这里我们就可以看出*this在某些情况下是需要在成员函数内部显式使用的
	return *this;//*this就是调用+=这个运算符的变量
}

从这里我们就可以看出*this在某些情况下是需要在成员函数内部显式使用的

在这里插入图片描述
可不可以再优化一些呢?
其实我们这个函数是可以传引用返回的,因为:

Date d1(2023, 10, 23);
d1 += 70;
返回的是d1,d1本身并不会随着+=这个运算符函数的销毁而销毁的,因此可以传引用返回
这也是引用作为返回值的一大应用
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就是调用+=这个运算符的变量,而且这个*this出了这个+=函数之后依然存在,所以可以返回引用
}

在这里插入图片描述

2.+的运算符重载

Date operator+(int day)
{
    
    
	Date d2(*this);//这里调用拷贝构造函数
	d2._day += day;
	while (d2._day > GetMonthDay(d2._year, d2._month))
	{
    
    
		d2._day -= GetMonthDay(d2._year, d2._month);
		d2._month++;
		if (d2._month == 13)
		{
    
    
			d2._year++;
			d2._month = 1;
		}
	}
	return d2;
}

那么问题来了,这个+的运算符重载可以传引用返回吗?
当然不可以
为什么呢?

d2是+这个运算符函数中的局部变量,随着+这个函数栈帧的销毁,d2也会随之销毁
因此传只能传值返回,而且返回的不是d2,而是d2的一份临时拷贝
在这里插入图片描述
其实这里也不需要这么麻烦,我们是可以复用+=这个运算符的

	Date operator+(int day)
	{
    
    
		Date d2(*this);//这里调用拷贝构造函数
		d2 += day;
		return d2;
	}

在这里插入图片描述

3.可不可以让+=复用+呢?

当然可以啦,我们刚刚介绍的赋值运算符重载函数就能派上用场了

Date& operator+=(int day)
{
    
    
	*this = *this + day;
	return *this;
}

4.这两种方案哪一个更好呢?

在这里插入图片描述
对于+复用+=来说
+=:拷贝了0次
+:拷贝了2次:分别是:
在这里插入图片描述
对于+=复用+来说
+拷贝了2次:分别是:
在这里插入图片描述
+=拷贝了3次:分别是:
在这里插入图片描述

因此使用+复用+=更好
同理,使用-复用-=更好

5.一个"坑点"

在+=复用+的版本中
但是当我们写好了这个复用函数后会发现编译器报错
在这里插入图片描述
这是为什么呢?

是因为我们所写的赋值运算符重载函数

Date& operator=(Date& d);

这个参数列表中没有加上const修饰
而*this=*this+day;
这个表达式的右值返回的是一个临时变量,而临时变量具有常性
也就可以认为这个临时变量具有const修饰

然后继续调用赋值运算符重载函数,可是赋值运算符重载函数的参数并没有const修饰,因此出现了权限的放大问题,进而报错

同理,如果我们这样调用拷贝构造函数也是如此

Date d((*this) + day);

在这里插入图片描述
当我们加上const之后就没事了
在这里插入图片描述

五.++运算符重载

1.前置++的运算符重载

我们可以直接复用+=运算符

//前置++:拷贝0次
Date& operator++()
{
    
    
	*this += 1;
	return *this;
}

这里注意:
因为+=已经考虑了不同月份的具体天数不同,因此我们这里就不需要去管不同月份天数的影响了
在这里插入图片描述

2.后置++

C++创始人规定
前置++这样去定义:

Date& operator++()

后置++这样去定义:

Date operator++(int)

这里就印证了我们之前在学习缺省参数的时候学习到的一点:

编译器允许函数的参数列表中的参数只写类型,不写名称

这个参数设计的目的就是为了区分前置++和后置++
那么为什么要设计为int呢?

没有为什么,C++创始人就是这么规定的

六.日期跟天数相减的运算符重载

1.怎么相减呢?

在这里插入图片描述
注意这里的红字
就是先
_day-=day;
然后向月份借天数,注意:向前一个月份借天数
直到_day>0为止

注意:
如果有人就是想要-=一个负数
那就相当于+=这个负数的相反数
因此我们在这里加了一个if先去判断

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

同理:对于+=,我们也可以去改进一下

Date& operator+=(int day)
{
    
    
	//加一个负数==减去这个负数的相反数
	if (day < 0)
	{
    
    
		return (*this) -= (-day);
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
    
    
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
    
    
			_month = 1;
			_year++;
		}
	}
	return *this;
}

2.-复用-=

Date operator-(int day)
{
    
    
	Date d(*this);
	d -= day;
	return d;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

七.日期-日期计算天数

还有最后一个需求
我们想要去计算两个日期之间相差多少天
因为每个月的天数都不相同,而且还有闰年的干扰
所以只能这么去计算相差多少天

int operator-(Date& d)
{
    
    
	Date max = *this;
	Date min = d;
	int flag = 1;//假设:*this大于d,左大于右     左-右
	//假设错误
	if (min > max)
	{
    
    
		max = d;
		min = *this;
		flag = -1;//右大于左
	}
	int day = 0;
	while (min < max)
	{
    
    
		//++min;//++min是代码实现是调用+=的,那我直接调用+=不好吗,更快
		min += 1;
		day++;
	}
	return flag * day;
}

在这里插入图片描述
我们在网上找一下日期转换工具测试一下
在这里插入图片描述
在这里插入图片描述

八.流插入和流提取运算符重载

1.流插入运算符重载的引出

我们之前提到过C++创始人对于C语言中的左移运算符<<和右移运算符>>
又分别添加了一种语法
规定<<不仅可以表示左移运算符,也可以表示流插入运算符
/>>不仅可以表示右移运算符,也可以表示流提取运算符

对于内置类型:
我们可以这样去打印:
在这里插入图片描述
说明流插入是可以自动识别类型的
那么我们可不可以这样做呢?
在这里插入图片描述
当然是做不了的
为什么呢?
因为当我们没有重载流插入运算符时,编译器是无法得知我们想要怎样打印这个自定义类型的
因此流插入运算符是无法识别自定义类型的

那么为什么流插入运算符能够自动识别内置类型呢?
在这里插入图片描述
其实是因为C++库中已经实现好了这些自定义类型的流插入运算符重载函数

而cout和cin的类型分别是:

cout的类型是:ostream
cin的类型是:istream

我们可以这么理解:
平常我们使用cout来打印数据时
例如:

cout<<100;

本质是调用了<<的一个重载版本:
我们可以理解为:cout是这样打印的
在这个运算符重载的类当中
cout被隐含的this指针传过去,而另一个参数就是我们要打印的这个100

这个<<的重载函数是这样的:
ostream& operator<<(int val);
cout.operator<<(100);

在这里插入图片描述

2.流插入运算符重载的类内版本

于是我们就可以写出<<的重载版本

void Date::operator<<(ostream& out)
{
    
    
	out << _year << " " << _month << " " << _day << endl;
}

这里我们用out作为参数,跟cout的名字区分一下
其实当我们调用cout<<d1;来打印的时候
我们会把cout传入并用out来接收,而且这个out的类型是一个引用,也就是说这个out就是cout的一个别名

然后我们去打印,但是发现了一个"奇怪"的现象

3.类内版本的局限性

在这里插入图片描述
使用cout<<d1;还是无法打印,为什么啊?
但是更奇怪的现象是:
在这里插入图片描述
使用d1<<cout就能打印了
这是为什么呢?

我们可以这样理解:
在这里插入图片描述

4.友元

在这里插入图片描述
实现一下:

void operator<<(ostream& out, const Date& d)
{
    
    
	out << d._year << " " << d._month << " " << d._day << endl;
}

在这里插入图片描述
此时就可以正常运行了
在这里插入图片描述
在这里插入图片描述
因此我们只需要这样做
在这里插入图片描述
在这里插入图片描述
就可以正常运行了

5.想要连续进行流插入怎么办呢?

下面的问题是<<流插入运算符是支持连续进行的啊
那么我们可不可以也这样呢?
在这里插入图片描述
报错
为什么呢?
因为我们刚才实现的是
在这里插入图片描述
上面的分析过程就有点类似于物理中的整体+隔离法进行受力分析了
下面我们来实现一下:

ostream& operator<<(ostream& out, const Date& d)
{
    
    
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}

在这里插入图片描述

6.流提取>>运算符重载

类似于流插入<<,流提取也可以按照同样的思路去重载

istream& operator>>(istream& in, Date& d)
{
    
    
	in >> d._year >> d._month >> d._day;
	return in;
}

在这里插入图片描述

7.C++为什么要设计流插入和流提取呢?

在这里插入图片描述

九.const成员函数

1.为什么要有const成员函数

在这里插入图片描述
有些情况下,我们需要定义出const修饰的类型的对象
而这时,因为我们的成员函数中的this指针默认是没有const去修饰的
那么这种情况下就会出现这种错误

怎么办呢?
我们无法显式定义this指针,那么就无法直接在this指针的类型前面加上const修饰

于是C++创始人规定:
我们可以这样去给一个成员函数的this指针加上const修饰

void Print() const
{
    
    
	cout << _year << " " << _month << " " << _day << endl;
}

就是在函数的()后面加上const
在这里插入图片描述

2.成员函数没有加const的意想不到的错误

在这里插入图片描述
下面就是一个典型的错误示范:
在这里插入图片描述
在这里插入图片描述
而当我们给>运算符函数加上const修饰之后
在这里插入图片描述
正常运行

3.const成员函数定义的原则

在这里插入图片描述

4.几个小问题

在这里插入图片描述
下面我们来验证一下:
第4条
在这里插入图片描述
第3条:
在这里插入图片描述

十.取地址运算符重载

这是最后两个类的默认成员函数

既然是类的默认成员函数,那么就说明:如果我们没有定义取地址运算符重载函数,那么编译器会生成默认的取地址运算符重载函数

它们的具体实现如下:

Date* operator&()
{
    
    
	return this;
}
const Date* operator&() const
{
    
    
	return this;
}

这两个运算符一般不需要重载,使用编译器生成的即可,只有特殊情况,才需要重载,比如不想让别人得到正确的地址

Date* operator&()
{
    
    
	return nullptr;
	//或者直接返回一个错误地址
	return (Date*)0x11223344;
}

在这里插入图片描述

十一.具体的解决方案(日期类的完善)

我们将Date类的声明跟定义分离
结果发现:

1.缺省参数的一个常见错误

在这里插入图片描述
为什么会报错呢?
这个原因我们在学习缺省参数的时候提到过,

也就是:缺省参数的缺省值不能同时在声明和定义中都有
规定要在声明中给出,定义中不要给缺省值
在这里插入图片描述
这样就可以了

2.Date.h

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
class Date
{
    
    
public:
	void Print() const;
	Date(int year = 1, int month = 1, int day = 1);
	Date(const Date& d1);
	Date& operator=(const Date& d);
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator<(const Date& d) const;
	//返回year年month月有多少天
	int GetMonthDay(int year, int month);
	//上面这个+复用+=更好
	//拷贝0次
	Date& operator+=(int day);
	//+复用+=
	//拷贝2次
	Date operator+(int day) const;
	//前置++:拷贝0次
	Date& operator++();
	//后置++,为什么是int呢?,C++创始人定的语法,没有为什么
	//拷贝2次
	Date operator++(int);
	//前置++拷贝0次,后置++拷贝2次,因此前置++更好
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(const Date& d) const;

	friend ostream& operator<<(ostream& out, const Date& d);

	friend istream& operator>>(istream& in, Date& d);

	Date* operator&()
	{
    
    
		return this;
	}
	const Date* operator&() const
	{
    
    
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.Date.cpp

#include "Date.h"
void Date::Print() const
{
    
    
	cout << _year << " " << _month << " " << _day << endl;
}
Date::Date(int year, int month, int day)
{
    
    
	//对传入的年份进行检查
	if ((year < 0) || (month < 1 || month>12) || (day<1 || day>GetMonthDay(year, month)))
	{
    
    
		//assert(false);//粗暴一点的方法
		Print();//本质是:this->Print();
		cout << "输入的日期为非法日期" << endl;
		exit(-1);
	}
	_year = year;
	_month = month;
	_day = day;
}
Date::Date(const Date& d1)
{
    
    
	//对传入的年份进行检查
	if ((d1._year < 0) || (d1._month < 1 || d1._month>12) || (d1._day<1 || d1._day>GetMonthDay(d1._year, d1._month)))
	{
    
    
		//assert(false);//粗暴一点的方法
		Print();//本质是:this->Print();
		cout << "拷贝对象的日期为非法日期" << endl;
		cout << "无法进行拷贝" << endl;
		exit(-1);
	}
	_year = d1._year;
	_month = d1._month;
	_day = d1._day;
}
Date& Date::operator=(const Date& d)
{
    
    
	//对传入的日期进行检查
	if ((d._year < 0) || (d._month < 1 || d._month>12) || (d._day<1 || d._day>GetMonthDay(d._year, d._month)))
	{
    
    
		//assert(false);//粗暴一点的方法
		cout << "拷贝对象的日期为非法日期: ";
		cout << "无法进行拷贝" << endl;
		exit(-1);
	}
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
bool Date::operator==(const Date& d) const
{
    
    
	return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator!=(const Date& d) const
{
    
    
	return ((*this) == d);
}
bool Date::operator>(const Date& d) const
{
    
    
	if (_year > d._year)
	{
    
    
		return true;
	}
	if (_year == d._year && _month > d._month)
	{
    
    
		return true;
	}
	if (_year == d._year && _month == d._month && _day > d._day)
	{
    
    
		return true;
	}
	return false;
}
bool Date::operator<=(const Date& d) const
{
    
    
	return !((*this) > d);
}
bool Date::operator>=(const Date& d) const
{
    
    
	return (*this) > d || (*this) == d;
}
bool Date::operator<(const Date& d) const
{
    
    
	return !((*this) >= d);
}

//返回year年month月有多少天
int Date::GetMonthDay(int year, int month)
{
    
    
	int MonthArray[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)))
	{
    
    
		MonthArray[month]++;
	}
	return MonthArray[month];
}

//上面这个+复用+=更好
//拷贝0次
Date& Date::operator+=(int day)
{
    
    
	//加一个负数==减去这个负数的相反数
	if (day < 0)
	{
    
    
		return (*this) -= -day;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
    
    
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
    
    
			_month = 1;
			_year++;
		}
	}
	return *this;
}

//+复用+=
//拷贝2次
Date Date::operator+(int day) const
{
    
    
	Date d1(*this);
	d1 += day;
	return d1;
}
//前置++:拷贝0次
Date& Date::operator++()
{
    
    
	*this += 1;
	return *this;
}
//后置++,为什么是int呢?,C++创始人定的语法,没有为什么
//拷贝2次
Date Date::operator++(int)
{
    
    
	Date d(*this);
	//+=已经考虑了不同月份日期不同
	*this += 1;
	return d;
}
//前置++拷贝0次,后置++拷贝2次,因此前置++更好
Date& Date::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;
}
Date Date::operator-(int day) const
{
    
    
	Date d(*this);
	d -= day;
	return d;
}
int Date::operator-(const Date& d) const
{
    
    
	Date max = *this;
	Date min = d;
	int flag = 1;//假设:*this大于d,左大于右     左-右
	//假设错误
	if (min > max)
	{
    
    
		max = d;
		min = *this;
		flag = -1;//右大于左
	}
	int day = 0;
	while (min < max)
	{
    
    
		//++min;//++min是代码实现是调用+=的,那我直接调用+=不好吗,更快
		min += 1;
		day++;
	}
	return flag * day;
}
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;
}

以上就是
C++类和对象中:运算符重载+const成员函数+日期类的完善
的全部内容,
希望能对大家有所帮助!

猜你喜欢

转载自blog.csdn.net/Wzs040810/article/details/133985726