C++入门学习(4)引用 (讲解拿指针比较)
上期回顾
在学习完函数重载之后,我们可以使用多个重名函数进行操作,会发现C++真的是弥补了好多C语言的不足之处,真的不禁感概一下,时代的进步是需要人去做出改变的,而不是一味的使用啊!所以我们今天继续学一下C++对C语言的指针的改变吧!
一、引用的诞生
在C语言中,指针的使用是很复杂的,涉及了二级指针,三级指针乃至我们很少见的多级指针,这会让我们使用起来很麻烦,程序的可读性很差,如果你不是一个功底很深的程序员,根本就要花上很长时间才会略知一二。
但是我们在C++中并不是摒弃了指针,而是发明了一个新的东西,在某些场合可以代替指针----引用!
二、引用的概念
那什么是引用呢?
我们语文中的引用是不是给某个东西起个别名,然后再用双引号引起来。在现实生活中,我们会有很多别名,比如李逵,在家叫“铁牛”,江湖人称“黑旋风”。这都是引用,所以我们C++中的引用也不例外,就是给一个变量起别名。
我们来一起思考一个问题,C++中的引用,会开辟一个新的内存空间吗?因为在C语言中指针会开辟空间的。
我们还是以现实生活为例,你有很多别名,那就有很多个你吗?肯定不是吧!
所以我们C++的引用,也是只有一个空间的,我们只是给变量起了个别名,但是引用是跟它引用的变量共用一块内存空间的。

概念:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
我们了解了概念之后,那引用是如何使用的呢?
三、引用的使用
数据类型& 引用变量名 = 引用实体
注意:引用的数据类型要和引用实体数据类型一致(不一致的情况我们放在常引用讲解)
#include <iostream>
using namespace std;
int main()
{int a = 3;int&a1 = a;cout << "a = "<< a << endl << "a1 = "<< a1 << endl;return 0;
}
看到下面的输出结果,也印证了引用是给变量取别名

四、引用的特性
4.1 引用必须初始化
如果我们写了这样的一段代码:是编译不过去的,会报错,正是因为引用没有初始化出现的错误。我们可以这样理解,引用实体都没有,哪里来的引用呢?就像一个人根本不存在,他就不可能有别名的。
#include <iostream>
using namespace std;
int main()
{int a = 3;int&a1;return 0;
}
4.2 引用的改变 会 改变引用实体
那如果是这样的代码呢?我们在此基础上➕了一行a1++,改变引用会改变引用实体吗?
#include <iostream>
using namespace std;
int main()
{int a = 3;int&a1 = a;a1++;cout << "a = "<< a << endl << "a1 = "<< a1 << endl;return 0;
}
答案是一定的,因为引用跟引用实体共用同一个空间,改变引用就是改变引用实体。

4.3 引用不改变指向,也不可以同时引用多个实体
那一个引用可以引用多个实体吗?或者可以改变引用指向的对象吗?因为我们C语言的指针可以改变指向,所以我们来探讨一下,比如下面这段代码:
#include <iostream>
using namespace std;
int main()
{int a = 3;int&a1 = a;int b = 5;a1 = b;cout << "a = "<< a << endl << "a1 = "<< a1 << endl;cout << "a的地址为:"<< &a << endl;cout << "a1的地址为:"<< &a1 << endl;cout << "b的地址为:"<< &b << endl;return 0;
}
我们可以看到,虽然把b的值赋给了a1,但是没有改变引用的指向,a1的地址还是跟a一样的,引用跟指针的区别之一也显现出来了,所以我们可以得出下面两个结论:
1. 引用不可以引用多个对象;
2. 引用不可以改变指向(指向的对象);

4.4 一个实体可以有多个引用
在日常生活中,你可以有很多个别名,所以在C++中,一个实体也可以有多个引用。
#include <iostream>
using namespace std;
int main()
{int a = 3;int& a1 = a;int& a2 = a;int& a3 = a;cout << "a = "<< a << endl;cout << "a1 = "<< a1 << endl;cout << "a2 = "<< a2 << endl;cout << "a3 = "<< a3 << endl;return 0;
}
因为一个实体可以有多个引用,所以这个输出就一定都会是3的。

