| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 9.15c++基础 -> 正文阅读 |
|
[C++知识库]9.15c++基础 |
目录 引用:接着我们上节课讲的一些引用的基础知识: 1:引用就是别名,所以当变量a的引用为ra时,ra不仅值与a相同,连地址也与a相同。 引用需要注意的问题: 1:引用必须进行初始化,不能凭空引用: 2:引用一旦引用一个实体,就不能再引用另外的实体。
如图所示,我们进行调试: 当main函数结束的时候,假如b和ra的地址是相同的,那么引用就可以引用多个实体,否则引用只能引用一个实体。 ?如图所示:我们可以发现,ra和b的值是相同的,ra和b的地址是不同的,这证明了引用多个实体是不行的。 引用的实际的用处:1:作为输出型函数的参数: 我们先举一个输出型参数的例子:
输出型参数表示我们要对参数本身进行修改。 输入型参数:
输入型参数就是我们平常使用的排序函数,输入要排序的对象和对应数组的元素个数,我们在函数内部实现调用。 2:引用来做返回值。 在这里,我们首先讲一下内存的划分: 内存分为一下四部分: 1:常量区(代码段):存放普通常量。 2:静态区:存放的static修饰的静态变量。 3:堆区:动态内存申请的空间 4:栈帧:函数调用所开辟的空间
要理解这个引用返回,我们可以先理解一下传值返回:
要理解这个传值返回,我们把最简单的传值返回讲解一下:
?这个最简单的传值返回对应的内存图像是这样的: 具体的步骤是这样的: 我们先调用Count函数,调用函数,形成Count函数对应的栈帧,局部变量n就在栈帧空间内部,n++,n变成1,我们把n返回,这里的返回并不是直接把n返回给ret的,这里的返回是通过我们创建一个临时变量,用这个临时变量来接收n,但接收的只是n的值,然后Count函数调用结束,对应的栈帧销毁,对应的空间返还给操作系统,我们再把临时变量的值返回给Count值。 注意:当这个临时变量比较小时,被存放在寄存器中。 当这个临时变量比较大时,被存放在上一层函数的栈帧中,也就是main函数的栈帧中。 要证明以上的想法,我们需要判断ret和n的地址是否相同: 对应的ret和n的地址不同。 接下来,我们来分析更难的传值调用:
?这段代码和上面的唯一的不同就是变量n用了static修饰,static修饰的话就是静态变量,静态变量存放在静态区,所以不会随着栈帧的销毁而释放。当我们调用函数完毕后,返回n,这时候,我们还是创建一个临时变量来接收n,再由临时变量返回给ret。 对应的ret还是1. 但是当我们使用引用返回时,我们需要小心: 我们举出一个错误案例:
我们首先对代码分析: 调用函数Count,创建变量n,n++后等于1,返回1,这时候因为我们的返回值类型用的引用,所以我们返回的就是n的别名,但是函数调用完毕之后,属于n的那部分空间也已经被操作系统回收,这时候我们对属于n的空间进行访问实际上就是一种越界访问了。 我们进行测试:
进行编译: ?打印的结果仍为1,原因是什么呢? 答:我们画图进行解释:当我们调用Count函数时,我们对应的内存图像是这样的。 ?但是当我们函数调用完毕后,我们对应的空间被释放: ?虽然我们的空间不属于我们了,但是对应的n值仍旧存在,没有被覆盖,我们用对应的同样的引用ra来接收a,a其实是n的引用,所以ra就是n的引用。 我们再打印几次:
?为什么剩下的两次打印都是随机值? 答:因为我们的ra是n的引用,当我们刚刚调用完毕Count函数时,我们对应的图象是这样: ?当我们第一次调用打印函数时,对应的图像是这样: ?我们对应的n值已经被覆盖,但是注意:当我们调用打印函数的时候传参的时候,n值仍是存在的,n值为1,所以我们打印的n为1. 我们第一次打印函数调用完毕的图象是: 我们第二次调用打印函数:我们的参数n对应的空间现在是打印函数内部的随机值,所以我们打印的结果是随机值 第三次调用也是同样。 正确的写法:
我们进行检测: ?正确的原因如下: 因为我们是静态变量,静态变量n并不会随着函数的调用结束而被释放,并且我们返回的是n的引用,所以我们并不需要把返回值拷贝到临时变量中,进而能够节省空间。 结论:出了函数作用域,返回的变量不存在了,不能用引用返回,因为引用返回的结果是未定义的。 总结:传引用返回的作用 1:减少拷贝,提高效率 2:修改返回值。 引用做参数的作用: 我们写两个交换函数进行分析:
其中,对于第一个函数,我们在调用函数时,需要拷贝实参作为形参,而对于第二个函数,我们只是引用了实参,所以第二个函数的效率更高。 对于第一个函数,我们的交换并不能成功,原因是形参的改变并不影响实参。 而对于第二个函数,我们的交换可以成功,原因是我们的形参是实参的引用,所以形参修改,实参也跟着修改了。 总结: 引用做参数的作用: 1:减少拷贝,提高效率 2:对于输出型参数,当形参改变的时候,实参也跟着改变。 这两个函数构不构成重载: 构不构成重载是根据编译器对于两个函数参数的理解,当编译器把int &识别成int时,这两个函数就不构成重载,当编译器把int&的类型识别为引用类型时,这两个函数就构成重载。 我们进行运行检测:
代码并没有报错,证明函数构成了重载。 但是这里其实构不构成重载无关紧要,因为无论构不构成重载,我们都无法调用这两个函数,因为Swap(2,3)同时适用于这两个函数,造成歧义性,所以无法调用。 const引用:
这种写法是正确的。
但是这种写法却不对。 错误的原因是什么? 因为b是只能读而不能写的参数,而rb是可读可写的参数,所以对应的b的权限增大了,所以导致错误。 我们写一个权限缩小的例子:
?b是可读可写的,而ra是只读不可写的,所以由b到ra是权限的缩小,我们进行编译: 我们再写一个权限不变的例子。
这两个都是只读而不可写的参数,所以由a到ra,权限是不变的。 所以,我们进行总结: 在指针或引用中,权限可以缩小,但是不可以扩大。?
假如我们要调用这个引用的函数用来减少拷贝来提高效率可以吗? 答:不可以,原因是当我们调用这个函数的时候,我们的传参是受限的。
如图所示,我们调用Func(a)是可以的,但是我们无法调用Func(b),因为b是只读的参数,我们传参的时候,我们把只读的参数传给可读可写的参数。 所以我们正确的写法是这样:
但是,注意这种情况
b是只读的,a是可读可写的,把b赋给a是不是导致权限的放大? 并不是,因为这里根本不涉及引用和指针,这里这是简单的赋值操作,传递的是参数值,所以没有任何影响。 临时变量如何处理:
这个是普通的缺省参数。 当我们加入一个引用符号时: 这里错误的原因我们可以这样理解: 10是一个常量,我们要对一个常量引用的话,我们要保证自身要是一个常量,所以我们可以用const修饰。
? ? 我们再举一个例子: ?为什么这里会发生错误? 许多人的想法是类型不同,但是为什么下面这种写法可以呢? ? 这里发生了隐式类型转换,转换的方式:首先,创建一个临时变量,临时变量接收了(d的数值被转换成int类型的形式),然后再把临时变量赋给变量i。(注意:d并没有改变,只是d的数值在传给临时变量的时候被转换了) 我们可以进行实验: ?如图所示,我们可以发现d被赋值给i后,其本身并没有发生改变。 这种写法其实就等价于强制类型转换。
无论是强制类型转换,还是隐式类型转换,或者是整型提升,亦或者是传值返回,都有临时变量的参与。 那这个时候,对应的i值是多少呢? ? 临时变量的一大特点就是:临时变量具有常性。 这个时候,我们就能解释为什么int&ri=d为什么报错了: d首先要进行转换,转换的时候,媒介是临时变量,我们要把临时变量赋给ri,而临时变量具有常性,我们要对具有常性的整型进行引用,那我们本身也要是常数才行。
所以当我们这样写的时候,就不会报错。 我们再写一串代码加强理解: 为什么这里会有错误呢? 答:因为这里是传值返回,传值返回的媒介就是临时变量,临时变量具有常性,所以我们无法用普通的引用来进行接收。 我们需要加上const。 ? 引用的实质:在语法角度,我们的引用相当于给变量取一个别名,所以不会额外开辟空间,但是在底层实现的话,引用需要额外开辟空间。 我们拿引用和指针解引用进行对比:
?第一个是通过引用把a的值修改为15. 第二个是通过指针解引用把pb的值修改为25. 我们转到汇编代码看一下: 我们虽然看不懂,但是可以发现mov和lea都出现多次,我们查一下mov和lea对应的意思: ?mov: ?这个很容易理解,既然我们要修改a和b的值,我们就肯定要用数据传送指令。 lea 这里就能看出问题了,在我们的印象中,指针相关的操作是需要传递地址的,引用并不需要。 这里的lea就证明了在底层实现的逻辑上来看:引用也是需要额外开辟空间的。 引用的总结:? c++11小语法auto
auto的意思就是根据a的类型推导b的类型,所以b的类型也是int了。 auto在for循环中的使用: 我们现在写for循环要这么写:
是不是非常复杂,我们使用auto可以这样写:
这串代码的意思是:我们首先调用for循环,把数组a中的内容赋给e,然后自动判断结束,自动迭代,打印出所有的数组值。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 9:35:10- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |