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

news2024/10/7 4:33:10

写在前面:

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

探究了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++入门铺垫的知识学的差不多了,准备要开始类和对象了。

写在最后:

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

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

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

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

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

相关文章

正交编码与正交沃尔什函数详解

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;https://github.com/timerring/information-theory 】或者公众号【AIShareLab】回复 信息论 获取。 文章目录 正交编码正交编码的…

Spring Boot 集成 WebSocket 实现服务端推送消息到客户端

假设有这样一个场景&#xff1a;服务端的资源经常在更新&#xff0c;客户端需要尽量及时地了解到这些更新发生后展示给用户&#xff0c;如果是 HTTP 1.1&#xff0c;通常会开启 ajax 请求询问服务端是否有更新&#xff0c;通过定时器反复轮询服务端响应的资源是否有更新。 在长…

css基础(二)

目录 1. CSS 的复合选择器 1.1 什么是复合选择器 1.2 后代选择器(重要&#xff09; 1.3 子选择器(重要&#xff09; 1.4 并集选择器(重要&#xff09; 1.5 伪类选择器 1.6 链接伪类选择器 1.7 :focus伪类选择器 1.8 复合选择器总结 二、 CSS 的元素显示模式 2.1什么是元素显示模…

多线程编程和并行计算的实例:期货交易及打车软件算法

多线程编程和并行计算的实例:期货交易及打车软件算法 解决现实生活中的问题时&#xff0c;多处理器和多核系统的普及使并行计算成为一个关键的性能提升手段。在这篇博客中&#xff0c;我们将通过深入讨论两个引人入胜而又具有实际意义的场景——期货交易和打车匹配算法&#xf…

CSS圆角进化论

CSS圆角发展过程 大致经历了3个阶段&#xff0c;包括&#xff1a; 背景图片实现圆角CSS2.0标签模拟圆角CSS3.0圆角属性&#xff08;border-radius属性)实现圆角 ☛背景图片实现圆角&#xff1a;使用背景图片实现圆角的方式很多&#xff0c;实现的方式和圆角的切图方式关系密…

AI绘图软件分享:Midjourney 基础教程(三)

大家好&#xff0c;我是权知星球&#xff0c;今天继续给大家分享Midjourney 基础教程&#xff08;三&#xff09;&#xff1a;Midjourney 图生图。 刚开始学习使⽤ AI 绘画时&#xff0c;⼤部分⼈的绘画⽅式&#xff1a; 有⼀个想象中的画⾯&#xff0c;⽤中⽂将这个画⾯描述…

【文件操作与IO】Java中如何操作文件

目录 Java 中操作文件 File 概述 属性 构造方法 方法 代码示例 文件内容的读写 —— 数据流 InputStream 概述 FileInputStream 概述 利用 Scanner 进行字符读取 OutputStream 概述 利用 OutputStreamWriter 进行字符写入 利用 PrintWriter 找到我们熟悉的方法 代码…

D. A Wide, Wide Graph(树的直径)

Problem - 1805D - Codeforces 给定一棵包含n个节点的树&#xff08;一个无环联通图&#xff09;&#xff0c;对于一个固定的整数k&#xff0c;定义Gk为一个具有n个节点的无向图&#xff0c;其中只有当在给定树中节点u和v之间的距离至少为k时才存在边。 对于从1到n的每个k&…

undefined reference to `tputs‘

昨天在Debian11 上编译 bluez 的时候&#xff0c;看这里&#xff0c;出现了如下这个错误&#xff0c;一般这种未定义的错误提示都是没有链接正确的库导致&#xff0c;但是搞了很久都没解决。 奇怪的是之前在 Centos 上编译却没有遇到这个问题&#xff0c;而且在 configure 时也…

做Java开发,真的“穷途末路”了吗?浅谈从2018-2023年,这行到底“卷”成了啥样

文章目录 一、火爆的行业1、裁员潮引发的行业惶恐2、国情下的行业现状3、时代的快速发展 二、Java开发“卷”成了啥样1、2013年2、2018年3、2013年4、真的需要这么多知识吗 三、大龄程序员何去何从引用来处 一、火爆的行业 “程序员”这个代名词&#xff0c;似乎总是跟“高薪”…

基于多进程并发-进程通讯之管道(pipe)

一、管道&#xff08;pipe&#xff09; 所谓的管道&#xff0c;就是内核⾥⾯的⼀串缓存&#xff08;Pipe&#xff09;。一个进程从管道的⼀端写⼊的数据&#xff0c;实际上是缓存在内核中的&#xff0c;另⼀端读取&#xff0c;也就是从内核中读取这段数据。 特性&#xff1a;…

windwos2016 由于没有远程桌面授权服务器可以提供许可证

一、问题&#xff1a; 经常会遇到&#xff0c;server2016、server2012、server2008操作系统&#xff0c;安装远程桌面服务之后没有激活&#xff0c;经过120天到期之后&#xff0c;没办法再使用&#xff0c;重新安装激活远程桌面服务也不能用。 二、具体的报错如下图&#xff…

归并排序详解-附Python代码

排序思路 将输入的列表递归分解成若干个有序的子列表&#xff08;只含有一个元素&#xff09;&#xff1b;将分解后的有序子列表两两归并成一个新的有序列表&#xff1b;重复步骤2&#xff0c;直到完成排序。 重点&#xff1a;如何定义一个归并函数&#xff0c;可以将两个有序…

Qt-自定义控件

Qt-自定义控件 简单使用 首先创建一个工程 在现有的工程上添加文件&#xff0c;选择Qt设计师界面类 选择Widget 添加两个控件之后&#xff0c;选择水平布局 将刚刚自定义的控件smallWidget放置在原始的控件中 首先在原始工程的ui界面 随便放置一个widget 选择&#xff…

我的内网渗透-代理转发(1)

概念 网关 必须经过 用来进行路由转发的设备&#xff0c;网关的作用是让不同网段之间能够通信 代理 委托访问 无论代理后面挂了几台设备&#xff0c;都认为是从代理进行访问&#xff0c;对外只表现为代理一台。外部认为是与代理进行…

计算机提示xinput1_3.dll丢失,三个详细修复方法

打开《绝地求生》游戏的时候&#xff0c;计算机提示xinput1_3.dll丢失&#xff0c;无法启动运行。重新安装一遍游戏依然无法启动运行。这个是由于xinput1_3.dll文件是属于电脑系统DirectX9.0的一个组件&#xff0c;用于提供输入和输出功能。它包含了各种接口和函数&#xff0c;…

spi控制器和spi设备的加载过程

spi控制器都是挂在platform总线上的&#xff0c;所以要等platform总线上的设备驱动加载spi控制器完成后才能加载spi设备。 1.spi控制器加载 由spi控制器驱动程序调用spi_register_master来完成spi控制器驱动加载 int spi_register_master(struct spi_master *master) { ... s…

【ubuntu20.04上构建qemu启动linux kernel】

参考Ubuntu环境下使用qemu搭建arm64运行环境 - 简书 一、交叉编译工具 sudo apt install gcc-aarch64-linux-gnu aarch64-linux-gnu-gcc -v 二、linux内核编译 git clone https://github.com/torvalds/linux.git cp arch/arm64/configs/virt.config .config make ARCHarm64 m…

并网逆变器杂记1-VO-DCC双环控制

NOTE1&#xff1a; 母线电压恒定的条件是&#xff1a; PV输出功率 &#xff0c;等于逆变侧消耗功率 假设&#xff1a;PV侧给母线输入10A &#xff0c;但是逆变侧消耗1A&#xff0c;此时母线Udc会升高 反之PV输入1A&#xff0c;消耗10A&#xff0c;母线Udc会降低。 NOTE2&#…

【IP地址】使用这个免费工具轻松获取地理位置

文章目录 前言一、Ip-API二、使用示例2.1、语言2.2、数据格式 三、简单示例四、更好的服务五、需要注意的点结尾 前言 今天分享一个免费的在线工具来查询IP地址所在的地理位置。可以通过IP地址所属的网络运营商和其他相关信息来确定设备的位置&#xff0c;包括国家、地区、城市…