Learning C++ No.2【类和对象No.1】

简介: Learning C++ No.2【类和对象No.1】

引言:

北京时间 2023/2/3/10:25,起床时间9:45,向窗外一望,天气乌云密布,给人一种阴郁的感觉,喘不过气,加上自己昨天晚上没有总结这篇博客,对,昨天就是因为想总结博客之前,手贱点了一个短视屏,然后就不能自拔了一个小时,然后中途还刷到一个打麻将的视屏,什么九五之尊,什么九莲宝灯,什么一条龙十八罗汉的那种,反正就是很有意思的感觉,然后我就手贱,下载了一下,然后打麻将又打了一个小时,发现这个游戏也不是很好玩之后,一看时间10:30多了好像,最后就想着,不熬夜,明天早起总结博客吧!但是当我躺在被窝的时候,懂的都懂,最终1:30分左右毅然决然决定睡觉。时间来到现在,所以总结一下,可以看出我本身对短视屏并不怎么感兴趣,但是我有时候会点击一些网站的提醒,点击进去又能在主页看到一些自己感兴趣的东西,又会不自觉的点进去,看完这个视屏又有一种意欲未尽的感觉,很容易把这种意犹未尽的感觉给一直维持下去,并且从我下载视屏游戏也可以看出,短视屏广告推销也是非常的令人不能自拔,所以当今的网络真的是一不下心就容易万劫不复,导致现在的我危机意识满满,并且我发现,干什么东西,都是那种意犹未尽的感觉是最可怕,比如我昨天看的赌神、赌侠、咕哩咕哩新年财,给我的感觉就是看好爽的感觉,但是结局看完因为这个爽感,导致我意犹未尽,直接导致我会一直去寻找别的很打麻将和赌牌有关的电视,OK!直接再次死循环,不过以前有时候看美剧的时候也有这种感觉,但是美剧给我的感觉没有那么持久,过了一会那种遐想后面剧情的感觉就没了,有的只是对这部电影的无限膜拜,所以……懂的都懂。今天我们接着上一篇博客,我们在学习上一篇的尾巴内容的同时,我们开始进入类和对象的学习,当然因为类和对象的细节较多,所以我们刚开始学习起来是有一定的挑战的。


复习巩固C++中的引用

引用的概念在C++中应该算是比较重要的一个概念了,该概念的提出本质上是用来弥补C语言中和指针有关的相关缺陷的,所以C++中的引用的地位我们就可以理解成是指针在C语言中的地位是一样的,所以我们应该要对引用玩的比较深一些,这样我们就可以很好的在C++中把引用用好,这样我们在学习别的内容时,就不会因为引用的理解有问题,而导致别的概念理解上有问题了,甚至可以因为引用的理解深入,而触类旁通,更好的理解好C++中引入的新概念。


引用的三个使用场景

1.我们上篇文章已经重点把引用作为一个输出型参数的使用场景给搞明白了。发现我们的引用的使用可以弱化实参和形参之间的关系,达到自己本身传参的效果,从而弱化我们对一些复杂传参上的理解压力(例:一级指针作为实参),所以我们的引用的第一个使用场景就是作为我们的输出型参数。


