learn C++ NO.2 ——认识引用、auto关键字

news2024/11/25 4:19:53

1.引用

1.1 引用的概念

引用并不是定义一个新的变量,而是给已经存在的变量起的一个别名。从语言的层面上,编译器并不会为了引用而去开辟新的内存空间。引用和被它引用的变量是共用一块内存空间的。举个生活中引用的例子,西游记中,孙悟空,他的别名有很多。像齐天大圣、弼马温、美猴王等。下面通过一个简单的代码看看什么是引用。

#include<iostream>

using namespace std;

int main()
{
	int a = 10;
	int& ra = a;

	cout << &a << endl;
	cout << &ra << endl;

	return 0;
}

在这里插入图片描述
运行上一段代码可以看见,引用的变量和被引用的变量是共享同一块空间的。那么对ra++是否影响a呢?答案是会影响的。

#include<iostream>

using namespace std;

int main()
{
	int a = 10;
	int& ra = a;
	
	ra++;

	cout << a << endl;

	return 0;
}

在这里插入图片描述
在这里插入图片描述
需要注意的是引用的类型和被引用的类型需要保持一致,这里简单举一个样例。

int main()
{
	double d = 1.23;
	int& ri = d;//这样引用是错误的
	return 0;
}

在这里插入图片描述

1.2 引用的特性

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

引用在定义时,必须初始化。否则编译器也不知道你具体是谁的别名。

int main()
{
	double a = 10;
	int& ri;//这样引用是错误的
	return 0;
}

在这里插入图片描述

int main()
{
	int a = 5;
	int& b = a;
	int& c = b;
	int& d = a;

	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	cout << d << endl;

	return 0;
}

在这里插入图片描述
这里的c虽然是b的别名,但它的本质还是a的别名。
在这里插入图片描述

int main()
{
	int a = 5;
	int& b = a;
	int& c = b;
	int& d = a;

	int x = 10;
	d = x;//赋值操作,而非引用操作

	return 0;
}

上面的代码中,在前面的基础上多定义了一个变量x,d = x这句代码的意思是将x的值赋给引用变量d,这里不会改变d的指向,d依旧是变量a的别名。所以,这句话后a,b,c,d变量的值都变成10。这也说明了C++中一个引用变量只能引用一个实体。

1.3 引用的使用场景

1.3.1 引用做参数

在之前的C语言学习中,当需要写一个交换两个局部变量值的函数时,通常需要一个两个变量类型的指针变量来做参数。当学习引用之后,便可以感受引用做参数带来的好处。

//指针做参数
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//引用做参数
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;v
}

int main()
{
	int a = 10,b = 20;
	int x = 3, y = 5;
	Swap(&a,&b);
	Swap(x,y);
	std::cout << a << " " << b << std::endl;
	std::cout << x << " " << y << std::endl;
	return 0;
}

在这里插入图片描述
通过两种方式实现Swap函数的比较可以发现,使用引用做函数的参数和使用指针做函数参数相比,使用引用做函数参数在函数调用和函数的实现都比较方便。因为,在调用时不必传变量的地址,在实现时不再需要解引用操作。所以,引用通常可以用做输出型的参数。引用做参数还可以减少传参时的拷贝从而提升效率。下面我通过举例和原理来进行说明。

#include<iostream>
#include <time.h>

using namespace std;

