【C++学习】C++入门 | 引用 | 引用的底层原理 | auto关键字 | 范围for(语法糖)

写在前面:

上一篇文章我介绍了缺省参数和函数重载,

探究了C++为什么能够支持函数重载而C语言不能,

这里是传送门,有兴趣可以去看看:http://t.csdn.cn/29ycJ

这篇我们继续来学习C++的基础知识。

目录

写在前面:

1. 引用

2. 引用的底层

3. auto 关键字

4. 范围for(语法糖)

总结:

写在最后:


1. 引用

引用就是起别名。

举一个经典的例子:周树人给自己起了一个笔名叫鲁迅,

那鲁迅和周树人是同一个人吗?答案是肯定的。(你找鲁迅跟我周树人有什么关系。。。)

那引用的语法是怎么样的呢:

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int& b = a; //b就是a的别名

	cout << a << endl;
	cout << b << endl;

	return 0;
}

引用的符号是&,在C语言中这个符号是取地址,

引用的符号也是这个,他共用了这个符号。

上面这段代码其实我们就能直接理解成 a 是周树人,b 是他的别名鲁迅。

所以他们实际上是一样的,来看输出:

10
10

所以 b 和 a 其实就是一样的,所以 a = 10,b 当然也等于10。

所以我们再看:

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int& b = a; //b就是a的别名

	cout << &a << endl;
	cout << &b << endl;

	return 0;
}

输出:

005EFE28
005EFE28

他们的地址也是一样的。

再来看:

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int& b = a; //b就是a的别名
	int& c = b;
	int& d = c;

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

	return 0;
}

输出:

00AFF8B4
00AFF8B4
00AFF8B4
00AFF8B4

这样子当然也是一样的。

现在你大概就知道引用是什么样子了。

另外,引用是不能这样写的:

#include <iostream>
using namespace std;

int main()
{
	int& a;
	return 0;
}

使用引用的时候你一定要告诉编译器,你是谁的别名。

这里我们马上来一个场景,

当我们学了引用之后,指针一下子就不香了,很多地方我们就直接使用引用了:

比如说经典的Swap函数:

#include <iostream>
using namespace std;

// 实际上这里x就是a的引用,y就是b的引用
void Swap(int& x, int& y) {
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	
	Swap(a, b);
	
	cout << a << endl;
	cout << b << endl;

	return 0;
}

我们就能根据引用的特性实现,不需要用繁杂的指针操作,

让 x 是 a 的引用,y 是 b 的引用,

这样我们在函数里面操作 x 和 y 的时候其实就是在操作 a 和 b 。

这里我总结了引用的特性作为补充:

1. 一个变量可以有多个引用(就好像一个人可以有多个别名)

2. 引用必须在定义时初始化(前面演示过了)

3. 引用一旦引用了一个实体,就不能再引用其他实体

(说人话就是如果你是 a 的引用,你就不能改成是 b 的引用,只能一直是 a 的引用)

这里给出例子:

#include <iostream>
using namespace std;

int main()
{
	//一个变量可以有多个引用
	int a = 0;
	int& b = a;
	int& c = a;

	//引用在定义时必须初始化
	//int& d;

	int x = 10;
	c = x; //这里是赋值操作,c依旧是a的引用(别名)

	return 0;
}

这是引用的基本特性,一定要熟悉好。

这里我继续介绍引用的作用:

1. 引用作为函数参数(输出型参数)(前面举了Swap函数的例子)

引用作为函数参数还能提高效率(之后学深浅拷贝的时候会再介绍)

2. 引用做返回值

我们来看这样一个例子:

 当一个有返回值的函数返回值的时候,

他会将返回值拷贝生成一个临时变量,再将临时变量赋值给 ret 。

学过C语言,我们都知道,当这个函数结束的时候他的函数栈帧就销毁了,

所以他才需要生成一个临时变量,这样就能将返回值成功赋给主函数中的 ret 。

而且无论返回值是个什么变量,就算是静态变量,在返回的时候也会生成临时变量,

这个时候该引用出场了,

使用引用作为返回值,就不会生成临时变量:

#include <iostream>
using namespace std;

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

int main()
{
	int ret = Count();

	return 0;
}

所以这个时候我们又能理解前面所说的引用的作用,

使用引用能够提高效率,减少拷贝。

但是要注意,我们上面那段代码用引用返回没有问题,