2.接下来我们看一看引用的第二个使用场景是什么,在我们理解引用的第二个使用场景的时候,我们首先一起来回顾一下我们在C语言中的一个小知识点,涉及我们的函数返回值的问题。当然也就是涉及我们的栈帧的问题,当然如果有的同学对栈帧不是很了解,请点击该链接函数栈帧详解,有了对栈帧的理解,我们都知道一个函数在被调用的过程中,肯定有以下过程,被调用参数从高地址到低地址依次被存储到栈中,被调用函数在栈上形成栈帧,栈上的两个指针ebp和esp为了适应栈空间的变化,并且因为我调用完该函数之后,销毁栈帧我的ebp需要可以找到上一栈帧(可以说是main函数的栈帧),所以我要保护我的原ebp栈帧,所以需要对ebp进行一个压栈操作(具体原理请百度),压栈后(也就是相当于把原先的位置记录下来之后),我们就可以利用ebp指针和esp指针形成一个能存储被调用函数空间的栈帧空间了,然后将被调用函数中的代码存储进去,之后进行系统调用,硬件处理等操作,最终得到返回值,得到返回值之后,返回给调用函数,重点:返回返回值给我的调用函数,不是直接返回,因为函数被调用完之后,栈帧就被销毁了,返回值自然也被销毁,所以真正的返回过程是,得到了返回值之后,该函数将返回值拷贝到eax寄存器中(如果变量空间较大,则有可能是拷贝到调用函数的栈帧空间中),反正就是需要另外进行存储之后,才可以被返回,所以此时被调用函数的栈帧销毁之后,我们的返回值依然可以通过临时变量(eax寄存器或者另一个栈帧空间中)返回到我们的调用函数中(大部分就是我们的main函数)。此时我们可以看出我们的函数返回值返回并不是一个容易的过程,不是一个容易的过程,我们的编译器执行起来就肯定是会有一定的消耗,所以此时我们就引入我们的新概念,引用的第二个使用场景就是作为返回值被返回。想要理解该点,就首先要搞明白不使用引用的情况下,我们的返回值的返回过程,也就是上述过程,不然不容易理解,总的来说,我们的引用作为返回值,就是把我们要返回的值,给取了一个新的名字,此时返回它的别名,本质上还是返回本身,所以我们就不需要想上述过程一样那么麻烦(当然该麻烦是你看不见的),进行拷贝之类的,所以入股我们可以直接将返回值以别名的形式返回,那么编译器的消耗肯定是会降低的。所以这就是我们引用作为返回值的好处,也就是我们学习引用的好处。

示例代码如下:


106.png

所以以上就是一个很好的使用引用返回的代码示例,从表面上你看不出和不同的返回值的区别,但是在脑海中一定要意识到,这两种返回返回值的方式是有很大差距的,并且意思是完全不同的,这点要时刻牢记,注意:并且此时我们发现,我们的Add函数中,我们的返回变量,是一个static修饰的变量,所以这里我想告诉大家的是,我们想要使用我们的引用返回,有一个很大的前提,该前提就是我们的返回值变量一定需要是,当我的被调用函数栈帧销毁时,该返回值不会被销毁的函数返回值,才可以使用我们的引用返回,否则并不可以使用引用返回,只能乖乖的使用我们的拷贝返回,当然这一切都是可以由我们写代码的人决定怎么写,你想怎么写,它就怎么返回,都是由我们自己决定的。


当然感兴趣的同学可以把以下代码理解一下:能理解证明你的引用大致已经掌握了。


0.png


0.png

上述代码中,我们使用了引用的返回值返回,返回了c变量的别名给我们的ret变量,此时又因为我们的ret变量也使用了引用,所以此时我们的ret其实本质上就是我们返回值c的别名

1.png

上述代码中,我们使用了两个引用符号,在int& PirntArr(Arr& ay,int i);函数中,我们不仅使用了引用作为输出型参数,我们也是用了引用作为返回值的方法,可以看出我们用了引用符号之后,我们可以在传参的时候,直接接收参数并且通过别名的方式使用参数本身,并且我们在返回值的时候,因为我们数组中的值是在我们的结构体数组中,没有因为函数调用完之后被销毁,所以我们就可以使用引用作为返回值的方法,直接将我的返回值通过别名的方式返回到我的调用函数中。


3.引用在权限方面的一些使用,如下图所示:


2.png


我们可以发现,当我们的一个变量被我们的const修饰之后,也就是具有了常属性之后,该变量是不允许再被引用的,只有当我们的引用此时也被const修饰之后,该变量才可以被引用,所以此现象,我们就可以称为是权限只允许被保持和缩小,不允许被放大(因为如果被const修饰的变量可以被引用的话,那么我改引用之后的别名就是在改该变量本身,但是该变量已经是被const修饰过了,如果可以使用这样子的形式进行const变量的改动,那么很多地方就会出现大问题,所以我们不允许这种语法,不允许这种编码方式,所以也就是我们不允许权利的放大)。


再如下图:


3.png