struct A { int a[100000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

在这里插入图片描述
从上述样例中可以看到,当传参为大对象(即sizeof值较大的对象)时,在函数栈帧开辟时,传结构体拷贝实参的性能损耗较大。而传引用,传的是结构体的别名,拷贝的损耗几乎可以忽略。所以,引用做参数传参的效率是更高的。

1.3.2 引用做返回值

在讲引用做返回值之前,我先简单普及一个概念。就是函数的返回值是怎么带回的。
在这里插入图片描述
引用做函数返回值就相当于上图中的返回值n是调用处ret的别名。

int& Add(int x, int y)
{
	int n = x + y;
	return n;
}

int main()
{
	int& ret = Add(1, 2);
	cout << ret << endl;
	return 0;
}

在这里插入图片描述

在这里插入图片描述
但是,这段代码是错误的,因为局部变量n随时函数栈帧的销毁,所属空间使用权归还给操作系统。这里获取到的数是3是恰好的,因为Add函数的栈帧没有被破坏。如果加上一次函数调用,那么原属于Add函数的栈帧会用于调用其它函数,从而导致引用返回的变量被覆盖。
在这里插入图片描述

总结

1、任何场景下都可以使用引用做参数。
2、慎用引用做返回值,如果变量出了局部作用域就销毁,使用引用做返回值就有可能会产生不可预知的错误。尽量使用存储在静态区、全局空间、堆区等等出了局部作用域不销毁的变量上做引用返回。

1.4 常引用

常引用就是对引用的变量前加上const修饰,使得引用具有常数性。

1.4.1 常引用的概念

int main()
{
	int a = 10;
	const int& ra = a;
	//ra++;//常属性变量不能被修改
	a++;
	//由于ra是a的别名,a的改变会影响ra
	cout << ra << endl;
	return 0;
}

在这里插入图片描述

在这里插入图片描述

1.4.2 引用的权限放大问题

样例

int Func()
{
	int n = 10;
	return n;
}

int main()
{
	int& ret = Func();
	return 0;
}

在这里插入图片描述

前面我们说到,函数调用结束后,返回值会存在一个临时空间里,然后带回给调用处。这里由于临时变量是具有常属性的。所以不能直接赋给ret,因为涉及引用权限放大问题。此时const修饰一下引用,使引用也具有常属性就可以。
在这里插入图片描述

int Func()
{
	int n = 10;
	return n;
}

int main()
{
	const int& ret = Func();
	return 0;
}

在这里插入图片描述

1.4.3 引用的权限平移和缩小

int main()
{
	//权限的平移
	const int a = 10;
	const int&ra = a;

	int b = 20;
	int& rb = b;

	//权限的缩小
	int* c = NULL;
	const int*& rc = c;

	return 0;
}

1.5 引用和指针的区别

1、定义引用是不需要额外的开辟空间,而定义指针变量是需要额外开辟空间来存储的。
2、引用必须初始化,指针可以不初始化。
3、引用在初始化指向一个实体后,便不能在更改指向。而指针变量指向一个实体后,可以继续更改自己的指向。
4、没有空引用,但是有空指针。
5、 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位平台下占8个字节)。
6、引用自加1,表示引用的实体对象的值加1。指针变量自加1,表示指针变量向后偏移一个自身类型大小的距离。
7、没有多级引用的概念,但是指针是有分多级,如一级指针变量的地址就得用二级指针变量存储。
8、访问实体的方式不同,引用由编译器来处理,指针需要解引用操作。
9、引用和指针比起来,使用起来更加安全。

1.5.1 引用的汇编指令实现

在这里插入图片描述
通过调试可以看到,VS2019编译器下,引用和指针在汇编指令实现的方式是类似的,都是将实体的值通过保存在寄存器上,再拷贝给编译器开辟的特定空间存储。所以,引用在汇编指令的层面上的实现其实是会开辟内存的。但是,在学习引用这个概念的时候还是要从C++的语法层面看待这一现象,即引用是不开辟空间的,这样有助于学习和理解。、

2.auto关键字

auto关键字由c++11标准引入。是一个根据右表达式的类型来推导左表达式类型的关键字。auto在实际中最常见的优势用法就是跟C++11提供的新式for循环,还有lambda表达式等进行配合使用。

2.1 auto关键字的概念

int main()
{
	int a = 10;
	int b = a;
	auto c = a;
	auto d = 1 + 1.11;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

在这里插入图片描述

需要注意的是使用auto关键字必须初始化。如果只是声明,编译器不能推导出他具体的类型。所以auto并不是一种类型的声明,而是类型的‘占位符’。

2.2 auto 与指针和引用

auto自动推导指针类型时,auto后是否带(*)指针标识符都是可以的。但是,auto自动推导为引用时,auto后必须带(&)引用标识符。

int main()
{
	int a = 10;
	auto* b = &a;//ok
	auto& c = a;//ok
	auto* d = 10;//error
	auto e = &a;//ok
	return 0;
}

在这里插入图片描述

2.3 auto不能推导的场景

2.3.1 auto做函数形式参数

因为编译器无法对auto作为形式参数的类型进行推导。

void test(auto a)
{
	\\...
}

2.3.2 auto不能定义数组

void TestAuto()
{
	int a[] = {1,2,3};
	auto b[] = {456};
}

3.基于范围的for循环

在C++11之前,遍历一个数组可以用以下方式

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };

	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}

	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
	{
		cout << *p << endl;
	}

	return 0;
}

也许是觉得这个语法太麻烦了,c++11给出了一个甜点语法即(范围for)。


int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	
	for (auto& e : array)
	{
		e++;
	}

	for (auto e : array)
	{
		cout<<e<<" ";
	}
	cout << endl;

	return 0;
}

在这里插入图片描述
当你使用了这种for循环遍历数组,你还会想用C语言的方式来写吗?当然是不会的。不过这种用法下,范围for的迭代条件必须是明确的。对于数组而言,范围for的遍历范围就是从第一个元素到最后一个元素。对于类而言,应该提供begin和end的
方法,begin和end就是for循环迭代的范围。

