目录

一.引用

1.引用的概念:

2.引用的格式:

3.引用的特性

4.取别名原则:

难点:隐式类型转换的引用

5.引用的使用场景:

【1】做参数:

【2】做返回值

(1)int& Count()  的讲解

(2)传值、传引用效率比较

6.引用和指针的不同点:

二.内联函数

1.概念:

2.写法和作用:

3.如何通过汇编查看内联后的结果?

4.为什么要有内联函数:

5.特性

6.内联函替换后指令变多真的提高效率了吗?——确实提高了


一.引用

1.引用的概念:

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

2.引用的格式:

类型& 引用变量名(对象名) = 引用实体; 

举例如下:

注意:引用类型必须和引用实体同种类型

3.引用的特性

(1). 引用在定义时必须初始化
例如: int& d;  这样就是没有初始化是错的
(2). 一个变量可以有多个引用
   int a = 10;int& b = a;int& c = b;int* p = &b;    //p是指针

(3). 引用一旦引用一个实体,再不能引用其他实体

4.取别名原则:

对原引用变量,权限只能缩小,即 可读可写(普通类型) 可以改成 只读(const);不能放大:,即 只读 不能改成 可读可写的

例子1:权限的放大,不能把const给非const

例子2:权限的缩小  非const 既可以给非const,也可以给const:

例子3: 权限缩小和放大规则:适用于引用和指针间

例子4: 权限不适用于普通赋值:

难点:隐式类型转换的引用

整形e能否做双精度浮点型d的别名呢?

直接赋值是不行的,需要加上const才正确。即:const int& e = d; 正确

解释:隐式类型转换的普通赋值的情况,上面的例子:int f=d;从语法上看把8字节的d不能直接给4字节的f,因为浮点数和整形的存储形式就不一样,没办法直接截取,所以d需要先把整数部分给一个4字节的临时变量,再把临时变量给f

再看引用:int& e=d; 临时变量具有常性,所以这里把临时变量给引用e的时候,相当于是把自带const的临时变量赋值给非const的e,把只读的给可读可写的是放大了权限,所以错误;必须改成const int& e=d; 才正确!

5.引用的使用场景:

【1】做参数:

(1)传参:实参给形参传值和传地址都需要传一份值/地址的拷贝,引用传参可以减少拷贝,提高效率
void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}void Swap(double& x, double& y)
{double tmp = x;x = y;y = tmp;
}int main()
{int a = 0, b = 1;swap(a, b);double c = 1.1, d = 2.2;swap(c, d);return 0;
}

(2)作输出型参数:

leetcode上的题往往有输出型参数,在c++中就可以用引用代替更加方便

【2】做返回值

(1)int& Count()  的讲解

传值返回:会有一个拷贝
传引用返回:没有这个拷贝了,函数返回的直接就是返回变量的别名

int& Count()
{int n = 0;n++;return n;
}int main()
{int ret = Count();return 0;
}

——————————————————————————————————手动分割

首先我们来看普通的传值返回:普通的传值返回需要把返回值n给一个函数类型int的临时变量(函数类型就是返回值类型),再把临时变量给ret。

为什么设计一个临时变量,直接把n给ret不行吗?

答:不行,因为当函数Count里执行完各种代码后,返回n,等出了Count函数的作用域后n就会销毁,所以不能直接把n给ret,需要一个临时变量。

如何证明返回时存在临时变量呢?:如果你用int& ret 接收,写成int& ret = Count(); 发现无法运行,因为临时变量有常性,所以需要写成const int& ret = Count();  才能通过。

——————————————————————————————————手动分割

此时再看传引用返回:

当用引用接收引用返回时:这里ret和n的地址一样,也就意味着ret其实就是n的别名。但是因为n出作用域不会立即被覆盖,所以第一次通过ret可以打印是1,当打印第二次时,因为前面已经调用过一次打印函数,已 "销毁" 的Count函数栈帧在此时被打印函数覆盖,再打印ret就会是随机数了!

即:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

用static修饰n后:用static静态变量使n只初始化一次且改变其生命周期,把n放进了静态区,这样n就一直存在,就可以通过ret找到n了,再怎么打印ret都是1.

(2)传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是 当参数或者返回值类型非常大时,效率就更低。
总结:传值和指针在作为传参以及返回值类型上效率相差很大