此时我们可以看出我们使用了传值的方式传参,然后返回一个返回值c,此时我们发现,我们此时并不可以对这个返回值c进行引用,这是什么原因呢?原因就是和上述的问题是一样的,因为我们c变量拷贝到我们的临时变量中时,我们的临时变量是具有const(常属性)的,所以此时不允许你对一个具有常属性的变量取别名,也就是不允许你进行权利的扩大。如果我们此时想要用引用的方式接收我们的返回值c,此时我们就需要在该变量的前面加一个const(使其具有常属性),这样就属于是权利的保持,此时该语法是被支持的。修改如下,发现果然可以。


4.png


此时我们来讲一下下图中的区别,并且这个位置涉及一个大家都很容易忽略的问题,就是隐式转换的问题,我们肯定都知道有强制类型转换这个东西,所以我们也应该要知道还有隐式转换这个东西,如下代码就是一个隐式转换,将我的int类型给隐式转换成double类型,此时在隐式转换的过程中会产生一个临时变量,并且该临时变量是具有常属性的,所以和上述一样,我们不允许进行权利的扩大,此时就会报错。


5.png


类型转换都是会产生一个临时变量的哦!如:double(i),此时是产生一个double类型的临时变量,然后将 i 的值拷贝到该doubel类型的临时变量中的意思。上述就是属于一个隐式转换,也是会按照该原理进行临时变量的拷贝的。

引用和指针的不同点:

1684831058471.png

总:引用是一个非常好用的东西,在以后使用C++语法编写代码的时候,我们会经常用到引用的概念。


C++中的内联函数


我相信我们以前在学习C语言的时候,我们都学习过宏的定义,当我们学习了宏的定义的时候,我们可以发现,我们在平时,使用宏的方法去写代码的场景是非常的少的,因为使用宏去写代码,是有缺陷的,例如:无法进行调式,使用起来较为复杂,使用不严谨等!导致我们现在对宏的概念和宏的使用都不是非常印象深刻,这个问题不单单是我们才有,所以此时在C++中,我们就有了一个新概念,叫内联函数,有这个概念就就可以使我的的宏的缺陷得到很好的解决。


复习一下宏的使用

宏的声明方式:#define name( parament-list) stuff 例如:#define ADD(x)((x)+(x))其中的parament是一个由逗号隔开的符号表(就是参数列表(里面有我需要的各种参数)),它们可以出现在stuff(内容)中,总的来说就是把这个宏的名字和参数放在了我的代码中,最后在预编译的过程中,它就会自己替换为我stuff(内容)中的相应的表达式,同时参数也会自己代换到我的表达式中。


此时我们可以看出,定义一个宏使用的括号是非常多的,也就是说明使用宏是需要比较严谨的,也就说明宏的使用不仅有缺陷,甚至是比较麻烦的,所以我们以下对宏的使用和函数的使用进行对比一下我们发现,使用宏相对于使用函数的最大的好处在于,调用我们的函数时,在汇编阶段,有大量的汇编代码需要执行,只有执行了这些代码,才可以完成我们的函数的调用和最后的函数返回值的返回,所以我们在编译过程中,调用函数这方面效率是比较的低下的,所以我们C语言的创始人针对于函数的这方面的缺陷就涉及出了宏的方式,使用宏就可以避免这一现象,原因是:使用宏定义的方式,我们的宏定义的stuff代码是直接对代码中的相应的宏定义表达式(name),进行替换,并且该替换过程是直接在我们代码编译过程中的预处理过程中的展开过程进行的,直接就把宏给展开了,根本不需要 进行各种汇编代码的调用,可以说执行速度是非常快的,所以这也就是宏出现的原因。但是我们还可以发现就算宏具有这个得天独厚的优势,但是我们平时都还是使用函数的方式进行编写代码,这是因为宏是有很多的缺点的,例如:


宏由于和类型无关,所以此时宏也是不严谨的
宏是没法调式的
宏可能会带来运算符优先级的问题,导致程序容易出错
每次使用宏的时候,一份宏定义的代码替换到我的程序中时,如果这个宏定义的代码非常长,就会是我的程序代码过长,导致问题


总:我们看了以上内容,我们就意识到了一个道理,在C语言中鱼和熊掌是不可兼得的