//错误样例
void test(int arr[])
{
	for(auto& e : arr)
	{
		cout<< e<<endl;
	}
}

范围for的范围不确定所以会报错。最后就是迭代的对象要实现++和==的操作。这个由于我现在还没有学习这一块的知识,只能等以后学了才知道。

4.nullptr关键字(C++11)

在c/c++编程的学习过程中,我们不免得要和指针打交道。我们需要养成良好指针使用习惯。那就是初始化指针变量。初始化指针变量的好处可以让指针变量的指向更安全。

int main()
{
	int *pa =NULL;
	int* pb = 0;
	return 0;
}

而在传统的C语言头文件(stddef.h)中对于NULL这个宏是这样定义的

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

在这里插入图片描述

可以看到当以cpp格式编译时,NULL就会被展开成0值,这其实是有些许缺陷的。当然,从语言发展的角度来看,这么做也许是祖师爷的迫于无奈。在C++11标准定义中,nullptr关键字引入,这样也就方便了我们去初始化指针。当然sizeof(nullptr)的大小和sizeof((void*)0)的大小是一样的。为了提高程序的健壮性,推荐使用nullptr关键字。

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

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

相关文章

C++入门(上)

C入门 c是对于c语言的补充而发展的一种面向对象的语言&#xff0c;也能兼容c语言的内容&#xff0c;所以c语言的东西可以在cpp文件中写c语言的内容&#xff0c;也是可以运行的&#xff08;可以混写&#xff09; 文章目录 C入门命名空间命名空间的定义命名空间的使用 C的输入…

22、Tweak原理及部分逆向防护

一、Tweak原理 1.1 Tweak产物.dylib 执行make命令时,在 .theos的隐藏目录中,编译出obj/debug目录,包含 arm64、arm64e两种架构,同时生成readbadges.dylib动态库 在arm64、arm64e目录下,有各自架构的readbadges.dylib,而debug目录下的readbadges.dylib,是一个胖二进制文件 fi…

ShareSDK QQ平台注册

注册开发者账号 1.在QQ互联开放平台首页 QQ互联官网首页 &#xff0c;点击右上角的“登录”按钮&#xff0c;使用QQ帐号登录&#xff0c;如下图所示&#xff1a; 重要提示&#xff1a; 开发者QQ号码一旦注册不能变更&#xff0c;建议使用公司公共QQ号码而不是员工私人号码注册…

软件测试好学习吗?

软件测试好不好学习其实各自的认知都不同&#xff0c;想要知道自己能不能学会&#xff0c;对于自己怎么样&#xff0c;最简单的方法就是找个基础教程先去学习一下了~ 其实软件测试这个行业与其他岗位相比&#xff0c;对零基础的学习者更加友好。即使你不懂互联网&#xff0c;不…

小程序过审失败,怎么解决?

小程序过审失败&#xff0c;怎么解决&#xff1f; 如果你的小程序未能通过审核&#xff0c;可以参考以下步骤解决问题&#xff1a; 1、审核不通过原因&#xff1a;在审核失败的通知中会注明不通过的具体原因和相关文件路径。请先认真阅读并理解不通过的原因&#xff0c;找到问…

存储电路:计算机存储芯片的电路结构是怎样的?

我们把用于存储数据的电路叫做存储器&#xff0c;按照到 CPU 距离的远近&#xff0c;存储器主要分为寄存器、缓存和主存。我们就来重点分析这三种存储器的特点、原理&#xff0c;以及应用场景。 存储器是由基本的存储单元组成的&#xff0c;要想搞清楚存储器原理&#xff0c;我…

【C++关联容器】set的成员函数

目录 set 1. 构造、析构和赋值运算符重载 1.1 构造函数 1.2 析构函数 1.3 赋值运算符重载 2. 迭代器 3. 容量 4. 修改器 5. 观察者 6. 操作 7. 分配器 set set是按照特定顺序存储唯一元素的容器。 在一个set中&#xff0c;一个元素的值也是它的标识&#xff08;值…

插装式两位两通电磁阀DSV-080-2NCP、DDSV-080-2NCP

特性 压力4000 PSI(276 Bar) 持续的电磁。 硬化处理的提升阀和柱塞可获得更长的寿命和低泄漏量。 有效的混式电磁铁结构。 插装阀允许交流电压。可选的线圈电压和端子。 标准的滤网低泄漏量选择 手动关闭选择。 工业化通用阀腔。 紧凑的尺寸。 两位两通常闭式双向电磁…

热门好用的企业网盘工具大盘点

