c++阶梯之引用与内联函数

news2025/1/18 21:07:15

1. 引用

1.1 引用概念

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

语法

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

示例 

很显然,在下面这个例子中,a与b共用同一地址。 

这里需要注意:引用类型必须和引用实体是同种类型的

1.2 引用特性

1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

示例 

引用定义时必须初始化。 

一个变量可以多次引用 

 引用一旦引用一个实体,再不能引用其他实体

1.3 常引用

1.权限只能缩小,不能放大。

2.类型转换时会产生临时变量,临时变量具有常性 

 关于这两个概念,我们结合代码来看,详见代码注释

所谓权限,无非就是对对象的操作权限,对常量我们的操作权限为只读,对变量则可读可写。 

那么类型转换时会产生临时变量是什么意思呢?

我们来看这一段代码:

int main()
{
	double a = 13.14;
	int b = a;//double转换为int

	int i = 97;
	char ch = 'a';
	if (i == ch) //char转换为int
	{
		cout << "相等" << endl;
	}
	cout << b << endl;
	return 0;
}

 

这一段代码有两个类型转换的例子,两者相类似,都是在类型转换时产生一个临时变量。

double a = 13.14;  int b = a;中,产生一个值为13的临时变量赋值给b。

i与ch的比较中,产生一个值为该字符ascll对应值的整型临时变量。

这些临时变量有一个共性,那就是具有常性。 

1.4  关于引用的使用场景

1.做参数

2.做返回值

1.做参数

我们以下述代码为例,曾经的交换数据,我们需要借助指针来实现,而现在引用就可以,而且还比指针简单很多。

引用版:
void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	cout << "a:" << a << "\t" << "b:" << b << endl;
	Swap(a, b);
	cout << "a:" << a << "\t" << "b:" << b << endl;
	return 0;
}
指针版: 

 

明显引用更为方便简洁。

2.做返回值

下面这段代码,大家觉得有没有问题呢?

int& add(int x, int y)
{
	int c = x + y;
	return c;
}
int main()
{
	int a = 10;
	int b = 5;
	int c = 9;
	int d = 10;
	int ret = add(a, b);
	cout << "ret:\t" << ret << endl;
    cout << "ret:\t" << ret << endl;
	return 0;
}

我们看看运行结果: 

很好,貌似没有问题,但我们发现,编译器给我们发出了一个小小的警告,如果是以前我们可能会嗤之以鼻不管不顾,但现在的我们不一样了,一个小小的警告也要抹杀在牢笼里。

 

完蛋,当我们准备解决警告时,却发现他是一个大问题。

这个问题我们之前遇到过,费了很大的力气才解决掉,现在他又出来了。

这是为什么呢?

很简单,一个函数的生命周期很短,函数调用,产生函数栈帧;函数调用结束,函数栈帧销毁。 那函数内产生局部变量或临时变量的生命周期自然随之结束,这一段空间就还给了操作系统,而我们直接对局部变量起别名并返回,然后在其他地方一直使用,不就相当于一直在退了房的宾馆里转悠嘛,违法行为不可取。

大家不爱看字,那就画个图给大家看。 

 如果你的东西还放在里面,宾馆换了个把锁,你进不去了,东西就丢了。

所以如果是全局变量,staic修饰过的变量,或者动态开辟的内存就不受限制。

1.5 传值,传引用效率比较

我们使用如下代码进行比较两种效率。 ( 关于clock () 的用法大家可以在网上查阅 )

struct A
{
	int arr[10000];
};

void TestTranVal(A a)//传值
{
}

void TestTranQuote(A& a)//传引用
{
}
int main()
{
	A a;

	size_t begin1 = clock();
	for(int i=0;i<10000;i++)
	TestTranVal(a);
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
	TestTranQuote(a);
	size_t end2 = clock();

	cout << "TestTranVal--time:\t" << end1 - begin1 << endl;
	cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;

	return 0;
}

 

至于为什么会这样?且看下面这段文字!

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

1.6值和引用的作为返回值类型的性能比较
 

这里只需要将上面的代码略微改变。

struct A
{
	int arr[10000];
};

A a;//全局变量

A TestTranVal()
{
	return a;
}

