引言:
北京时间:2023/2/7/21:03,昨天熬夜写完博客,导致早上没起来,然后下午有课,只能到现在开始写博客了,刚刚玩了一下斗地主,发现斗地主这个游戏,玩起来还是挺舒服的,想当初我也是一个至尊70多个星的小佬佬,并且手持200多万的豆豆(大概两年前),当时真的是叱咤风云,谁与争锋,哈哈哈!很久很久没玩了,刚刚玩了一下,广告看了10几个,哈哈哈!所以各位假如无聊了,不知道干嘛的时候,不要打开腾讯视频、爱奇艺、优酷,更不要打开抖音、快手之类的,虽然我很喜欢打开腾讯视频,哈哈哈!因为你总会在这些东西里面,发现好看的东西,然后不能自拔,我就是典型案例,哈哈哈!所以我们以后无聊了,就去斗地主,斗着斗着,你就把豆子输光了,很好的就能抵制不能自拔,哈哈哈!好了,多的不说,今天我们继续学习我们的类和对象中的相关知识,并且我意识到再有两篇博客我们的类和对象就可以学完了,就可以算是正式的步入了C++门槛,So Let’s go !
规范实现日期类
在我们学习新的知识之前,我们先来把我们以前学习的有关类和对象的知识像什么拷贝构造,构造函数,析构函数,运算符重载、赋值重载、和一系列有关类内部的知识复习一下,我们就通过日期类的实现来完成这个工作吧!
实现日期类代码:(一图带你搞懂日期类)
上图就是有关日期类的完整代码,细节方面都有标注,如有某些地方写的不好,欢迎点评,此时我们把类和对象的知识和相关知识差不多都给复习了一遍,此时我们就继续学习一下别的有关类和对象的知识吧!
浅浅的摸一下io流
此时搞定了上述知识,我们会发现,除了上篇文章说的那5个运算符不能重载之外,其它的运算符是可以重载的,所以我们的流插入(<<) 运算符也是允许重载的,接下来我们就学习一下流插入运算符的重载方式和具体原理。
首先,我们搞清楚流插入的具体使用原理,如上图,此时我们可以发现,流插入运算符是在我们C++中的 < ios >库中的< ostream >库中的,在我们的<iostream>这个头文件中,所以我们如果想使用cout函数就必须要引入头文件#include<iostream>,但此时就算我们引入了头文件之后,我们还会发现一个问题,就是如图的这个问题,此时我们打印的时候,只能对内置类型打印,却不能对自定义类型进行打印,这是为什么呢?此时就涉及到一个新的知识点,叫自定义类型除了我们的(赋值运算符)可以直接使用之外,别的运算符都不允许直接给自定义类型使用,这又是为什么呢?原因就是:赋值运算符是一个默认成员函数,无论是自定义类型还是内置类型,都可以直接使用该运算符,所以得出结论:我的自定义类型,如果想要使用除了赋值运算符之外的任何一个运算符(当然这里特指的就是<<),都一定要去把该运算符进行重载(就是自我实现),否则自定义类型,永远用不了除了赋值运算符以外的运算符。但是此时又有一个问题来了,为什么我的内置类型可以直接使用流插入运算符呢?,此时原因就是: 在我们的<iostream>库中,库中的<ostream>已经把有关内置类型的所有的流插入运算符进行了重载,所以我们可以在包含了头文件的情况下,是可以直接使用流插入(<<) 打印内置类型的,所以此时第一个问题的答案就来了,就是因为<iostream>库中,并没有自定义类型的流插入运算符重载,所以此时库中没有,自然不能直接调用,就要通过自己实现了,并且有人会问,为什么<iostream>库没有自定义类型的流插入重载呢?这个问题非常好回答,自然就是因为:我们的编译器并不能控制我们的自定义对象,因为自定义对象是我们自己实现出来的,编译器中并没有这个设定,此时就只能通过自己来完成这个自定义类型的流插入重载。
总:所以本质上根本没有什么自动实现,都是通过调用已经实现了的重载而已,只是看这个重载需不需要你自己去实现而已(是否已经存在)。
例:下图中就是我们的<ostream>库中的内置类型的流插入(<<) 函数的重载,不仅涉及运算符重载,还涉及函数重载:
自定义类型流插入的实现
弄懂了这些,我们知道了自定义类型除了赋值运算符之外是没有其它运算符重载函数可以使用的,所以此时我们为了自定义类型可以直接使用流插入(<<) 运算符,我们是必须要去自己实现流插入(<<) 运算符的重载的,因为不重载,我们就不能打印自定义类型,所以运算符重载的意义就是可以支持自定义类型的使用,如果C++中没有运算符重载,那么此时流插入(<<) 使用不了,那么自定义类型就无法被打印,原因:因为在C++中,我的类是受到访问限定符的限制的,此时如果没有重载流插入(<<),单单只是用C语言中的printf,此时就会因为printf只能直接访问类中的公有成员变量,不能访问私有的成员变量,就会变得较为不好处理,所以此时就有了运算符重载这个概念,特指我们的流插入(<<) 此时就可以很好的使用运算符的重载对无论是内置类型,还是自定义类型都可以很好的进行打印了。所以此时我们就自己实现一下流插入(<<) 这个运算符吧!
此时的实现方法有两种,一种是把流插入(<<) 运算符函数写在我们的类中,一种是把它写在全局中(也就是类外),此时我们就先来看一下将其写在类中的场景:
此时通过上图,我们就可以发现,在流插入(<<) 运算符中,左操作数是第一个参数,右操作数是第二个参数,所以现在发现不是d1流入cout,而是cout流入d1中,所以我们猜想应该是在传递参数的时候出现了问题,但是我们又会发现,我们并不能传递cout作为第一个参数,d1作为第二个参数,因为d1参数的位置,在我们的类中,就被默认成了this指针,而我们并不能对this指针进行任何的修改,所有如果我们把该函数写在类中的话,就会导致流插入(<<) 运算符的左边永远都是this指针,也就是我们创建的对象,永远不可能是我们的cout函数,此时就可以发现,我们如果把该函数写在类中是不能完成我们的预期的,所以我们将流插入(<<) 运算符函数写在我们的类中是不成立的,所以我们使用第二个场景。
写在类外的场景:
此时按照这样子写,有了返回值之后,我们的流插入(<<) 运算符就算是真正的自己实现好了,并且记住想要重载这个运算符,首先就一定要去库里面调用该运算符所在的那个类,也就是<ostream>类中,这是我们实现这个流插入(<<) 运算符的首要条件,没有这个类就不能实现该运算符的自定义,也就不能使自定义对象得到很好的打印了。
流提取的实现
搞定了流插入,此时我们肯定是接着搞定我们的流提取,大致原理差不多,此时我们就不做过详细的介绍了,如下图:
const成员函数
搞明白了上述的知识,我们把运算符的重载就算是基本上搞清楚得差不多了,此时我们就可以进入到新的知识的学习,所以我们现在就来看一看什么是const成员函数,总而言之看到成员函数,我们就应该要想到是在我们的类中的,所以const成员函数,最大的前提就是,该函数是在类中,并且该函数是被const修饰的,是具有常属性的,当的对象就行调用该函数的时候,是不会发生权利放大的,只有可能是权利缩小,所以加上了const的成员函数是非常的安全的,当然前提是合适的情况之下,如图:我们通过一个小问题,正式的来看一看什么是const成员函数。
问题:
此时问题的原因就在于其涉及了一个权利放大的问题,因为从图中,我们可以看出,此时的a是被const修饰了的,并且a直接去调用了Print函数,且因为Print函数是在类中的,所以a代表的就是就是我们的this指针,且a是具有常属性的,所以不能改变,所以此时的this指针也就更不可能可以改变,所以此时是不可能可以把a传给this指针的,因为如果可以传给this指针的话,那么this指针就可以拿着这块地址,把a给改变,此时就会造成权利放大的问题,编译器是不可能允许的,所以此时想要让a可以调用Print的话,就一定要让this指针也具有const(常属性),这样才可以形成权利的保持,a才可以调用,但此时问题就来了,this指针怎样才可以被const修饰呢? this指针是一个隐藏起来的变量,要怎样才可以使它有常属性了,面对这个问题,此时我们的const成员函数就可以很好的解决,直接让整个成员函数具有常属性,这样this自然而然的就也具有常属性了。
所以解决方法就是在该函数的后面,跟上一个const就行了(编译器规定好了的)
如图:
并且因为平时我们为了减少拷贝构造,一般会在接收参数的使用,使用传引用的方法传参,并且我们传引用传参,一般都会加上const,此时加上了const,在进行对象调用成员函数的时候,就非常的容易导致对象因为权限放大的问题而报错,而不可以去调用相应的函数,所以此时不管怎样,我们都最好是给我们的成员函数加上一个const,这样不管是什么情况,我都可以很好的去调用该函数了,因为这样就不可能出现权利放大的问题,函数调用就不会因为该问题而出问题了,所以按照这个原理,此时我们的类中的函数都可以适当的加上一个const,像我们昨天实现的日期类中的比较大小的、判断相不相等的、反正只要是内部变量不会被修改的,就都可以加上const,这样就可以使我们的代码变得更加的规范和安全。
但是注意:我们的+和-其实本质上是可以加上const的,只有像+=和-=和++和–这种才不可以(前提:在类中,并且属于是成员函数,像友元并不算成员函数的)并且声明和定义都要加。
取地址和const取地址操作符重载
搞定了上述的const成员函数,此时我们就最后来看一看我们的6大默认成员函数中最后两个取地址和const取地址操作符重载,首先这两个默认成员函数是满足默认成员函数的特性的。我们不自己实现,这个函数编译器也是会自己去实现的。
取地址重载函数的自己实现
取地址函数的使用