企业网盘作为热门的企业文件管理工具相比于个人网盘&#xff0c;更注重安全性&#xff0c;并增加了协同功能。当下市面上的企业网盘工具可谓是百花齐放&#xff0c;今天就盘点几款热门好用的网盘工具&#xff0c;希望能帮助您挑选到心仪的网盘工具~ 1. Zoho Workdrive Zoho Wo…

#PythonPytorch 2.如何对CTG特征数据建模

系列文章目录 #Python&Pytorch 1.如何入门深度学习模型 #Python&Pytorch 2.如何对CTG特征数据建模 我之前也写过一篇使用GBDT对UCI-CTG特征数据进行建模的博客&#xff0c;不过那是挺早的时候写的&#xff0c;只是简单贴了代码&#xff0c;方便了解流程而已&#xff0…

原神3.2剧情服搭建教程

同步官服所有剧情和交互 优化后电脑16G运行内存也可以完美运行 数据库再次启动报错的,把将redis.service中的Type=forking配置删除或者注释掉即可。 位于:/usrb/systemd/system/redis.service 然后重启服务就不会爆错了。 下面是具体步骤 su root (此处会提示输入密…

相机雷达联合标定cam_lidar_calibration

文章目录 运行环境&#xff1a;1.1 ROS环境配置1&#xff09;工作空间创建和编译2&#xff09;官方数据集测试环境 2.1 在线标定1&#xff09;数据类型2&#xff09;标定板制作3&#xff09;配置文件4&#xff09;开始标定5&#xff09;完整实现步骤 3.1 python版本选择3.2 rvi…

医疗保障信息平台HASF应用系统技术架构名词解释技术选型架构图

下载地址&#xff1a; 医疗保障信息平台HASF应用系统技术架构规范.pdf下载—无极低码 HSAF 医疗保障应用框架&#xff08;Healthcare Security Application Framework&#xff09; IaaS 基础设施即服务&#xff08;Infrastructure-as-a-Service&#xff09; PaaS 平台即服务…

实现了单链表各种功能,并配上详细解读。

单链表 链表的概念及结构链表的分类链表的实现初始化打印申请结点头插尾插头删尾删查找在pos位置之后插入在pos位置之前插入删除pos位置之后的值删除pos位置的值销毁 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素…

离散数学期末复习第一章 数理逻辑

离散数学 离散数学是研究各种各样的离散量的结构及离散量之间的关系一门学科&#xff0c;是计算机科学中基础理论的核心课程。 什么是连续变量&#xff1f; 在一定区间内可以任意取值的变量叫连续变量&#xff0c;其数值是连续不断的&#xff0c;相邻两个数值可作无限分割&a…

buuctf4

目录 [极客大挑战 2019]LoveSQL [极客大挑战 2019]Http [极客大挑战 2019]Knife qr 镜子里面的世界 ningen 小明的保险箱 爱因斯坦 easycap 隐藏的钥匙 另外一个世界 FLAG [极客大挑战 2019]LoveSQL 1.启动环境&#xff0c; 使用万能密码尝试一下 2.跳转到了check.php…

维度云工业品ERP进销存软件教您如何突破工业品生意的困境?

是困境也是机遇 随着全球化和技术进步的不断推进&#xff0c;工业品贸易正逐渐成为国际贸易的重要组成部分。工业品包含了从原材料、零部件到工业设备、机械以及其他工业用品等范畴的产品&#xff0c;涉及各种制造、加工和组装过程。在全球供应链互联互通之下&#xff0c;工业品…

【人工智能概论】 用Python实现数据的归一化

【人工智能概论】 用Python实现数据的归一化 文章目录 【人工智能概论】 用Python实现数据的归一化一. 数据归一化处理的意义二. 常见的归一化方法2.1 最大最小标准化&#xff08;Min-Max Normalization&#xff09;2.2 z-score 标准化 三. 用sklearn实现归一化 一. 数据归一化…

服务(第十一篇)LVS

什么是群集&#xff1f; 多台主机组成的一个整体&#xff0c;提供一个ip访问通道&#xff0c;所有的主机干一件事 提供同样的服务。 群集有哪些类型&#xff1f; ①负载均衡群集&#xff08;LB&#xff09;&#xff1a; 提高系统的响应能力&#xff0c;处理更多的访问请求&a…

20、Theos越狱调试Wallet

前面的总结中使用砸壳重签后的App进行调试,本篇在越狱环境下不重签App进行调试,但是还是需要砸壳获取Headers. 一、Cycript 1.1 在越狱环境中使用Cycript 在越狱环境上,安装Cycript插件.需要先安装adv-cmds插件,因为被Cycript插件所依赖、在Cydia中,安装Cycript 在设备中找到…