A& TestTranQuote()
{
	return a;
}
int main()
{

	size_t begin1 = clock();
	for (int i = 0; i < 10000; i++)
		TestTranVal();
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
		TestTranQuote();
	size_t end2 = clock();

	cout << "TestTranVal--time:\t" << end1 - begin1 << endl;
	cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;

	return 0;
}

 

 造成这一现象的原因在1.5后面哦!

1.7引用和指针的区别

相信大家在学习引用的时候,总是有一种似曾相识的感觉,这是因为他的功能和指针有很大部分的重叠。

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main()
{
	int a = 10;
	int c = 9;
	int& b = a;
	b = 20;
	cout << " a " << a << endl;

	int* p = &a;
	*p = 30;
	cout << " a " << a << endl;
	p = &c;
	cout << " *p " << *p << endl;
	return 0;
}

 

我们来看看汇编视角的引用与指针:

 

 很显然,在汇编看来,指针与引用并没有区别。

我们试着来总结一下指针与引用的不同点

1.引用是给一个变量起别名,而指针存储变量的数据地址。

2.引用在定义时必须初始化,指针则没有这个要求。

3.引用定义后就不能再更改指向,指针可以,这是他们两个最本质的区别,也是引用无法完全替代指针的根本原因。

 4.在sizeof中,引用的结果就是引用类型的大小,而指针恒定为4或者8(取决于编译器)。

5.引用相对于指针更加安全,没有空引用,但有空指针。

6.譬如自增自减,引用操作的是对象的实体内容,而指针操作的是地址块。

7.在底层汇编视角,引用与指针并没有什么区别。

8.对于对象的访问方式不同,指针需要解引用,而引用由编译器处理。

2.  内联函数

2.1 概念 

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


如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用。

 2.2 内联函数的特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到
 

 

结语 

    指舞键盘上,悠然博弈回。如果您感兴趣,不妨看看我其他的文章,也许会有更多的收获。希望我们能在未来的日子里一起成长,共同进步。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1416408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

21.Arrays类

Arrays类 1. 概述2. 常见方法3. sort 方法的自定义排序4. 代码示例5. 输出结果6. 注意事项 具体信息请查看 API 帮助文档 1. 概述 Arrays类是Java中的一个工具类&#xff0c;位于java.util包中。 它提供了一组静态方法&#xff0c;用于操作数组。通过Arrays类&#xff0c;我们…

springboot136人口老龄化社区服务与管理平台

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

web前端---------盒子模型2

一------内边距 padding 属性用来设置元素的内边距长度&#xff0c;元素在默认情况下没有内边距&#xff0c;其值为none。 &#xff08;1&#xff09;当 padding 属性中仅含一个值时&#xff0c;该长度应用在上、下、左、右四个区域。 &#xff08;2&#xff09;当 padding …

Java二分查找-图文

一、二分查找概念 二分查找也叫折半查找&#xff0c;是在一组有序(升序/降序)的数据中查找一个元素&#xff0c;它是一种效率较高的查找方。 二、二分查找原理 1.二分查找的数组必须是有序数值型数组。 2.将想要查找的目标元素与查找范围内的中间元素进行比较&#xff0c;如果…

文件包含漏洞长度截断

长度截断 文件漏洞的利用方式什么是长度截断通过实操理解00截断对版本要求更高一点&#xff0c;而长度截断则是利用了windows的系统漏洞&#xff0c;就是windows文件名&#xff08;就是文件名后缀之后&#xff09;之后如果有空格&#xff0c;或者是点都会被忽略掉&#xff0c;也…

研发日记,Matlab/Simulink避坑指南(八)——else if分支结构Bug

文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结归纳 前言 见《研发日记&#xff0c;Matlab/Simulink避坑指南(三)——向上取整Bug》 见《研发日记&#xff0c;Matlab/Simulink避坑指南(四)——transpose()转置函数Bug》 见《研发日记&#xff0c;Matlab/Simuli…

Spring Cloud + Vue前后端分离-第13章 网站开发

源代码在GitHub - 629y/course: Spring Cloud Vue前后端分离-在线课程 Spring Cloud Vue前后端分离-第13章 网站开发 13-1 网站模块的搭建 新建web模板 1.网站开发&#xff0c;增加web模块&#xff0c;使用命令&#xff1a;vue create web vue版本4.2.3 大家拿到一个v…

【Web】小白也能做的RWCTF体验赛baby题部分wp