那如果变量 n 不是一个静态变量呢?

#include <iostream>
using namespace std;

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

int main()
{
	int ret = Count();

	return 0;
}

这样就出问题了,

ret 的值是不确定的,因为出了作用域 n 就不在了。

如果Count函数结束,栈帧销毁,没有清理栈帧,那么ret的结果侥幸是正确的,

如果Count函数结束,栈帧销毁,清理了栈帧,那么ret的结果就是随机值。

总结:

1. 基本任何场景都可以用引用传参,

2. 谨慎使用引用返回,避免出现上面的情况。

这里还有一种情况,常引用:

比如说这段代码:(这是一段错误代码)

#include <iostream>
using namespace std;

int main()
{
	//引用的过程中,权限不能放大
	const int a = 0;
	int& b = a;
	
	return 0;
}

再来看这一段代码:

#include <iostream>
using namespace std;

int main()
{
	//引用的过程中,权限不能放大
	//const int a = 0;
	//int& b = a;

	//引用的过程中,权限可以平移或者缩小
	int a = 0;
	int& b = a;
	const int& c = b;

	return 0;
}

这个时候问题来了,

我们能不能修改 a 的值呢?

#include <iostream>
using namespace std;

int main()
{
	//引用的过程中,权限不能放大
	//const int a = 0;
	//int& b = a;

	//引用的过程中,权限可以平移或者缩小
	int a = 0;
	int& b = a;
	const int& c = b;
	a++; 

	return 0;
}

答案是可以的,const int& c 修改的是这个别名的权限,

而 a 是不受影响的。

我们再来看一个例子:

#include <iostream>
using namespace std;

int main()
{
	double a = 1.1;
	int b = a; //隐式类型转换

	int& c = a;// ?

	return 0;
}

我们都知道,第二个语句是隐式类型转换,

那 int& c = a ,能编译通过吗? 答案是不能:

 难道是因为不同类型的变量不能用引用吗?

我们再来看:

 加了一个 const 在前面然后就编译成功了。。。

这又是为什么?

实际上,在学习C语言阶段我们曾经学习过,

在进行类型转换的时候,会生成一个临时变量,如图:

而临时变量具有常性,具有常性是什么意思呢?其实就类似用 const 修饰过一样。

这样就会出现权限的放大,所以导致报错。

这个时候我们结合前面的例子来看:

#include <iostream>
using namespace std;

int cnt() {
	static int n = 0;
	return n;
}

int main()
{
	int& a = cnt(); //这里会出错

	return 0;
}

为什么这段代码会出错?

其实这也是一个道理,函数返回值的时候会创建一个临时变量,

而临时变量具有常性,所以这里也会出现权限放大导致的错误。

只要在 int& a 前面加上一个const就行了,这样就达成了权限的平移。、

2. 引用的底层

那么引用又是怎么实现的呢?他的底层是什么样的?

我们还是得从汇编的角度来观察,

先来看这段代码:

#include <iostream>
using namespace std;

int main()
{
	int a = 0;

	int& ra = a;

	int* pa = &a;

	return 0;
}

这段代码里面分别使用了引用和指针来对 a 进行操作,

来看看他的汇编代码是怎么样的:

哦~,看看我们发现了什么,引用和指针的底层怎么一模一样?

在语法的层面:

引用不开空间,是对 a 取别名,

而指针开空间,是取 a 的地址,

但是,从底层汇编的角度来看,引用是以类似指针的方式实现的。 

不过我们平时牢记引用在语法层的效果就行,底层就简单了解一下。

补充:引用之后还会有许多的场景,这些我会之后遇到具体的场景在做总结和分析。

3. auto 关键字

auto 能根据右边的表达式自动推导类型,

来看一个例子:

#include <iostream>
using namespace std;

int main()
{
	int a = 0;
	auto c = a;
	//这个操作能够查看变量的类型
	cout << typeid(c).name() << endl;

	return 0;
}

输出:

int

当然,右边不一定要是变量,也可以使表达式:

#include <iostream>
using namespace std;

int main()
{
	auto c = 1 + 1;
	//这个操作能够查看变量的类型
	cout << typeid(c).name() << endl;

	return 0;
}

输出:

int

现在这样看来,auto好像价值不大,

但是如果以后遇到非常复杂的类型的时候,直接使用auto会非常方便。

这里补充一点:auto是不能作为函数参数或者用来声明数组的。

4. 范围for(语法糖)

这里再基于我们刚刚学的auto,介绍一下范围for。

C语言阶段,我们平时遍历一个数组都是这样遍历的:

#include <iostream>
using namespace std;

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

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;

	//使用范围for
	for (auto e : arr) {
		cout << e << " ";
	}

	return 0;
}

以上面的代码为例:

范围for 其实就是依次取数组中的数据赋值给e,

自动迭代,自动判断结束。

你就说范围for 方不方便,甜不甜?不甜又怎么会被叫做语法糖呢。

这里补充一点,修改e 是不会修改到数组的,因为e 的值是取数组的数据赋值过来的,

如果想要通过修改e 来修改数组,需要加个引用:

#include <iostream>
using namespace std;

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

	//使用范围for
	for (auto& e : arr) {
		e++;
		cout << e << " ";
	}

	return 0;
}

输出:

2 3 4 5 6

当然,不止int 类型的数组,其他什么类型的数组都可以用范围for,

还有一些我们之后要学的容器,因为auto 会自动推导类型。

总结:

C++入门铺垫的知识学的差不多了,准备要开始类和对象了。

写在最后:

以上就是本篇文章的内容了,感谢你的阅读。

如果感到有所收获的话可以给博主点一个哦。

如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图像金字塔

​ 图像金字塔是由一幅图像的多个不同分辨率的子图构成的图像集合。是通过一个图像不断的降低采样率产生的&#xff0c;最小的图像可能仅仅有一个像素点。下图是一个图像金子塔的示例。从图中可以看到&#xff0c;图像金字塔是一系列以金字塔形状排列的、自底向上分辨率逐渐降低…

【数字图像处理】3.对比度增强

目录 3.1 灰度直方图 3.2 线性变换 3.3 直方图正规化 3.4 伽马变换 3.5 全局直方图均衡化 3.6 CLAHE 对比度增强是图像增强的一种&#xff0c;它主要解决的是图像的灰度级范围较小造成的对比度较低的问题&#xff0c;目的是将图像的灰度级增强到指定范围&#xff0c;使得…

实战:用docker-compose容器化springboot项目

文章目录 前言技术积累docker-compose定义docker-compose文件参数docker-compose命令 实战演示1、创建挂载路径2、编写docker-compose.yml3、启动并管理容器 写在最后 前言 前面我们学习和实战了用dockerfile构建镜像&#xff0c;通过镜像可以任意在docker环境容器化部署项目。…

Opencv-C++笔记 (7) : opencv-文件操作XML和YMAL文件

文章目录 一、概述二、文件操作三、打开文件四、写入五、读写个人源码 一、概述 除了图像数据之外&#xff0c;有时程序中的尺寸较小的Mat类矩阵、字符串、数组等 数据也需要进行保存&#xff0c;这些数据通常保存成XML文件或者YAML文件。本小节中将介绍如何利用OpenCV 4中的函…

神经网络:卷积操作

当谈到计算机视觉中的网络模型结构时&#xff0c;卷积操作是其中一个关键的组成部分。卷积操作是一种基于局部区域的操作&#xff0c;它在计算机视觉中用于图像处理和特征提取。 卷积操作的原理如下&#xff1a; 给定一个输入图像和一个称为卷积核&#xff08;或滤波器&#x…

【ARIMA-SSA-LSTM】合差分自回归移动平均方法-麻雀优化-长短期记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

前端Vue自定义数字输入框 带加减按钮的数字输入框组件

前端Vue自定义数字输入框 带加减按钮的数字输入框组件&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13163 效果图如下&#xff1a; # cc-numbox #### 使用方法 使用方法 <!-- title: 标题 isSetMax: 是否设置最…

synchronized原理

Synchronized能够实现原子性和可见性&#xff1a;在Java内存模型中&#xff0c;synchronized规定&#xff0c;线程在加锁时&#xff0c;先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。 synchroniz…

Axure教程—折叠面手风琴效果

上文中介绍了用Axure制作折叠面板的基础制作&#xff0c;这次介绍折叠面板手机风琴效果 效果 预览地址&#xff1a;https://e18rf6.axshare.com 功能 点击标题展开内容&#xff0c;点击另一标题&#xff0c;其展开的内容折叠 制作 拖入四个动态面板&#xff0c;分别命名为1、…