我相信大家一定会有这样的疑问,我们上面所做的事情,C语言的指针也可以做呀,没什么特别的嘛,大家不要着急,继续看下去,引用使用的地方并不是在这里哦!
我们可以发现,目前的引用都是在引用变量,那是否可以引用常量或者是常变量呢?接下来是我们要讲的重点之一:
五、常引用
在学习之前,我们要先知道这个概念
权限:一般是指我们可以操作的范围,使用的范围,权限只可以平移和缩小,不可以放大
了解之后,我们就开始对常引用的学习吧!
那什么是常引用呢?就是对常量和常变量的引用,但是一定要记住权限,来看下面的代码:
#include <iostream>
using namespace std;
int main()
{const int a = 3;int& a1 = a;return 0;
}
这段代码可以正常运行吗?
我们先来分析一下,在程序中我们定义了一个常变量a,a本来是变量,但是被const修饰了,变得不可被修改,所以权限是 “只读”,然而我们用int类型的引用来去引用a是不可以的,因为我们的引用变量a1是int类型的,权限是“读和写”,可以被修改。所以这里的权限是被放大了,a1可以修改,a不可以修改,权限被放大,该程序错误
那怎么弄才是对的呢?还是得看权限只能平移和缩小,所以我们更改了代码:
#include <iostream>
using namespace std;
int main()
{const int a = 3;const int& a1 = a;return 0;
}
这段代码中,a是常变量,引用变量a1也是常变量,这是权限的平移,程序正确。
那有没有权限缩小的呢?当然有,来看下面代码:
#include <iostream>
using namespace std;
int main()
{int a = 3;const int& a1 = a;return 0;
}
这段代码就是a本来可以修改,是“读写”,然后我们的引用变量a1是➕const修饰的,权限是“只读”,这里就是权限的缩小,程序正确。
以上都是对常变量的引用,那如何来引用常量呢?
猜对了,还是靠权限来:
#include <iostream>
using namespace std;
int main()
{const int &a1 = 3;return 0;
}
我们的常量,也是不可被修改的,权限是“只读”,所以要引用常量的时候,要加上const,把权限也变成“只读”
六、引用和引用实体的数据类型不同
当我们了解了这些之后,我们要填一下上面埋的坑,当引用和引用实体的数据类型不一样的时候,如何引用呢?
我们要了解一个概念,当类型不同的时候,一定会发生类型转换,比如下面的代码:
#include <stdio.h>
int main()
{int a = 3;double b = a;printf("a = %d\nb = %lf",a,b);return 0;
}
我们先定义了一个整形变量a,再将a变量赋值给double类型的b,两个类型不同,会发生隐式类型转换,就是把a转换成double类型,再赋值给b,那我们的a的数据类型是什么呢?

从上面可以看出a还是整型,但是不是把a转换成double之后,再赋值给b的吗?怎么a还是int类型?我们带着这样的疑问,去学习一个新的知识:
凡是涉及到类型的转换,都会产生一个临时变量,存放转换的值。这个临时变量具有常属性
这也就解释了为什么a还是int类型。
我们用图解的方法来看一下:

