赋值运算符重载(下)

        我们在上一个博客当中只写了部分的运算符的重载,我们将在这篇博客当中把他们补充完整。

        赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟 拷⻉构造区分,拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象。
赋值运算符重载的特点:
1. 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成
const 当前类类型引⽤,否则会传值传参会有拷⻉。
2. 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋 值场景。
3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷 ⻉构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数。
4. 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就 可以完成需要的拷⻉,所以不需要我们显⽰实现赋值运算符重载。像Stack这样的类,虽然也都是 内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我 们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部 主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载, 也不需要我们显⽰实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显⽰实现 了析构并释放资源,那么他就需要显⽰写赋值运算符重载,否则就不需要。

一 所有的运算符的重载

        接下来我们将用一个日期类来写这个运算符的重载操作。
        以上就是我们所需要的一个日期类以及我们要完成的所有的方法。
        那个GetMonthDay这个函数的作用就是来获取每个月份的天数的,通过一个if语句来判断是否为闰年。
        接下来我先来说一下这个运算符重载的作用吧,我们正常的两个数相加比如int i=10,int j=20,i+j=30,这样是合法的可以完成的,但是类对象就无法使用这些东西了,就拿我们的日期类来说我们想让某个日期加上一个天数,这是无法实现的,所以我们必须自己写一个类对象也可以用的运算符方法了。

1.1 operator+=方法

        这是我们的构造方法和加等操作,我们先来完成一个加等操作来看一下,简单了解一下。

        我们先不要看上面的那个小于0的操作,这要结合后面的方法我们才能看懂,首先,我们要知道的是加等操作是对自己进行操作的,改变的是自己本身的值,所以这里我们可以用传引用返回的方式,更加的节约空间,因为你传入的对象是在方法的外部的,不会随着这个函数的销毁而销毁,所以我们可以使用传引用返回的方式,我们先让我们需要加的天数先加在原来的天数上,此时我们再进行操作,通过一个while循环直到我们的_day小于自己月份所对应的天数时结束,此时就是图中的操作的,每次让天数减去那个月所对应的天数,然后再让月份加加,最后就可以完成这个操作了,月满了,再让年份++就可以了,最后再返回我们这个对象就可以了。

        为什么必须要有返回值呢?

        为了防止连续的加等操作的发生,如果没有返回值,此时就会报错了。

        如果没有返回值,此时就会报错了。

        我们来验证一下上面的那个方法。

        

        我们发现结果是符合预期的,此时我们的d2就是这个方法中的*this,这里的1就是我们传给形参的值。

1.2 operator+方法

        

        我们这个+的操作就是另外创建一个对象了,而不是在原有的对象上面操作,比如这个int i=10,i+10这个操作并没有改变i本身的值,而是需要存放到一个新对象当中。

        所以我们就要创建一个新对象tmp了,通过*this来进行初始化操作,然后和上面一样,但是要对tmp进行操作了,而不是这里的*this操作了。

        因为这里的tmp操作是在这个函数当中创建的,所以我们就需要传值返回了,返回它的一个副本了,而不能传引用返回,因为它出函数之后就被销毁了。

        这俩函数可以合并一下,我们来写一下,当我们+=函数正常写的时候,我们的加操作就可以写生如下函数了。

        

        这样就使得代码的复用性更高了。

        当我们的+操作正常写的时候,我们的+=操作就可以写成如下方式:

        

        直接让返回操作通过=的运算符重载给了*this。

        有了两种方式我们就要分析它们谁更好了,我们先看第一个,我们在调用第一个的时候,是0次拷贝,调用复用的第二个代码的时候,是拷贝了两次,调用一次,返回一次,一共两次,

但是第二种,我们调用第一个的时候,拷贝了两次,我们调用第二个的时候,后面的*this+day又拷贝了一次,一共三次,所以第一种更好,我们建议使用第一种方式的复用。

        


1.3 operator-方法

        

        这个和+的赋值运算操作差不多相似,原理也一样,看一下理解一下即可。

        


1.4 operator-=方法

        

        这是我们的-=操作,这两个也是可以复用的,大家可以练习一下。


1.5 operator<方法

        这个我们这个就是我们的比较操作了,谁调用这些函数谁就是*this,这里的参数为什么要用const呢?因为我们的比较操作是只读操作,不涉及修改,所以我们可以加一个const,此时我们既可以传const Date类型的类对象,也可以传Date类型的类对象了,如果我们不加const,无法传const Date类型的对象了,传了就会涉及到权限的放大了,就会报错了。


1.6 operator==方法
        

        这是我们的相等的操作了,只需要让他们的值全部相等就可以了,全部相等就返回true,有一个不相等就放回false。

        


1.7 operator<=方法

        此时我们就又要使用到代码的复用了,<=是什么意思呢?是不是小于和等于随便成立一个就返回true,对吧,我们的代码就可以这样写了。

        

        小于和等于其中一个成立即可,直接使用刚才那两个重载的运算符即可了。

        


1.8 operator>方法

        大于的意思就是不小于和等于就行,所以我们的代码可以这样写了。

        


1.9 operator>=方法

        大于等于的意思就是不小于,代码就可以这样写了。

        


1.10 operator!=方法

        