【微服务】springboot 通用限流方案设计与实现

目录 一、背景 二、限流概述 2.1 dubbo 服务治理模式 2.1.1 dubbo框架级限流 2.1.2 线程池设置 2.1.3 集成第三方组件 2.2 springcloud 服务治理模式 2.2.1 hystrix 2.2.2 sentinel 2.3 网关层限流 三、常用限流策略 3.1 限流常用的算法 3.1.1 令牌桶算法 3.1.2 …

【深度学习】2-5 神经网络-批处理

批处理&#xff08;Batch Processing&#xff09;是指在深度学习中每次迭代更新模型参数时同时处理多个样本的方式。 批处理时要注意对应维度的元素个数要一致 关于之前手写数字识别的例子&#xff1a; 用图表示&#xff0c;可以发现&#xff0c;多维数组的对应维度的元素个数…

前端Vue自定义导航栏菜单 定制左侧导航菜单按钮 中部logo图标 右侧导航菜单按钮

前端Vue自定义导航栏菜单 定制左侧导航菜单按钮 中部logo图标 右侧导航菜单按钮&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13152 效果图如下&#xff1a; # cc-navHeader #### 使用方法 使用方法 在page.json设…

一种实现Spring动态数据源切换的方法 | 京东云技术团队

1 目标 不在现有查询代码逻辑上做任何改动&#xff0c;实现dao维度的数据源切换&#xff08;即表维度&#xff09; 2 使用场景 节约bdp的集群资源。接入新的宽表时&#xff0c;通常uat验证后就会停止集群释放资源&#xff0c;在对应的查询服务器uat环境时需要查询的是生产库…

实例讲解,一文弄懂workqueue和waitqueue

本期主题&#xff1a; 讲清workqueue和waitqueu&#xff1a; 从中断讲起waitqueue是什么workqueue总结 往期链接&#xff1a; linux设备驱动中的并发linux设备驱动中的编译乱序和执行乱序linux设备驱动之内核模块linux字符驱动linux字符驱动之ioctl部分linux字符驱动之read、…

分布式存储Ceph的部署及应用(创建MDS、RBD、RGW 接口)

系列文章目录 文章目录 系列文章目录一、1.存储基础2. 单机存储的问题3. 分布式存储&#xff08;软件定义的存储 SDS&#xff09; 二 Ceph1.Ceph 简介2. Ceph 数据的存储过程 总结 一、 1.存储基础 1.1 单机存储设备 ●DAS&#xff08;直接附加存储&#xff0c;是直接接到计算…

SAX解析XML返回对应格式的Map对象

前言 最近有一个解析大型xml的需求&#xff0c;xml大小7M&#xff0c;其中xml结构非常复杂&#xff0c;元素各种嵌套 不乏有元素下对象&#xff0c;元素下集合&#xff0c;集合下对象&#xff0c;集合下集合&#xff0c;兄弟不同元素节点&#xff0c;元素下对象下集合&#xff…

HDFS读写流程

读数据流程 客户端向NameNode请求文件的位置&#xff1a;客户端想要访问一个文件时&#xff0c;会向NameNode发送一个请求&#xff0c;要求获取该文件在HDFS上的位置信息。 NameNode将位置信息返回给客户端&#xff1a;NameNode接收到客户端的请求后&#xff0c;会返回该文件所…

设计模式—访问者模式

需求&#xff1a;店铺采购了一批水果&#xff08;苹果及橘子&#xff09;&#xff0c;现在市场监督局来店里检查过期的水果。 public class Fruit {private String name;private Date pickDate;public Fruit(String name, Date pickDate) {this.name name;this.pickDate pic…

javaWeb之cookiesession

1 回顾 1.1 response对象 一次响应封装对象&#xff0c;由服务器创建。使用response对象将服务器需要的数据发送给浏览器。 将数据存放response对象中&#xff0c;tomcat从response对象获得数据&#xff0c;根据数据组织http响应&#xff0c;最后将http响应内容发送给浏览器&…

selenium自动化教程及使用java来爬取数据

目录 一、介绍二、下载浏览器驱动1.获取要下载的驱动版本号2.下载驱动 三、Maven如下四、简单使用五、定位器1.定位器2.说明(1) class name 定位器(2) css selector 定位器(3) id 定位器(4) name 定位器(5) link text 定位器(6) partial link text 定位器(7) tag 定位器(8) xpa…