我们在了解上面的概念之后,看下面的代码:
#include <iostream>
using namespace std;
int main()
{int a = 3;const double& b = a;return 0;
}
我们定义了一个int类型的变量a,用double类型的引用变量去引用a,因为类型不同,会发生类型转换,但本质是先创建一个double类型的中间变量,再把a的值转换到这个中间变量中,由这个中间变量赋值给我们的b,为什么要➕const,因为这个中间变量具有常属性。
七、引用的使用场景
其实在上面我们仅仅是在介绍引用该怎么使用,没有介绍引用都用在哪里,所以接下来我们来学习引用使用场景。
7.1 做函数参数
我们在C语言中写一个交换函数,是需要传地址的,也就是用到一级指针:
void Swap(int* x,int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
而在C++中,我们可以用引用来解决这个问题:
因为引用就是引用实体的别名,所以改变引用就是改变引用实体。
void Swap(int& x,int& y)
{int tmp = x;x = y;y = tmp;
}
7.2 做返回值
第二个用途就是用引用当返回值
但是有一个条件:函数返用引用作为返回值的时候,返回值不可以被销毁
下面的例子主要是讲解,我们用引用做返回值的时候,返回值不可以被销毁。
我们先看这样的一段代码:
我们是定义了一个函数Count,里面实现的是简单的n++,n最后是1,然后将n的值返回,可是n是函数里面创建的一个临时变量啊,临时变量出了作用域就销毁了
而我们函数的返回值是n的引用,引用实体销毁了,引用还能正常使用吗?肯定不能的对吧,但是这里要分两种情况,ret可能是1,也可能是随机值,这主要看编辑器销毁的速度。
int& Count()
{int n = 0;n++;// ...return n;
}
using namespace std;
int main()
{int& ret = Count();cout << ret << endl;return 0;
}

如果我们是下面的代码呢?打印出来的结果又是什么呢?
很神奇吧,一个是3可以理解,有可能是z没有被销毁,但是后面怎么是10了呢?
这是因为我们函数栈帧是可以复用的,所以我们的那个空间本来是3,后来虽然被销毁了,又被征用了,变成10了。而我们的引用还是指向那个位置的,所以就变成10了,但是也可能是随机值。
int& Add(int x,int y)
{int z = x + y;return z;
}
using namespace std;
int main()
{int& ret = Add(1,2);cout << ret << endl;Add(3,7);cout << ret << endl;return 0;
}

但是有没有什么办法来解决这样的问题呢?也就是保证返回值不被销毁。
我们可以定义一个静态变量就可以了,因为静态变量是在堆上开辟的,函数的销毁不会影响静态变量。
需要注意的一点就是:局部的静态变量只可以初始化一次
多次进入这个函数,只会执行一次初始化,剩下的都跳过
这样我们就可以正确的返回z的引用了,
int& Add(int x,int y)
{static int z = x + y;return z;
}
using namespace std;
int main()
{int& ret = Add(1,2);cout << ret << endl;return 0;
}
所以我们的我们函数返回值要是引用的话,返回值不呢被销毁
八、传值和传引用的效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
8.1 函数参数 用 引用和值 的效率比较
#include <time.h>
#include <iostream>
using namespace std;
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.1 函数返回值用引用和值的效率比较
#include <iostream>
#include <time.h>
using namespace std;
struct A
{int a[10000];
};A a;// 值返回
A TestFunc1()
{ return a;
}// 引用返回
A& TestFunc2()
{return a;
}void TestReturnByRefOrValue()
{// 以值作为函数的返回值类型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作为函数的返回值类型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{TestReturnByRefOrValue();return 0;
}

当数据过大的时候,函数返回值用引用的效率更快
通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。这就是引用的优势,不需要开辟临时空间。
九、引用和指针的区别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
10. 空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象
相关文章:
C++入门学习(4)引用 (讲解拿指针比较)
上期回顾 在学习完函数重载之后,我们可以使用多个重名函数进行操作,会发现C真的是弥补了好多C语言的不足之处,真的不禁感概一下,时代的进步是需要人去做出改变的,而不是一味的使用啊!所以我们今天继续学一下…...
温度采集DS18B20
/******************************************************************** * 描述 : 该文件实现了用温度传感器件DS18B20对温度的采集,并在数码管上显示出来。 ***********************************************************************/ #include<reg52.h&…...
同城跑腿服务预约小程序的作用如何
无论是互联网服务化加快还是前几年疫情冲击,在同城生活服务场景中出现了很多商机,如外卖跑腿、校园跑腿、代买代送等,无论公司还是个人都借势不断提升自己品牌的影响力,并且依赖朋友圈不断提升生意营收。 同城跑腿品牌不少&#…...
前后端开发迭代
要创建一个具有登录和注册功能的前端网页,并使用Go语言编写后端来支持它,你需要分两部分来进行:前端开发和后端开发。下面我将提供一个基本的指导方案。 前端开发 前端部分主要涉及HTML、CSS和JavaScript。你可以使用框架如React或Vue来简化…...
Git可视化界面的操作,SSH协议的以及IDEA集成Git
目录 一. Git可视化界面的操作 二. gitee的ssh key 2.1 SSH协议 2.2 ssh key 三. IDEA集成Git 3.1 分享项目 3.2 下载项目 一. Git可视化界面的操作 上一篇博客只用到了git的命令窗口,现在就来看看可视化窗口要怎么操作。 点击Git GUI Here GUI界面 在g…...
CSS的初步学习
CSS 层叠样式表 (Cascading Style Sheets). CSS 能够对网页中元素位置的排版进行像素级精确控制, 实现美化页面的效果. 能够做到页面的样式和结 构分离. CSS 就是 “东方四大邪术” 之化妆术 CSS 基本语法规范: 选择器 若干属性声明 选择器决定针对谁修改 (找谁) 声明决定修…...
小程序样式问题
小程序切换下一个文章或者页面,淡入淡出效果 // detail.js getArticleData: function(articleId) {// 开始淡出效果this.animate(.detail-page, [{ opacity: 1.0, ease: ease-out },{ opacity: 0.0, ease: ease-out }], 500, () > {// 在淡出动画完成后请求文章…...
大数据毕业设计选题推荐-生产大数据平台-Hadoop-Spark-Hive
✨作者主页:IT毕设梦工厂✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…...
什么是Ribbon的饥饿加载?有什么优势?
目录 一、什么是Ribbon 二、什么是饥饿加载 三、Ribbon饥饿加载的优势 四、Ribbon饥饿加载的劣势 一、什么是Ribbon Ribbon是一个开源的、基于HTTP和TCP的客户端负载均衡工具,它提供了一个简单的、基于配置的负载均衡策略,可以帮助开发人员更轻松地…...
苹果手机安装未上架APP应用测试教程
STEP 2:找到下载的描述文件(如果没有找到,请到 设置 - 通用 - 描述文件 中查看) STEP 3:安装描述文件 STEP 4:输入解锁密码安装描述文件 STEP 5:同意免责声明,安装描述文件 STEP 6…...
asp.net core自定义授权过滤器
1.记录点击页面的地址,如果点击页面时未登录,直接跳转到登录页面,登录后直接跳转到自己点击的页面 2.创建授权过滤器 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System.Web;namespace Log4NetTest {/…...
SW如何显示样条曲线的控标
刚刚学习隔壁老王的sw画图时,怎么点都点不出样条曲线的控标,于是果断查询了一下解决方法,其实很简单,只不过是培训机构故意不说,叫你还解决不了,难受了就会花钱买他们的课了。毕竟如果学会了怎么解决问题了…...
CSS 的 link 标签放在 head 标签之间的作用
当我们在开发网页时,经常会遇到关于CSS文件引入位置的讨论。有些人喜欢将CSS文件的链接放在HTML文件的底部,而另一些人则认为最好将CSS链接放在<head>标签之间。那么,究竟应该将CSS的link标签放在head标签之间的原因是什么呢࿱…...
【VS2019 Qt5 VTK9.2】临时解决配置相关问题的简单方法
配置报错 编译报错提示(LNK2019或LNK2001) 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2019 无法解析的外部符号 “__declspec(dllimport) public: __cdecl QVTKOpenGLNativeWidget::QVTKOpenGLNativeWidget(class QWidget *,class QFlags)(_i…...
国际版腾讯云/阿里云:云解析DNS是什么
云解析DNS是什么 问答 云解析DNS是一种安全、快速、安稳、牢靠的威望DNS解析处理服务。 云解析DNS为企业和开发者将易于处理辨认的域名转换为计算机用于互连通讯的数字IP地址,然后将用户的拜访路由到相应的网站或应用服务器。 云解析 DNS(Domain Nam…...
深度学习之基于Django+Tensorflow商品识别管理系统
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 项目简介 本系统是一个基于DjangoTensorflow的商品识别管理系统。通过深度学习技术,实现商品的自动识别…...
postman 参数化使用csv导入外部数据
一、参数化脚本入参 postman中变量用{{变量名}}表示变量 二、创建外部数据文件 csv文件逗号分割多个变量和对应值注意编码格式必须为utf-8 三、run collection导入数据文件 四、设置运行参数run 浏览数据 可调试设置迭代次数:防止批量出错,可先设定…...
C语言概述
目录 编辑 1. C语言发展史 2. C语言特点 3. C语言标准 4. C语言编程机制 4.1 预处理(Preprocessing) 4.2 编译(Compilation) 4.3 汇编(Assemble) 4.4 链接(Linking) 结语 1. C语言发展史 C语言是由美国贝尔实验室的Dennis Ritchie于1972年设计开发的一种编…...
elasticsearch分词器
内置分词器 Standard Analyzer - 默认分词器,英文按单词词切分,并小写处理Simple Analyzer - 按照单词切分(符号被过滤), 小写处理Stop Analyzer - 小写处理,停用词过滤(the,a,is)Whitespace Analyzer - 按照空格切分,不转小写Ke…...
# 深度解析 Socket 与 WebSocket:原理、区别与应用
在网络通信领域,Socket和WebSocket都是关键的技术,但它们背后的原理和应用有着显著的差异。本文将深入剖析Socket与WebSocket的工作原理,突出它们之间的区别,并探讨它们在不同场景下的应用。 1. Socket 的基础 1.1 什么是 Socke…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
