【C++干货基地】C++引用与指针的区别:深入理解两者特性及选择正确应用场景

news2024/11/13 9:06:35

在这里插入图片描述

🎬 鸽芷咕:个人主页

 🔥 个人专栏: 《C++干货基地》《粉丝福利》

⛺️生活的理想,就是为了理想的生活!

引入

  哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作为一门篇底层的一种语言,世面的免费课程大多都没有教明白。所以本篇专栏的内容全是干货让大家从底层了解C++,把更多的知识由抽象到简单通俗易懂。

⛳️ 推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

文章目录

  • 引入
  • ⛳️ 推荐
  • 一、引用的概念
    • 1.1 引用的语法
  • 二、引用的特性
    • 2.1 引用必须初始化
    • 2.2 引用不能更改指向
    • 2.3 一个变量可以有多个指向
  • 三、常引用
    • 3.1 权限的放大与缩小
    • 3.2 临时变量具有常性
  • 四、引用的使用场景
      • 做参数
      • 做返回值
    • 4.2 传值和传引用的效率对比
  • 五、引用和指针的区别
    • 5.1 引用与指针的大小
    • 5.2 引用和指针的底层对比
    • 总结
  • 📝文章结语:

一、引用的概念

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

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

在这里插入图片描述
所以,铁牛黑旋风,都是 李逵

  • 这俩就相当于李逵的别名

引用的概念其实有点相当于指针的平替,以往我们在使用指针等要 取地址 解引用 太麻烦了,所以C++祖师爷在开发C++的时候就有了引用的概念,下面就来看看引用到底是个什么东西吧!

1.1 引用的语法

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

以上就是引用的语法了下面我们就来看一下实际是如何使用的

🍸 代码演示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

int main()
{
	int a = 10;
	//给a取别名
	int& b = a;
	b = 20;

	cout << b << endl;
	return 0;
}

📑代码结果:
在这里插入图片描述

这时就有很多人说了这引用不就相当于指针吗?对的引用和指针的作用其实是差不多的,对变量引用的修改会影响变量,而指针也是对指针的修改会影响指针所指向的内容:

  • 但是引用在使用上和一些场景比指针更简便更容易理解

🍸 代码演示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

void fun(int* x)
{
	*x = 20;
}

void fun1(int& y)
{
	y = 30;
}
int main()
{
	int a = 10;
	//使用指针做形参
	fun(&a);
	cout << a << endl;
	//使用引用做形参
	fun1(a);
	cout << a << endl;
	return 0;
}

哦豁,这里我们就可以看到引用的奇妙之处了。以往我们用指针做参数的时候老是忘记去地址传参,而引用本身就是变量的别名所以,在当形参的时候我们只需要传变量就好了

  • 而在修改变量值的时候指针还要解引用才能修改
  • 而引用却可以直接修改

现在看来引用和指针对比,简直就是一个还在使用老年机一个却已经使用智能手机全自动了,别急引用的好处还在后面呢大家慢慢看完,我们在以后的项目里面可以说%80的地方都不需要指针而用引用了。

二、引用的特性

2.1 引用必须初始化

以往我们在指针定义时候老是忘记初始化而到处野指针的情况频频发生,所以祖师爷在定义引用的时候规定了引用必须初始化

🍸 代码演示:


int main()
{
	int a = 10;
	int& b = a;
	int& c;
	return 0;
}

📑代码结果:

在这里插入图片描述

2.2 引用不能更改指向

指针我们都知道是可以更改指向的,但是引用祖师爷规定了引用不能更改指向。因为C++是兼容 C语言祖师爷可能认为更改指向的事情交给 指针 做就可以了,没必要去让引用去更改指向;

🍸 代码演示:

int main()
{
	int a = 10;
	int x = 30;
	int& b = a;
	cout << b << endl;

	b = x;//不是改变指向而是赋值
	cout << b << endl;
	cout << a << endl;
	return 0;
}