6.引用和指针的不同点:

1. 引用在定义时必须初始化,指针没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体
3. 没有NULL引用,但有NULL指针
4. sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全
引用和指针在语法上是不一样的,但是实际上从反汇编的代码上我们能看到引用和指针的底层实现是一样的!

这就好比保时捷的卡宴和大众的途锐汽车,他们的三大件底盘,发动机,变速箱都是一样的,但是他们的品牌不一样,价格不同

二.内联函数

1.概念:

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。

 2.写法和作用:

在函数定义前加个 " inline ",就会在调用此函数的时候展开这个函数,通过汇编指令我们能清晰看到原本调用此函数的地方应是call Add,加上inline后他就会直接把Add函数的指令放到ret = Add(1, 2); 的位置了,即:如果在函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。

3.如何通过汇编查看内联后的结果?

查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进
行优化,以下给出vs2013的设置方式)

4.为什么要有内联函数:

代码少的函数频繁被调用,每次调用建立栈帧,汇编看建立栈帧里面一堆操作很浪费时间,所以干脆在调用的地方展开,虽然指令多了但是效率高了,空间换时间的做法,在c语言中是通过宏函数的方式展开的,比如#define ADD(x, y) ((x)+(y)) ( 当Add(1 & 2 , 3 | 4); 时有优先级问题,+比&和|优先级高,所以x和y也要加括号),但是大佬们发现宏函数易写错难懂,所以在c++中创造了更易懂的展开做法:内联函数——总结一句:频繁调用小函数,建议定义成inline

顺便再讨论一下宏,宏的缺点就是内联函数的优点

(1)宏

优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不支持调试。(因为预编译阶段进行了替换)
2.宏函数语法复杂,容易出错。
3.没有类型安全的检查。

5.特性

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长(一般函数的代码是10行左右就很长了,具体取决于编译器)或者有循环/递归的函数不适宜 使用作为内联函数。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。因为inline被展开,符号表里就没有函数地址了,链接就会找不到。
错误示例:声明定义分离

// F.h#include <iostream>
using namespace std;
inline void f(int i);// F.cpp#include "F.h"
void f(int i)
{cout << i << endl;
}// main.cpp#include "F.h"
int main()
{f(10);return 0;
}

F.h 在F.cpp中被展开,因为声明是inline,符号表不会生成函数地址,当main.cpp 中调用函数f时,call(函数名) 这个指令去符号表找函数名和地址映射关系时,找不到函数地址,则无法展开。

正确应该这么写:

最佳写法:(1)

F.h 在main.cpp中被展开,因为是内联,所以会在调用处展开,此时call指令也能找到函数地址。

// F.cpp#include "F.h"
inline void f(int i)
{cout << i << endl;
}// main.cpp#include "F.h"
int main()
{f(10);return 0;
}

(2)声明和定义写都写在.h中也可以,但是没必要

// F.cpp#include "F.h"
inline void f(int i);void f(int i)
{cout << i << endl;
}// main.cpp#include "F.h"
int main()
{f(10);return 0;
}

6.内联函替换后指令变多真的提高效率了吗?——确实提高了

举例:比如把3行的swap函数调用1000次,如果用内联展开就是3000行指令,如果不展开就是1000+3=1003 行指令(1000次调用就要1000次call指令,还有3行swap函数的指令),那把小函数内联展开指令多了很多,真的比建立栈帧快吗?

答:不是说不展开指令就变少了,每次call过去,swap函数里面的3行指令还是会执行的,所以实际上一共是执行了 1000次call指令+1000*3次调用swap指令=4000次指令,所以说还是inline的3000行效率更快。

7.为什么 频繁调用小函数,建议定义成inline ?

代码很长的时候不适宜用内联函数,比如10行swap函数调用1000次,内联后指令会执行10*1000=1w次,不内联就是1000次call指令+1000*10次调用swap指令=11000次指令,虽然少走1000次,但是多消耗了很多展开的空间 不值得,所以不建议长函数用内联