但是此时,有的顶级大佬就不服了,不就是提高一下我们函数的执行效率吗?这有什么难的,所以此时我们C++的创始人,就在C++中理所当然的提出了一个概念,也就是我们将要学习的内联函数的概念,有了内联函数这个东西,我们就可以将我们的鱼和我们的熊掌都给得到了。


内联函数的概念

我们以inline这个关键字来修饰我们的内联函数,只要我们在我们的函数前面加上inline,或者说只要是函数前面有inline这个关键字的函数(前提是该函数的代码长度符合我们编译器的要求),此时在编译过程预处理阶段就会像宏的方式一样,直接在有调用该内联函数的地方展开,没有函数调用栈帧的开销和各种汇编代码的执行,从而提高我们的运行效率。但我们的内联是一个以空间换时间的方法实现的,所以使用内联使我们的运行效率变高的同时,该可执行文件的所占的内存也会增加。


例如:此时一个程序中有1000个地方需要调用某一个内联函数,并且此时内联函数代码量是10行,符合编译器的要求,此时在编译过程的预处理中,展开内联函数,此时可以算出,代码量就足足增加了大概10000行,假如没有内联的话,在编译过程中,代码量仅仅就是那1000个需要调用该函数的代码。所以代码量可以说是相差巨大的。


内联函数的注意点

内联函数是不支持声明和定义分开使用的,举一个例子:此时我们有一个test1.cpp文件和一个test2.cpp文件和一个test.h文件,test1.cpp用来实现函数,test2.cpp用来调用函数,test.h用来声明函数,在test1.cpp中定义函数,我们并没有进行内联,而是在test.h声明的时候进行内联,此时在编译过程中,就会因为预处理过程头文件展开和内联函数展开,编译器不会生成相应的函数地址,直接对内联函数进行展开,导致test2.cpp中调用函数的地方找不到该函数的地址,就有链接错误。所以我们应该把函数的定义和声明写在一起,都写在我们的test.h文件中,这样我们的test.h在编译过程被展开时,调用函数直接就可以被展开使用了。


如何使用auto

具体如下图:typeid函数,获取某个变量的类型

6.png


所以此时根据上图,我们得出结论,auto就是自动匹配变量的类型,并且可以限定变量的类型

以后auto的使用场景

7.png


此时就涉及到我们以后会学的知识:范围for,起遍历数组的作用。for(auto e : arr)此句代码的意思就是,自动取数组中的数据赋值给给e变量,自动判断结束,并且因为此时e变量使用了auto类型,所以可以自动匹配任何类型的数组数据。但是范围for的使用是有一定的条件的,就是必须是数组是一个有范围的数组,不可以单单是一个首元素之类的,必须是范围数组才可以使用范围for。

注意:auto是不可以作为形参使用的,auto也不可以声明数组,auto可以同时声明多个变量,但是这些变量必须是同类型的变量。


并且此时通过下图,我们可以发现:


8.png


我们在不使用引用(&)的时候,只能改变从arr依次拿出来赋值给给e的值,并不可以直接改变数组中的数据,但是当使用了引用之后,就可以通直接更改数组中的数据了。

正式学习类和对象

初步认识面向过程和面向对象

我们的C++是一个半面向对象的一个语言,而我们的C语言就是一个地地道道面向过程的语言

面向过程:就是关注过程,例如:做一件事情,需要了解做好这件事情需要的每一个步骤,并且越细化每一个步骤越好。

面向对象:就是关注对象,例如:将一件事情拆分成不同的对象,靠对象之间交互完成。


什么是类

类的引入:我们都知道,在C语言中,我们的结构体只可以用来定义变量,但是此时在C++中,我们的结构体不仅可以用来定义变量,也可以用来定义函数,所以此时我们在结构体中定义函数就可以构成类。所以C++中的类的概念就是在结构体中定义函数和定义变量,然后进行使用,从而构成类。


此时凌晨1点,为了不熬夜,睡了,以下内容,在明天下篇博客中更新。


类的定义

类的访问权限定符及封装

类的作用域

类的实例化

类的对象大小的计算

类成员函数的this指针

相关文章
|
5天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
25 5
|
11天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
36 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
24 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
17 0
|
1月前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
31 0
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)