📑代码结果:
在这里插入图片描述

2.3 一个变量可以有多个指向

🍸 代码演示:

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

	//给别名起别名
	int& c = b;
	c = 30; 
	cout << a << endl;
	return 0;
}

📑代码结果:
在这里插入图片描述

三、常引用

这里为什么会有常引用的概念呢?引用和指针一样都会去改变所指向的变量从而造成失误。而这时使用常引用就不会了

🍸 代码演示:

void fun(const int& x)
{
	x = 30;
}
int main()
{
	int a = 10;
	fun(a);
	return 0;
}

📑代码结果:
在这里插入图片描述

3.1 权限的放大与缩小

  • 权限的缩小

🍸 代码演示:

int main()
{
	int a = 10;
	//权限的缩小
	const int& b = a;
	b = 30;

	return 0;
}

📑代码结果:

在这里插入图片描述

这里我们就把变量 a 的别名 b 的权限缩小了,从可读可写变成了可读,所以我们就不能就行修改了

  • 权限的平移

🍸 代码演示:

int main()
{
	const int a = 8;
	//权限的平移
	const int& c = a;
	const int& b = 10;
	return 0;
}

平移很简单就是相同权限的变量我们就给他相同权限的别名才能使用

  • 权限的放大

在我们变成中其实权限是不能放大的,一个常量如果强行把它提升成变量是非常不安全

  • 所以权限是不能放大的

在这里插入图片描述

3.2 临时变量具有常性

这里给大家看一个代码,这里为什么int 类型可也转换为double double 却转换不了为int 引用?

在这里插入图片描述
这是因为 当我们进行赋值,或者进行隐式类型转换的时候,这里会产生一个临时变量,而临时变量具有常性

  • 是不可进行,改变和隐式类型转换的

在这里插入图片描述

  • 这是我们对其 临时变量的常性,进行权限的平移就会进行报错了
int main()
{
	int a = 10;
	double b = a;
	const int& x = b;
	return 0;
}      

那么为什么会产生临时变量?下面看一下这段代码,这里我们进行判断比较时是不会进行提升?

  • 那么这里是对变量本身进行提升吗?
int main()
{
	int a = 97;
	char b = 'a';

	if (a == b)
	{
		cout << "a == b" << endl;
	}
}    

这里是对临时变量进行对比然后提升进行对比的

  • 权限放大案例
    在这里插入图片描述
int main()
{
	int a = 10;
	int b = 20;
	//权限的放大,a+b的结果是一个临时变量。临时变量具有常性
	//int& b = a + b;
	const int& b = a + b;

	return 0;
}      

四、引用的使用场景

看到这里其实大家,都知道指针和引用的功能大致相同

  • 但是 C++ 的引用是为了替换掉一下指针复杂场景的替换使代码,使代码更加简介但是引用不能代替指针
  • 他们更多的是相辅相成

做参数

引用的更多使用场景就是传参来用的,以往我们在使用指针更改指针指向的变量,或者二级指针使用起来太不方便了,但是使用引用就非常简单:

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;
}

以往我们在进行交换函数的使用每次都需要,取地址进行传参,而有了引用的概念用起来就方便多了

做返回值

📑 错误示范:

int& fun()
{
	int a = 10;
	return a;
}

int& fx()
{
	int b = 20;
	return b;
}

int main()
{
	int& a = fun();
	cout << a << endl;

	fx();
	cout << a << endl;
	return 0;
}

这里我们就错误的使用引用做返回值的,我们吧函数 fun 里面本来要销毁的变量给使用别名返回了。但是这个快空间本来是要还给操作系统的:

  • 这样我们就造成了内存泄漏
  • 当我们在进行调用函数时会对上一个销毁的函数空间进行复用,所以就把原来的空间a给改变了

在这里插入图片描述