1.11 operator++方法

        

        这是我们的++操作,我们都知道,++是有前置++和后置++的,这两个的区别是很大的,我们在cpp上面是如何区分这两个++操作呢?

        没有参数的代表的是前置++,返回的是++之后的结果,这里可以使用返回引用。

        为了和后置的区分,加了个参数形成了重载,这个参数无实际的意义所以不用把他定义出来,因为它返回的是++之前的结果,所以我们就要先把++之前的结果存下来,再++,再把++之前的结果返回即可。


1.12 operator--方法

        和上面的++同理。

        


1.13 operator-方法

        这个方法的作用就是来计算两个日期之间的差值的。

        

        我们用一个flag来控制差值的正负,然后再通过if判断找到比较大的那个值给max,小值给min,通过一个count来记录差值的天数,我们让小的那个值一直++,直到与大的值相等即可,此时返回这个值即可计算出这个值。

        


1.14 operator<<方法

        这个方法还是有点麻烦的,当我们把它写在类里面的时候。

        就是这样,此时这个方法是在日期类里面的,我们来实现一下。

        

        我们就实现了这个方法,但是我们在使用的时候,可能就有点别扭,我们来看一下。

        

        我们发现报错了,这是为什么呢?

  • 对于 void Date::operator<<(ostream& out) 这个成员函数,它是 Date 类的成员函数,当你尝试 cout << d1 时,实际上编译器期望的是 cout.operator<<(d1),但由于 operator<< 是 Date 类的成员函数,这样的调用不符合 C++ 的运算符重载规则。

  • 因为 cout 是 ostream 类的对象,它无法调用 Date 类的成员函数。

  • 简单来说就是因为它是一个类内部的方法,我们在调用它的时候必须使用一个类对象来调用它,而不是通过一个cout对象,这个cout是ostream对象,我们通过这样使用的时候,相当于把cout这个对象传给了this指针,这样肯定就会报错啊。

        

        我们可以通过d2<<cout的形式调用,这样既不符合我们日常使用的语法习惯,也无法同时打印多个值。

        我来举个例子。

        

        如图所示,这样还是会报错,我们该如何改变它的形式和完成多个值的连续打印呢?

        首先我们得先解决第一个问题,就是this指针在作祟,我们无法手动的调节this指针的位置的,但是我们可以消除this指针,就是把这个方法移动到全局当中,此时就不会有this指针了

        此时我们就要给两个参数了,此时我们cout<<d2就不会报错了,我们首先解决了第一个问题,我们该如何解决第二个问题呢?

        第二个问题的原因就是我们在第一次调用完成这个函数之后,无返回值,无法继续调用,有点抽象,我们来举个例子看一下。

        cout<<d1<<d2;我们在执行完cout<<d1之后由于这个函数是无返回值的,所以此时我们就变成了<<d2,我们没有传入第一个参数,所以就会报错了,解决办法就是,返回一个值做第一个参数。

        就是这样的形式了,把ostream这个类型的值再次返回作为第一个参数,此时就不会报错了。


1.15 operator>>方法

        

        这个方法也是同理。

        这个方法就是通过while,每次当你输入不合法的时候,它就会提醒你,直到你输入的信息是合法的时候才会结束循环。


1.16 CheckDate()方法

        

        这个方法的作用就是看你输入的年月日是否合法的,也是很简单的一个函数,我们再构造函数和输入流当中都有使用这个函数。


二 取地址运算符重载

        2.1 const成员函数

        

        将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后 ⾯。
        const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
        const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const
Date* const this.
        我们简单举个例子来说明一下吧。
        
        如下图为什么会报错呢?
        因为,我们的d1是const Date类型的,而我们的this指针是Date* const(在普通的类成员函数中,this 指针的类型是 类名* const this。这里的 const 修饰的是 this 指针本身,而不是 this 所指向的内容。这意味着 this 指针是一个常量指针,即 this 指针本身不能被修改(即this不能在指向其他的地方),使其指向另一个对象。但是, you 可以通过 this 指针修改其所指向对象的成员变量。)类型的,这里进行了权限的放大,肯定就会报错了。
        我们cpp提供了一种方法,就是在函数后面加const即可。
        如图所示,此时就不会报错了。
        所以我们可以在只读的函数后面都加上一个const。
        

2.2 取地址运算符重载

        

        取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当 前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。
        我们来举个例子。
        
        这里我们获取到了地址,但是如果我们不想让其他人获取到地址该怎么办呢?
        我们就可以使用取地址符运算符的重载了。
        
        这就是我们系统提供的两个类似的运算符重载了。
        
        我们这样实现一下。前面的const表示返回值类型是const Date*类型的,后面的const则表示这里的this指针是const Date* const this类型的。只要我们写了这个运算符重载之后,原来的Date* operator& ()这个方法你就可以理解它不存在了,当Date* d1这种类型的对象去调用的时候无非是权限缩小了,这是可行的,但是,如果我们函数后面没有const的话,此时我们的const Date d2,此时就无法处理这个d2了,所以默认的取地址符重载函数就是后面有const的它还存在,去处理d2,因为权限是无法放大的,我们来举个例子。
        
        我们先写第一种情况,函数后面有const的,我们先让他返回null。
        
        我们发现都调用了这个后面又const的函数,我们再把const去掉看一下。
        
        再来运行一下。
        
        我们发现d1还是调用了默认的后面又const的那个情况,而d2则调用了这个重载之后的情况。
        
        

三.结束语

        感谢大家的查看,希望可以帮助到大家,做的不是太好还请见谅,其中有什么不懂的可以留言询问,我都会一一回答。  感谢大家的一键三连。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值