遇到不会的题&#xff0c;怎么办&#xff01;有的师傅告诉你完了&#xff0c;废了&#xff0c;寄了&#xff01;只有Z3告诉你&#xff0c;稳辣&#xff01;稳辣&#xff01;都稳辣&#xff01; 这种CVE复现的题型&#xff0c;不可能要求选手从0到1进行0day挖掘&#xff0c;其实…

如何快速上手一个vue框架

安装nvm 下载nvm-setup.zip&#xff1a; https://github.com/coreybutler/nvm-windows/releases 解压安装nvm&#xff1a; 创建两个文件夹&#xff0c;一个是nvm的安装位置&#xff0c;另一个是node.js的下载位置。不需要配置环境变量和修改setting文件了 检查nvm是否安装成功…

Redis客户端之Redisson(三)Redisson分布式锁

一、背景&#xff1a; 高效的分布式锁设计应该包含以下几个要点&#xff1a; 1、互斥&#xff1a; 在分布式高并发的条件下&#xff0c;我们最需要保证&#xff0c;同一时刻只能有一个线程获得锁&#xff0c;这是最基本的一点 2、防止死锁&#xff1a; 在分布式高并发的条…

【vue3源码】vue源码探索之旅:项目介绍

简言 记录下我眼中的vue源码项目。 gitHubvue3项目仓库 项目要求: vue版本 3.4.15nodeV18.12.0以上使用pnpm包管理器vitest测试框架Vue3 vue3是渐进式JavaScript框架,易学易用,性能出色,适用场景丰富的 Web 前端框架。 Vue 是一个框架,也是一个生态。其功能覆盖了大部分…

VBA技术资料MF111:将表对象转换为正常范围

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

【Linux C | 进程】Linux 进程间通信的10种方式(1)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

python14-Python的字符串之原始字符串

由于字符串中的反斜线都有特殊的作用。 因此当字符串中包含反斜线时,就需要对其进行转义。 比如写一条Windows的路径:D:\pythonStudy\demo,如果在Python 程序中直接这样写肯定是不行的,需要写成:D:\\pythonStudy\\demo,这很烦人. 此时可借助于原始字符串来解决这个问题。…

LeNet跟LeNet5详解

1 LeNet结构 主要是为了手写数字识别 具体结构讲解&#xff1a;从图中例子可得 1 先传入一个灰度图像尺寸为1x28x28&#xff0c;通道数为1&#xff0c;尺寸为28x28的灰度图像 2 第一层5x5卷积&#xff0c;经过公式 输入图像尺寸-卷积核尺寸2padding/步长1&#xff0c;&#…

网络编程小总结

【一】网络编程 互联网的本质就是一些网络协议 【1】网络开发架构 &#xff08; 1 &#xff09; C / S 架构 C : client &#xff08;客户端&#xff09; S: server (服务端) APP - 就是服务端 C/S 架构通过客户端软件和服务器之间的交互&#xff0c;实现了前端界面和后…

32GPIO输入LED闪烁蜂鸣器

一.GPIO简介 所有的GPIO都挂载到APB2上&#xff0c;每个GPIO有&#xff11;&#xff16;个引脚 内核可以通过APB&#xff12;对寄存器进行读写&#xff0c;寄存器都是32位的&#xff0c;但每个引脚端口只有&#xff11;&#xff16;位 驱动器用于增加信号的驱动能力 二.具体…

Android系统开发之TimeZoneDetectorService浅析--下

TimeZoneDetectorService类图 可以看出TimeZoneDetectorService类&#xff0c;其具体实现是由TimeZoneDetectorStrategy类完成的。 在TimeZoneDetectorService类中&#xff0c;三种更新时区的接口分别为&#xff1a; suggestGeolocationTimeZone() //更新时区主要有三种方式…

linux安装docker-compose

前言 如果你的docker版本是23&#xff0c;请移步到linux安装新版docker&#xff08;23&#xff09;和docker-compose这篇博客 查看docker版本命令&#xff1a; docker --version今天安装docker-compose的时候&#xff0c;找了很多教程&#xff0c;但是本地一直报错&#xff0…

【mongoDB】图形化界面工具(mongoDB Compass)

官网地址&#xff1a;https://www.mongodb.com/try/download/compass 下载完之后直接安装 桌面上会产生一个快捷方式 双击就会进入mongoDB图形化界面工具