🔥 所以使用引用做返回值的时候一定是对在堆上开辟,或者动态开辟的空间不会随着函数销毁而销毁的空间才可以用引用做返回值

  • 如果不是动态开辟的空间或者再堆上开辟的空间,会随着函数的销毁而销毁就一定要用传值传参

这里在顺序表里面如果把 Get 获取函数指定位置的值进行传引用返回的话就可以把修改循序表的的 Modity 给干掉了

  • 一个函数既可以获取值又可以修改值

struct SeqList
{
	int* a;
	int size;
	int capacity;

	//成员函数
	void Init(SeqList& sl)
	{
		int* tmp = (int*)malloc(sizeof(int) * 4);
		if (tmp == NULL)
		{
			perror("malloc file");
			exit(-1);
		}
		sl.a = tmp;
		sl.size = 0;
		sl.capacity = 4;
	}

	void PushBack(SeqList& sl,int x)
	{
		//...
		sl.a[size++] = x;
	}

	int& Get(SeqList& sl, int pos)
	{
		return sl.a[pos];
	}
};
int main()
{
	SeqList s;
	s.Init(s);
	s.PushBack(s, 1);
	s.PushBack(s, 2);
	s.PushBack(s, 3);
	s.PushBack(s, 4);

	for (int i = 0; i < s.size; i++)
	{
		cout << s.Get(s, i) << " ";
	}
	cout << endl;

	for (int i = 0; i < s.size; i++)
	{
		s.Get(s, i) *= 2;
		cout << s.Get(s, i) << " ";
	}
	cout << endl;
	
	cout << endl;
	return 0;
}

4.2 传值和传引用的效率对比

函数在进行传值做形参的话,形参是实参的一份临时拷贝。所以对系统的空间是有一定消耗的,当我们调用函数次数多的话就在效率上就会有一定影响,从而降低效率:

  • 下面我们就来测试一下传值调用和传引用调用的效率吧!
#include <time.h>
struct A { int a[10000]; };
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;
}

🔥 在这里我们调用一万次他们的差别分别是 8毫秒多 和 0毫秒

在这里插入图片描述

五、引用和指针的区别

5.1 引用与指针的大小

🍸 代码演示:

int main()
{
	int a = 10;
	
	int& b = a;
	int* c = &a;
	cout << "引用大小:" << sizeof(a) << endl;
	cout << "指针大小:" << sizeof(c) << endl;

	cout << "int大小:" << sizeof(int) << endl;
	cout << "longlong大小:" << sizeof(long long) << endl;

	cout << "引用大小:" << sizeof(long long&) << endl;
	cout << "指针大小:" << sizeof(long long*) << endl;
	return 0;
}

📑代码结果:
在这里插入图片描述
🔥 在sizeof中:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

5.2 引用和指针的底层对比

这边我们定义一个指针和引用,然后我们把它转成汇编代码会发现他们来生成的汇编代码是一样的

  • 🔥 所以在底层,引用和指针都是一回事,引用是按照指针方式来实现的。

在这里插入图片描述

总结

引用和指针的底层对比

  1. 在语法上引用是给一个变量起别名,不开空间。指针是把一个变量的地址存起来。

  2. 引用必须初始化才能使用,指针可以初始化也可以不初始化

  3. 引用不可以改变指向,但指针可以改变指向

  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
    位平台下占4个字节)

  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  6. 有多级指针,但是没有多级引用

  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  8. 引用比指针使用起来相对更安全

底层上:

🔥 在底层上引用是开辟空间的,因为引用是用指针实现的

📝文章结语:

☁️ 看到这里了还不给博主扣个:
⛳️ 点赞🍹收藏 ⭐️ 关注
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!

在这里插入图片描述

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

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

相关文章

go并发编程-runtime、Channel与Goroutine