C++入门 “引用”,“内联函数” 详解相关推荐

  1. C++ 内联函数详解(搞清内联的本质及用法)

    目录 一.什么是内联函数 1.直观上定义: 2.更深入的思考: 二.为什么使用内联函数 1.为什么要代替部分宏定义 2.普通函数频繁调用的过程消耗栈空间 3.更深入的思考 三.内联函数和编译过程的相爱 ...

  2. inline 内联函数详解 内联函数与宏定义的区别

    一.在C&C++中 一.inline 关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义. 表达式形式的宏定义一例: #define ExpressionName ...

  3. C++ inline内联函数详解

    函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码.CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码:被调函数执行完毕后再返回到主调 ...

  4. c++:内联函数详解和普通函数的区别

    文章目录 前言 Ⅰ.常规函数 Ⅱ.内联函数 1.语法 2.注意 Ⅲ.选择地使用内联 思考? 前言 内联函数是c++为了提高程序的运行速度做的改进,它与普通函数区别在于: 编译器如何将它们组合到程序中. ...

  5. python3 内置函数详解

    内置函数详解 abs(x) 返回数字的绝对值,参数可以是整数或浮点数,如果参数是复数,则返回其大小. # 如果参数是复数,则返回其大小.>>> abs(-25) 25>> ...

  6. python中反三角函数用法_Python入门之三角函数atan2()函数详解

    python 的 Python入门之三角函数atan2()函数详解 描述 atan2() 返回给定的 X 及 Y 坐标值的反正切值. 语法 以下是 atan2() 方法的语法: import math ...

  7. Python内置函数详解——总结篇

      引 言 国庆期间下定决心打算学习Python,于是下载安装了开发环境.然后问题就来了,怎么开始呢?纠结一番,还是从官方帮助文档开始吧.可是全是英文啊,英语渣怎么破?那就边翻译边看边实践着做吧(顺便 ...

  8. C++ C++基础语法入门总结(二)引用-内联函数-C++11新特性

    C++基础语法入门总结 C++引用 再谈引用和指针 C++内联函数 附加C++11新特性 auto关键字 基于范围的for循环 指针空值nullptr C++引用 引用:就是某一变量(目标)的一个别名 ...

  9. python内置函数教程_Python内置函数详解

    此文参考python文档,然后结合自己的理解,写下来,一方面方便自己,让自己好好学习,顺便回忆回忆:另一方面,让喜欢的盆友也参考一下. 经查询,3.6版本总共有68个内置函数,主要分类如下: 数学运算 ...

最新文章

  1. 想转行学软件测试要注意哪些问题
  2. 世界上本没有架构,建设的需求多了便有了架构
  3. 棋盘格氧化铝标定板漫反射不反光12*9方格视觉光学校正板
  4. pygame学习和python巩固——字体显示
  5. php查询变量类型,php判断变量类型常用方法
  6. django-后台管理
  7. Django环境搭建及学前准备
  8. c语言中printf输出格式
  9. linux ssd硬盘做缓存,linux系统中ssd当块设备缓存
  10. php为什么要使用静态方法,为什么PHP在对象上下文中使用静态方法?
  11. 基于centos的FasfDFS安装配置
  12. 将15位的身份证号码升级到18位的关键是校验码
  13. 企业微信老是服务器异常,添加好友提示”操作异常”,企业微信需要养号吗?...
  14. 5.Django路由path和re_path详解
  15. Android发送通知
  16. 百度后端二面有哪些内容,万字总结(一)
  17. 哈希函数(散列函数)详解
  18. ff15测试软件翻译,最终幻想15数据详细分析 FF15详细的数值参数测试
  19. 暗影精灵4清灰、加硅脂
  20. 在Mac中使用Word添加带圈的脚注

热门文章

  1. JSP第四篇【EL表达式介绍、获取各类数据、11个内置对象、执行运算、回显数据、自定义函数、fn方法库】(修订版)...
  2. android smallestWidth 限定符屏幕适配方案dimens.xml
  3. 一个优秀IT专家的成长历程---献给所有的颓废或..
  4. 为什么明明能上网,Windows却显示“无Internet”?
  5. zcu102出现运行可执行程序时出现的错误
  6. 统计人数-C语言实现
  7. Unit Test and Integration Test
  8. 联邦学习 Federated Learning 相关资料整理
  9. 【Linux】电子词典
  10. 知乎上的48条神回复!看完人生透彻了许多!