> 文档中心 > 深剖C++内联函数和引用机制

深剖C++内联函数和引用机制

目录

    • 传统艺能😎
    • 深剖C++引用🤔
    • 优势分析🤔
    • 使用场景🤔
    • 引用和指针🤔
    • 内联函数🤔
    • 特性🤔

传统艺能😎

小编是双非本科大一菜鸟不赘述,欢迎大佬指点江山(QQ:1319365055)
此前博客点我!点我!请搜索博主 【知晓天空之蓝】

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,背后烟火,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我

🎉🎉🎉倾力打造转码社区微信公众号🎉🎉🎉
在这里插入图片描述


在这里插入图片描述

深剖C++引用🤔

我们之前浅提了一下C++里面引用的概念,其实就是给变量起绰号,这个绰号的地址仍然不变。

比如这串代码:

int main(){int a = 1;int& b = a;int* p = &b;int& c = b;//甚至可以对引用进行引用return 0;}

我们打开监视窗口,一看便知确实如此

在这里插入图片描述
注意引用也有一些细节必须要注意:

  1. 首先在定义时就必须初始化 ,比如类似 int& a; 这样的是不允许被定义的
  2. 一个变量可以有多个引用,上面已经演示了不赘述
  3. 引用一旦引用了一个实体,就不能引用其他实体,通俗的讲就是一个变量可以多个别名但一个别名只能对应一个变量。

再往深了走,引用的原则遵循对原来引用的变量,权限只能相同或缩小,不能放大。什么意思呢我们以下面两组代码为例:

const int x= 10;int& y = x;//放大const int& y = x;//不变int a = 10;const int& b = a;//缩小

第一组 x 为 const 类型,只能读不能写,而别名 y 为 int 类型为可写入的类型,那么这个别名 y 的权限就被放大了,属于越俎代庖,是不被允许的。而后面的 const 类型 y 对应 const 类型 x 没有改变,就保持了权限的不变;第二组反过来 a 原本为可写变量,别名 b 属于不可写的 const 类型,则权限缩小是允许的。

在 C++ 程序里,我们的传值在学习模板后我们会明白他的局限,但传址在遇到下面情况时也会存在限制,因为权限的只读性,我们加上 const 就可以解决辣

void test(const int& a)int main(){int a = 1;int& b = a;const int& c = 2;test(a);//可调test(1);//不可调test(c);//不可调}

优势分析🤔

所以我们经常看到函数的参数采用 const 进行维护,其原因就是 const 的价值在于可以减少传参的拷贝,在 test 函数里 const 修饰的是引用,引用的是临时变量,临时变量具有常性,const 修饰下任何类型都能传,哪怕类型转换也能传

所以说 C++ 的引用机制是很舒服的,相比我们的C语言,他在涉及到指针时就格外优秀了,之前在二叉树题目中甚至会出现二级指针,现在只需要对一级指针引用即可。

插个话,最早的时候你可能疑惑过 scanf 函数使用时为什么会取对象地址而 printf 函数不需要,因为 scanf 是去缓冲区提取变量后赋值给他,这个变量实际上是传给形参的,那我们只能传他的地址,这就是指针的烦恼啊~所以大家能明白引用设计的用心良苦吗?
在这里插入图片描述

引用的使用场景可以做参数也可以做返回值,先提醒一下,引用做返回值的水很深,稍不注意就会陷进去

假如我写了一个传值返回的函数:

int num(){int n =0;n++;return n;}int main(){int a = num();return 0;}

函数每执行完一次就会 return 一次,调用完就会给 a 赋值一次,但是 n 不可以拿来赋值,因为出了作用域临时变量就直接销毁了,我们的原生语法里面编译器不敢做那么聪明去判断 n 可不可以被返回,他采用统一的做法就是让我们写代码的自己决定,只要写的是传值函数,就会生成一个临时变量,先把 n 拷给他再让他拷给 a。

这个临时变量可以由两种方式充当:如果小就用寄存器(栈帧销毁不影响寄存器),如果大(像结构体这种)他会在上一层提前开辟好栈帧,也就是 main 函数栈帧
在这里插入图片描述

我们再来玩玩引用,就用刚刚的函数,我写成下面这样的话又该怎么理解呢?

int& num(){int n =0;n++;return n;}

你可以这么理解,他和主函数之间传递也会生成一个临时变量,只是这个临时变量他是一个引用,他会变成我们返回值 n 的别名,==那是不是就意味着 n 直接赋值给了 a?正确的!==他们的地址是一样哒,所以还是那句话:引用yyds,听懂掌声。
在这里插入图片描述

使用场景🤔

但是!这个代码他有没有问题啊,答案是有的

在这里插入图片描述

现在 a 就是我 n 的别名,我们临时变量 n 返回后销毁,a 怎么还会是他的别名呢? 先想想空间销毁的意义是啥,他和栈帧一起被销毁,这个 “销毁” 其实就像 hotel 退房一样,退是退了但房间还在的,我有意无意还是可以访问的,所以这个代码是个由引用间接搞出来的野指针。函数返回时出了作用域,如果返回对象还没还给系统,则可以使用引用返回,否则使用传引用返回。

其实静态变量 static 修饰可以解决这个问题,每次调用完毕后栈帧销毁了但空间使用权还是该函数的,别人调用函数也覆盖不了,但我们之所以稍有看到稍有用 static 是因为在实际运用中会存在线程安全问题。

传引用返回不再像传值返回一样要拷贝,函数返回的直接就是变量的别名,但传引用返回也不是随随便便就可以用的,是会有问题的,C++ 控制这个过程他是这么想的:有些地方不能使用传引用返回比如刚刚那种情景,他设计了一个统一的临时变量依赖机制,到底用传值返回还是传引用返回是我们自己判断,可以就加上引用,引用就不用拷贝,效率就高。

引用和指针🤔

全是细节,请务必看一遍(敲黑板)
在这里插入图片描述

  1. 引用概念上定义一个变量的别名,指针存储一个变量的地址
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. NULL没有引用但有NULL指针
  5. 在 sizeof 中含义不同,引用结果为引用类型的大小,但指针始终在地址空间所占空间字节(32 位平台占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 多级指针有但没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来更安全:引用只是别名不开空间,而指针存储了变量的地址,他是需要开辟额外空间的

语法角度上讲他俩风格各异,但从底层原理出发他们的实现方式都是一样的,就好像乐事和子弟,包装产地口感不一样,但他们都是薯片
在这里插入图片描述
这里讲一点扩展知识:

内联函数🤔

C++ 引入了一个 inline ,以 inline 修饰的函数叫做内联函数,编译时 C++ 编译器会在调用内联函数的地方展开inline ,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率

比如以下场景:

inline int& add(int a, int b){int c = a + b;return c;}int main(){add(10, 20);add(10, 20);add(10, 20);    …………}

像这样频繁调用这种小规模函数,是很没有效率的,其实我们之前说过C语言里面他是通过宏解决的,这里 add 函数写成宏就是

#define add(a,b) ((a)+(b))

显而易见宏的写法比较复杂缠绕,这就是为什么C++会有内联机制,就是为了解决宏函数的晦涩且容易因为优先级写错的缺点,C++的改进是非常直观的。而且不要忘了宏是不支持调试,不支持类型安全的检查等缺点,这都是 C++ 语法填上的坑。

inline 之所以支持调试是因为 debug 环境下 inline 补齐作用,默认不打开优化;所以他就是集大成者的高性价比产物。
在这里插入图片描述

特性🤔

  1. inline 实质是一种以空间换时间的做法,所以为什么我们这里说是小规模函数,长代码和递归函数就不适合作为内联函数了,因为 inline 对于编译器只是一个建议,在代码很长或者带有递归时,编译器会自己做出优化导致内联不起作用。

  2. inline 不建议声明和定义分离,分离会导致链接错误,因为被展开了就没有函数地址了,链接会找不到!

  3. 在内联函数内不允许使用循环语句和开关语句;

  4. 一个较为合理的经验准则是, 不要内联超过 10 行的函数,谨慎对待内联函数
    在这里插入图片描述

今天就到这里吧,润了家人们。

美国云服务器