【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. 全局中的函数无法随意调用类中的私人成员
就会出现这样尴尬的场面(这里有解决方法,但是还是留在之后讲)
但是如果将运算符重载函数写在类中,就可以随意调用了。
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;
}
所以从上面的实现中可以看出
当我们用自定义类型的++时,最好使用前置++
因为后置++需要创建一个新对象,会影响效率。
同样对于内置类型前置++和后置++
虽然也大致是这样的原理
前置++比后置++更加有效率
但是因为内置类型拷贝成本比较低
所以没有特意去区分前置++和后置++的使用
(=)赋值运算符的重载
我们知道,赋值运算符也是默认成员函数之一
就是用户不写时,编译器会自己生成。
目的:将一个对象的值,赋值给另一个对象
实现
这个赋值构造函数可能看着没啥问题
但是别忘了,对于内置类型来说,赋值能这样做
x=y=c=a
能做到这样的连续赋值。
但是我们的这个实现方法不能进行连续赋值
原因就在于:
返回类型为空。
所以这里需要把赋值后的原对象进行返回
Date& operator=(Date& d1)
{
_year = d1._year;
_day = d1._day;
_month = d1._month;
return (*this);
}
后话
这里其实还有很多内容没有去实现。
比如说
对象与对象的运算符
后置–与前置–
但是这里主要是想带大家学习一下重载运算符而已,
所以这个日期类就点到为止。