【C++】运算符重载(日期类的实现)

【C++】运算符重载(日期类的实现)

  • 前言
  • 运算符重载
    • operator
      • 全局和类中
  • 日期类的实现
    • 成员变量的确定
    • 构造函数
      • 拷贝构造
    • 运算符重载部分
      • +=的重载
        • 思路
        • 实现
          • GETmonthday
          • operator+=
      • +的重载
        • 思路
        • 实现
      • -=的与-的重载
        • 实现
      • 各个比较运算符的重载
        • 实现
      • 前置++与后置++
        • 实现
      • (=)赋值运算符的重载
        • 实现
  • 后话

前言

博主在快写完类和对象(中)时,突然发现需要用到运算符重载的知识
但是运算符有很多运算符要进行重载,会导致文章的篇幅略长
所以就单独写了一个,另外,日期类是学习运算符重载比较好的一个例子,所以这里就用了日期类来讲解运算符重载

运算符重载

众所周知,对于内置类型我们可以直接使用运算符进行计算
int y=1, int x=1 x+y
这显然是可以的

那自定义类型可不可以直接用运算符来算呢?
比如
stack s1; stack s2; s1+s2

当然是不可以的

这不是显而易见吗
现在编译器也不至于聪明到能直接分辨出用户的自定义类型的运算。
要是编译器真的高级到这个程度,那就是更高级的语言了

但是类如果要进行加减乘除的时候又要进行函数的调用

如果能直接用加减乘除等操作符的话,那不是能大大增强函数的可读性吗?

所以就有了关键字

operator

运算符重载函数的形式为:
void(返回类型)operator<(需要重载的运算符)(参数)
例:

Date& operator+=(int x);

这里在使用operator进行重载时
有几个点需要注意:

1.不可以用operator重载一个原本没有意义的符号。

如:operator@

2.运算符重载必须有一个自定义类型的对象

因为本来运算符重置就是为了自定义类而创建
要是没有自定义类的对象参数
那还有啥意义。

在这里插入图片描述

这五个运算符不能进行重载。

4.讲了很多次
自定义类型深究到最后,也就只是内置类型

所以当我们编写运算符重载函数的时候:
内置类型的运算符含义不能进行改变

你想改也改不了。

全局和类中

这里来给大家提个问题,全局和类,有啥区别

1. 全局中的函数无法随意调用类中的私人成员

【C++】运算符重载(日期类的实现)_第1张图片
就会出现这样尴尬的场面(这里有解决方法,但是还是留在之后讲)

但是如果将运算符重载函数写在类中,就可以随意调用了。

2. 类中有this指针这个存在

这意味着运算符重载在全局和类中有不同的定义方式

上面我们看到了:

全局对象需要完全地写出参数

但是在类中的函数因为有this指针的隐形传参,所以不需要将全部参数写出来

class Date
{
	Date& operator+=(int day) 
	{

	}
}

这里this指针将对象的地址传了过去

所以不需要像全局变量一样传一个类的对象形参

所以为了方便,还是尽量讲运算符重载函数写在类中。

日期类的实现

日期算是我们生活中常见的了。

成员变量的确定


年-月-日组成

所以我们这里针对成员变量的设计也是十分容易

class Date
{
private:
	int _year;
	int _month;
	int _day;
};

构造函数

还记得我们这里要创建构造函数的原因吗?

因为date类中全是内置类型
编译器自动生成的默认构造函数不会对内置类型变量进行设定。
所以遇到内置类型就要自己写构造函数了

这里就写个缺省的和无参的

//缺省
 Date(int year,int month,int day)
 {
    _year = year;
	_month = month;
	_day = day;
 }
 //无参
Date()
{
	_year = 2023;
	_month = 5;
	_day = 24;
}

拷贝构造

因为拷贝构造属于构造函数里的,这里就直接把它放在这里了

Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

运算符重载部分

这个部分算是正式进入我们的正题了

这里的函数我都默认放在类中进行实现的。

+=的重载

这里要实现的功能是:

日期+=天数 =>(返回)新日期

注意这个运算符号是+=,原本就是要对前一个值进行改变的。
所以我们这里就是对 原对象进行+=,然后返回原对象就好

这里理解起来很容易,但是要实现起来的话还是有点小繁琐的。

思路

想要得到日期+天数转化为日期。

1.首先要得到知道每个月的天数

2.将现在日期的天数和传参的天数相加

3.判断,现在天数是否大于这个月应有的天数

若大于:
扣除每个月对应的天数,然后月份+1

若小于:
则日期已经++完成,直接返回就好

实现

我们要知道所在的这个月的天数,所以我们需要设定一个函数专门用来返回当月的对应的天数

GETmonthday
int GETmonthday(int year, int month)
{
	static int arr[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;
	}
	return arr[month];
}