1. runtime包 1.1.1. runtime.Gosched() 让出CPU时间片&#xff0c;重新等待安排任务(大概意思就是本来计划的好好的周末出去烧烤&#xff0c;但是你妈让你去相亲,两种情况第一就是你相亲速度非常快&#xff0c;见面就黄不耽误你继续烧烤&#xff0c;第二种情况就是你相亲速度…

C#,入门教程(36)——尝试(try)捕捉(catch)不同异常(Exception)的点滴知识与源代码

上一篇&#xff1a; C#&#xff0c;入门教程(35)——哈希表&#xff08;Hashtable&#xff09;的基础知识与用法https://blog.csdn.net/beijinghorn/article/details/124236243 1、try catch 错误机制 Try-catch 语句包含一个后接一个或多个 catch 子句的 try 块&#xff0c;这…

项目:博客

1. 运行环境&#xff1a; 主机 主机名 系统 服务 192.168.223.129 Server_Web Linux Web 192.168.48.131 Server-NFS-DNS Linux NFS/DNS 2. 基础配置 配置主机名&#xff0c;静态IP地址 开启防火墙并配置 部分开启SElinux并配置 服务器之间使用同ntp.aliyun.com进行…

prometheus和alertmanager inhibit_rules抑制的使用

172.16.10.21 prometheus 172.16.10.33 altermanager 172.16.10.59 mysql服务&#xff0c;node探针以及mysql的探针 [rootk8s-node02 ~]# docker ps -a CONTAINER ID IMAGE …

SpringBoot+BCrypt算法加密

BCrypt是一种密码哈希函数&#xff0c;BCrypt算法使用“盐”来加密密码&#xff0c;这是一种随机生成的字符串&#xff0c;可以在密码加密过程中使用&#xff0c;以确保每次加密结果都不同。盐的使用增强了安全性&#xff0c;因为攻击者需要花费更多的时间来破解密码。 下图为…

深度剖析Sentinel热点规则

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 深度剖析Sentinel热点规则 前言核心概念解析&#xff1a;数字守护者的起源核心概念解析&#xff1a;简单示例演示&#xff1a; 参数索引&#xff1a;规则的基石参数索引的作用&#xff1a;不同场景下选…

数学建模-灰色预测模型

灰色预测练习解答 x(0){183,189,207,234,220,256,270,285}; X(1){183,372,579,813,1033,1289,1559,1844}; Matlab操作程序: x0[183,189,207,234,220,256,270,285];>> format long; %(表示设计精度)>> nlength(x0); %&#xff08;输入数据长度&#xff09;>&g…

在 python 中调用 C/C++

Python 是一种很好用的胶水语言&#xff0c;利用Python的简洁和C的高效&#xff0c;基本可以解决99%的问题了&#xff0c;剩下那 1% 的问题也就不是问题了&#xff0c;毕竟不是所有问题都可解。 一般的&#xff0c;Python和C的交互分为这两种情况&#xff1a; 用C扩展Python&…

标准化编程系列(常用模式状态介绍)

任何事情任何编程都是有方法可循的,我们所要做的工作就是在看似没有规律的运行中,发现规律总结一般性的方法。这篇博客主要介绍标准化编程相关的基础知识,编程化编程离不开大家扎实的编程基本功,所以在学习标准化的同时,大家需要提升对于子程序,模块FB 、FC等的应用知识,…

Windows11通过Hyper-V创建VM,然后通过vscode连接vm进行开发

这边需要在win11上建立vm来部署docker(这边不能用windows版本的docker destop)&#xff0c;学习了下&#xff0c;记录。 下载系统镜像 首先下载系统镜像&#xff1a;https://releases.ubuntu.com/focal/ 这边使用的是ubuntu20.04.6 LTS (Focal Fossa) &#xff0c;Server inst…

D4800——AB类立体声耳机放大芯片, 输出电压振幅大,电源抑制比好且低功耗, 工作温度范围宽 无开关噪声

