【C++】引用和内联函数
阿巴阿巴,最近搭建好了腾讯云的Linux环境,所以本篇C++的博客就尝试在Linux环境下来测试代码吧!
阿巴阿巴,最近搭建好了腾讯云的Linux环境,所以本篇C++的博客就尝试在Linux环境下来测试代码吧!
今天学习了C++的引用和内联函数,一起来瞅瞅它们都是些啥……
感谢你关注慕雪,欢迎来我的寒舍坐坐❄慕雪的寒舍
[TOC]
前言
众所周周知,C语言之中,有一个叫指针的家伙,它的使用方式如下
1 | int main() |
这时候我们就可以通过*p
对指针解引用访问变量a
所以C++之中也有一个类似的东西,叫做引用
,不过它和指针完全不同哦
1.引用
1.1基本形式
引用的基本方式如下
1 | int a=10; |
此时的b和c都是a的别名,注意是别名!打印的时候,三个变量的结果都是10;
可以用两个不同的变量名引用同一个变量,而且引用了之后不可以更改对象
- 一个变量可以有多个引用
- 指针可以更改指向的对象,引用不可以
- 引用必须在定义的时候就初始化,不可以
int& b;
比如你叫李华,有人叫你“小李”,还有人叫你“英语作文人”,这两个外号都是你的别名。
指针并不是别名,指针是通过地址访问某个变量。而引用是给a变量起另外的两个名字,实际上b和c都可以当作a来使用
编译运行代码,让编译器打印出这三者的地址,可以看到它们的地址是一样的,因为它们本来就是同一个变量的不同名字。
指针变量的地址和指针变量所指向对象的地址是不同的
引用的类型必须和引用实体的类型相同,不能用int&
引用double类型
1.2引用的权限问题
①const常量
引用可以引用常量,但是必须加const
修饰
基本的思路就是“权限可以缩小,但不可以放大”。
- 在上面的代码中,a是一个可以修改的变量,但是
const int&d=a;
中的d是不能修改,只可读取a的内容。 - e是不可修改的常量,所以我们不能用
int&
来放大权限
②int和double相互引用
在1.1
中有提到,我们不能用int&
来引用double
类型的变量,编译器会报错
不过我们可以用const int&
类型来引用double,此时引用就不是简单的一个别名了
先来了解一下把double复制给int类型,这时候会产生“隐式类型转换”,h保存的是z的整数部分
在这个过程中,编译器会产生一个临时变量存放z的整数部分,然后赋值给h
- 临时变量具有“常性”,可读不可改
而当我们用const int&
类型来引用double时,实际上引用的是编译器产生的临时变量,它是一个常量,所以我们需要用const int&
来引用
1 | const int& i=z;//这里的i是临时变量的别名 |
一个非常直观的验证方法,就是打印一下,瞅瞅它们的地址是否相同。可以看到,i的值和h是相同的,因为它引用的就是那个存放了整数部分的临时变量,这个临时变量的地址和z不同
1.3引用的使用场景
①函数传参
众所周知,在C语言中,如果我们想在函数中修改某一个main传过来的参数,就必须进行传址调用
。而在C++中,我们可以通过引用来操作
可以看到,我们通过引用实现了在函数中修改a的值
更加充分的体现便是Swap函数,在C语言中必须两个都传地址来调用
在C++中,配合函数重载,我们可以很方便的写出多个交换函数
直接测试一下,交换成功!
②函数返回值
1 | int& Count(){ |
当我们把n作为int&
类型来返回时,ret此时是对n的引用。但是函数中的变量n在出了函数后销毁了,所以在main函数中打印ret的时候,可能会打印出随机值(这个要看什么时候n的内容会被编译器覆写)
而当我们带上static
后,多次打印n的值都不会出现问题,因为此时n的空间并没有被销毁
- 如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回
- 如果已经还给系统了,则必须使用传值返回,避免出现访问随机值
下面用一个简单的Add函数来演示一下上面提到的两种情况
如果去掉static
修饰,编译器会报警告,而且打印的值会在第二次cout调用的时候被覆写
当Add函数的c变量加了static
修饰后,打印的值都是稳定的,不会被覆写(因为c的空间没有被销毁)
③优化函数调用时间
在函数返回传参的时候,其实是先把返回值存放到寄存器中,而不是直接返回给main函数的变量
- 当返回值很小(指占用空间)的时候,会用寄存器存放它的值
- 当返回值很大的时候,部分编译器会先在main函数中预先开辟栈帧用来存放返回值
而使用引用作为返回值的时候,就不需要用寄存器来接收临时变量,这时候就优化了函数返回的时间!
可以看到,用引用返回的时间消耗很小!
再来试试把引用作为参数传参的消耗,和传址、传值进行对比,代码和上面的类似,稍微修改一下测试函数就行了
可以看到,传地址和引用作为参数的传参消耗都是很小的。因为传值的时候需要拷贝数据!
1.4引用和指针的汇编代码
用下面的代码来查看引用和指针的汇编区别
1 |
|
你可以看到,指针和引用的汇编代码是相同的。因为C++的引用,本质上是用指针实现的!
用objdump -S
语句,查看Linux环境下的汇编👇
1.5引用和指针的区别
- 引用是别名;指针是指向地址
- 引用必须在定义的时候初始化;指针无要求
- 引用的sizeof大小和引用对象相同;指针无论指向的谁,大小都是4/8
- 引用不能为NULL;指针可以为NULL
- 引用++即对象数值+1;指针++是指向的地址向后偏移
- 引用无多级;指针存在二级、三级……
- 引用比指针使用起来更加安全(不会出现野指针)
- 引用是编译器处理的;指针需要手动解引用
- ……
1.6 数组指针引用
笔试的时候遇到了这个纠错题,我感觉这里是错的,但还是没选出来;
1 | int arr[20]; |
2.内联函数
2.1基本形式
在函数名前用inline
修饰的函数是内联函数,编译器在处理此类函数的时候,会将函数在调用它的地方打开。此时内联函数就没有函数压栈的开销,提高了程序运行的效率
这部分和C语言学习过的
#define
类似,但define是直接替换,内联函数不是
1 |
|
2.2查看预处理文件
使用下面的Linux语句可以把源文件生成为预处理后的文件
这部分可以看看我之前用树莓派操作的博客哦!【传送门】
1 | g++ -E test.cpp -o test.i |
可以看到define被替换了,但是内联函数并没有
2.3查看汇编代码
①Linux环境
这时候我们先编译这个文件
1 | g++ test.cpp |
然后使用下面的语句查看汇编代码
1 | objdump -S a.out |
然后你就发现,这不还是有call函数调用嘛?这哪里没有调用呢?
实际上,我们在编译的时候需要调整编译器的优化操作👉【参考博客】
1 | g++ -O2 test.cpp |
这时候的汇编代码就没有call了
②VS2019
要想调整VS2019的优化等价,需要在项目属性中C/C++ -常规
中修改调试信息格式
为“程序数据库”
然后在优化-内联函数扩展
修改成只适用于inline(/Ob1)
然后调试,右键转到反汇编,可以看到,no call🕵️♂️
2.4内联函数的特性
define
没有传参检查,且不能debug+可读性不高,内联函数解决了这一缺点
- 内联函数是用空间换时间的做法,省去函数调用的开销
- 函数代码很长的时候不适合用内联函数(define同理)
- 在代码行数很长的时候,编译器会自己判断是否使用inline。如果函数体内有循环/递归等,编译器优化的时候会取消内联
- inline不可以声明和定义分离,会导致链接错误
对最后一点展开介绍一下,当我们把内联函数的声明和定义放在不同的源文件和头文件中,编译器会报错找不到函数
这是因为内联函数在调用的时候已经展开了,对应的函数地址也没了,所以无法正常链接
结语
引用和内联函数的博客到这就结束啦,如果对你有帮助,还请点个赞再走哦!
笔记难免有错,还请大佬们无情指出!