这里通过静态数组将每个月对应的天数存储下来
(因为这个函数在后续需要多次调用,所以使用静态数组,防止多次开辟空间影响效率)

通过传来的年月,来进行 闰年/平年 对应月 的判断
得到对应的天数后返回。

operator+=
//这里返回值用了引用,因为+=返回的就是原对象,函数结束后没有随着函数被销毁。
Date& operator+=(int day) 
{
	_day += day;
	//将天数和天数相加
	while (_day > this->GETmonthday(_year,_month))
	//当现有天数大于本月总天数,程序继续
	{
		_day -= GETmonthday(_year, _month);
		_month++;
		//天数减去这个月对应天数,当前月份++
		if (_month > 12)
		//当月份大于12时,年++,当前月份变为1
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
	//返回改变后的天数
}

+的重载

思路

+和+=有啥区别?

+=对原对象进行了改变,并且返回的也是原对象

而+ 就不一样了:
不会对原对象进行赋值改变
并且返回的是:原对象与天数相加后的新对象

实现

这里搞懂了区别后就可以进行实现了

这里就会发现:
其实+=和+中间的对象与天数相加的思路一样
就是需要
1.返回新对象
2.原对象不能进行改变。

Date operator+(int day)
{
//用原对象进行了拷贝构造出了tmp
	Date tmp(*this);
	
//将tmp进行+=(用的是之前实现的+=重载)
	tmp += day;
//返回tmp
	return tmp;
}

-=的与-的重载

这里其实思路和+=与+的重载相似,所以这里就不多讲了
因为这篇博客主要是想讲清不同运算符重载的实现

实现
//-=的实现
Date& operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		_day += GETmonthday(_year, _month);
		_month--;
		if (_month < 1)
		{
			_month = 12;
			_year--;
		}
	}
	return *this;
}
}
//-的实现
Date operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

各个比较运算符的重载

这里比较运算符算是最简单的运算符重载了

传入一个Date类对象
和this指针的本对象进行年月日比较
最后返回布尔值即可

实现
//<的实现
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 _day == d._day && _year == d._year && _month == d._month;
}
//大于等于的实现
bool operator>=(const Date& d)
{
	return !(*this < d);
}
//小于等于的实现
bool operator<=(const Date& d)
{
	return !(*this > d);
}
//不等于的是实现
bool operator!=(const Date& d)
{
	return !(*this == d);

}

前置++与后置++

这里还是和之前一样,来搞清楚这两个的区别

前置++

直接对象+1,然后返回对象值即可

后置++
先返回对象的原值,然后对象值++
这里的实现思路肯定不是这样,return值了以后函数就结束,怎么还能++

所以这里就要用原对象创建新对象
对原对象++,返回创建的新对象

搞清楚思路了以后,接下来还有个问题

区分两者

还记的运算符重载的命名方式吗

void(返回类型)operator<(需要重载的运算符)(参数)

但是前置++和后置++,都是++运算符

所以定义时都是operator++

这就不好区分了

但是祖师爷在定义的时候就发现

自增运算符都是对单个对象的运算

所以传参时不需要传任何参数
(原对象this指针会进行传递)

那让编译器给前置运算符或者前置运算符
随便传一个参数不就可以识别了吗

所以选择了后置++

Date& Date::operator++()
//前置++
Date Date::operator++(int)
//后置++
//用户自己声明时,要在后置运算符加上int参数
实现
//前置++
Date& Date::operator++()
{
	return (*this += 1);
}

//后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

所以从上面的实现中可以看出

当我们用自定义类型的++时,最好使用前置++

因为后置++需要创建一个新对象,会影响效率。

同样对于内置类型前置++和后置++

虽然也大致是这样的原理
前置++比后置++更加有效率
但是因为内置类型拷贝成本比较低
所以没有特意去区分前置++和后置++的使用

(=)赋值运算符的重载

我们知道,赋值运算符也是默认成员函数之一
就是用户不写时,编译器会自己生成。

目的:将一个对象的值,赋值给另一个对象

实现

【C++】运算符重载(日期类的实现)_第2张图片

这个赋值构造函数可能看着没啥问题

但是别忘了,对于内置类型来说,赋值能这样做

x=y=c=a
能做到这样的连续赋值。

但是我们的这个实现方法不能进行连续赋值

原因就在于:
返回类型为空

所以这里需要把赋值后的原对象进行返回

Date& operator=(Date& d1)
{
	_year = d1._year;
	_day = d1._day;
	_month = d1._month;
	return (*this);
}

后话

这里其实还有很多内容没有去实现。

比如说
对象与对象的运算符
后置–与前置–
但是这里主要是想带大家学习一下重载运算符而已,
所以这个日期类就点到为止。

你可能感兴趣的:(c++)