D4800是一块AB类立体声耳机音频功率放大器电路。D480在5V电源时输出功率最高可290mW(89负裁失真度1090.适合在便携式数字音响设备中作功率放大用。 主要特点&#xff1a; ● 电源电压:单电源: 2V to 7V 双电源:1.0V to3.5V ● 高信噪比: 100dB DIP8. ● 转速快: 5V/us ● 失…

【乳腺肿瘤诊断分类及预测】基于PNN概率神经网络

课题名称&#xff1a;基于PNN的乳腺肿瘤诊断分类及预测 版本日期&#xff1a;2023-06-15 运行方式: 直接运行PNN0501.m 文件即可 代码获取方式&#xff1a;私信博主或QQ&#xff1a;491052175 模型描述&#xff1a; 威斯康辛大学医学院经过多年的收集和整理&#xff0c;建…

关于Spring框架的 @Configuration 与@Service 加载顺序哪个先后(某些环境加载是随机的)

很多资料都说Configuration 优先加载&#xff0c;Service后加载&#xff0c;如下图&#xff1a; 本来也是以为 Configuration 优先加载于 Service &#xff0c;那参数处理放在Configuration注入完后&#xff0c;service构建时就可以拿来用的&#xff0c;在我在IDEA的调试时下断…

【蓝桥杯日记】复盘篇三——循环结构

前言 本篇内容是对循环结构进行复盘的&#xff0c;循环可谓是在基础阶段特别重要的东西&#xff0c;是三大结构&#xff08;顺序结构、选择结构、循环结构&#xff09;中最重要的结构之一。 目录 &#x1f351;1.找最小值 分析&#xff1a; 知识点&#xff1a; 代码如下 &…

【C/Python】Gtk部件ListStore的使用

一、C语言 在GTK中&#xff0c;Gtk.ListStore是一个实现了Gtk.TreeModel接口的存储模型&#xff0c;用于在如Gtk.TreeView这样的控件中存储数据。以下是一个简单的使用Gtk.ListStore的C语言示例&#xff0c;该示例创建了一个列表&#xff0c;并在图形界面中显示&#xff1a; …

PostGIS教程学习二十二:使用触发器追踪历史编辑操作

PostGIS教程学习二十二&#xff1a;使用触发器追踪历史编辑操作 生产环境下数据库的一个常见要求是能够跟踪用户编辑数据的历史&#xff1a;数据在两个日期之间是如何变化的&#xff0c;是谁操作的&#xff0c;以及它们哪些内容变化了&#xff1f;一些GIS系统通过在客户端接口…

单片机学习笔记--- 定时器/计数器(简述版!)

目录 定时器的介绍 定时计数器的定时原理 定时计数器的内部结构 两种控制寄存器 &#xff08;1&#xff09;工作方式寄存器TMOD &#xff08;2&#xff09;控制寄存器TCON 定时计数器的工作方式 方式0 方式1 方式2 方式3 定时器的配置步骤 第一步&#xff0c;对…

go语言socket编程

1.互联网分层模型 过程分析&#xff1a; 2.Socket图解 Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议族隐藏在Socket后面&#xff0c;对用户来说只需要调用Socket规定的相关函数&a…

【笔记】React-Native跟Android交互--简单示例

/** * 使用命令 npx react-nativelatest init DemoRN创建项目 * * "react": "18.2.0", * "react-native": "0.73.2" * * 官网有详细教程&#xff1a;https://reactnative.dev/docs/native-modules-android */ 一、RN invoke androi…

【Oracle云】使用 boto3 访问 OCI 对象存储 (AWS S3协议兼容)

在现代云计算环境中&#xff0c;S3&#xff08;Simple Storage Service&#xff09;协议已经成为云对象存储的事实标准。它提供了简单、可扩展、高度耐用的存储解决方案&#xff0c;得到了广泛应用。Oracle Cloud Infrastructure&#xff08;OCI&#xff09;秉承着开放性和灵活…