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. 空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅析三维模型重建的地面控制点精度常见的几个问题及解决方法

浅析三维模型重建的地面控制点精度常见的几个问题及解决方法 在倾斜摄影三维模型重建过程中&#xff0c;地面控制点的精度是影响模型几何精度的关键因素之一。以下是常见的问题及相应的解决方法&#xff1a; 1、问题&#xff1a;地面控制点坐标测量误差较大。 解决方法&#…

《golang设计模式》第三部分·行为型模式-05-仲裁者/中介模式(Mediator)

文章目录 1. 概述1.1 作用1.2 角色1.3 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 仲裁者&#xff08;Mediator&#xff09;可以封装和协调多个对象之间的耦合交互行为&#xff0c;以减弱这些对象之间的耦合关联。 1.1 作用 将多个对象相互耦合的设计转变为所有对象…

【OpenCV实现图像:图像处理技巧之空间滤波】

文章目录 概要导入库空间过滤器模板展示效果分析与总结 概要 空间滤波器是数字图像处理中的基本工具之一。它通过在图像的每个像素位置上应用一个特定的滤波模板&#xff0c;根据该位置周围的相邻像素值进行加权操作&#xff0c;从而修改该像素的值。这种加权操作能够突出或模…

非线性【SVM】的创建和使用

先来绘制散点图&#xff1a; from sklearn.datasets import make_circles X,y make_circles(100, factor0.1, noise.1) # 100个样本&#xff0c;factor:内圈和外圈的距离之比&#xff0c;noise:噪声 X.shape y.shape plt.scatter(X[:,0],X[:,1],cy,s50,cmap"rainbow&qu…

数据库SQL

数据库&SQL 数据库基本概念数据库DataBase定义 数据库管理系统(DBMS)定义在JAVA项目中与数据库的结合数据库管理系统中常见的概念库与表的关系 SQL数据类型数字类型浮点类型字符类型TEXT类型日期类型 SQL语言的分类DDL:数据定义语言修改表结构的注意事项 DML:数据操作语言D…

uni-app:js实现数组中的相关处理-数组复制

一、slice方法-浅拷贝 使用分析 创建一个原数组的浅拷贝&#xff0c;对新数组的修改不会影响到原数组slice() 方法创建了一个原数组的浅拷贝&#xff0c;这意味着新数组和原数组中的对象引用是相同的。因此&#xff0c;当你修改新数组中的对象时&#xff0c;原数组中相应位置的…

PDF Expert for mac(苹果电脑专业pdf编辑器)兼容12系统

PDF Expert是macOS平台上的一款优秀的PDF阅读和编辑工具&#xff0c;由Readdle公司开发。它不仅拥有方便、易用的界面&#xff0c;还具备诸多功能&#xff0c;比如编辑PDF文件、添加批注、填写表格、签署文件、合并文档等。安装:PDF Expert for Mac(PDF编辑阅读转换器)v3.5.2中…

HT6819 3.3W 防削顶低EMI立体声 D类音频功率放大器

HT6819的防削顶失真功能可检测并抑Z由于音乐、语Y信号幅度过大或电池电压下降所引起的输出削顶失真&#xff08;破音&#xff09;&#xff0c;显著提高音质&#xff0c;创造舒适的听音享受&#xff0c;并保护扬声器免受过载损坏。通过在ACRC端外接不同电阻电容值,可灵活设置放大…

C++引用和指针的区别

C引用和指针的区别 引用是一种更加安全的指针 1、引用必须初始化&#xff0c;指针可以不初始化&#xff1b; 2、由下图可以看出&#xff0c;定义一个指针和引用在汇编阶段是一模一样的&#xff1b; 通过引用变量修改所引用的内存的值和通过指针解引用修改指针指向内存的值&…

CAS200 CLS216 基于图形用户界面的快速应用程序开发

CAS200 CLS216 基于图形用户界面的快速应用程序开发 最新的Sapera Vision软件套件包括萨佩拉加工和新的星形胶质细胞铥人工智能(AI)的图形应用。该软件套件提供经过现场验证的图像处理和人工智能功能&#xff0c;用于设计、开发和部署高性能机器视觉应用。 这个最新版本的Sape…

chrome driver下载、selenium安装及报错解决

目录 一、Chrome驱动下载 1.查看Chrome版本 2.下载驱动 3.驱动的路径 无法运行驱动 二、selenium的安装与使用 1.安装selenium 2.使用selenium 参考 一、Chrome驱动下载 1.查看Chrome版本 打开Chrome浏览器&#xff0c;点击右上角的三个点&#xff0c;再点击设置。 …

07-MySQL-进阶-锁InnoDB引擎MySQL管理

涉及资料 链接&#xff1a;https://pan.baidu.com/s/1M1oXN_pH3RGADx90ZFbfLQ?pwdCoke 提取码&#xff1a;Coke 一、锁 ①&#xff1a;概述 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xf…

详解机器学习最优化算法

前言 对于几乎所有机器学习算法&#xff0c;无论是有监督学习、无监督学习&#xff0c;还是强化学习&#xff0c;最后一般都归结为求解最优化问题。因此&#xff0c;最优化方法在机器学习算法的推导与实现中占据中心地位。在这篇文章中&#xff0c;小编将对机器学习中所使用的…

SpringCloud——服务注册——Eureka

1.Eureka概述 2.Eureka架构&#xff1a; Eureka中80服务要实现对8001和8002服务访问的负载均衡&#xff0c;需要在80服务的RestTemplate上面加LoadBalanced注解&#xff0c;默认采用的是轮询的策略。 3.Eureka自我保护 当一个EurekaClient注册进EurekaServer&#xff0c;Eurek…

Pycharm-community-2021版安装和配置

一、下载Pycharm-community-2021 1.从官网下载pycharm-community Pycharm 版本官网 二、安装PyCharm 1.打开下载完成的安装包&#xff0c;点击Next 2.安装PyCharm到其他位置,点击Next 3.一定把更新PATH变量勾上,可以创建桌面快捷方式&#xff0c;创建关联&#xff0c;最后…

卓越进行时 | 赛宁助力职业院校实践“岗课赛证训创”育人模式

11月7日&#xff0c;赛宁网安邀请南京城市职业学院网络安全学科师生走进网络安全卓越中心&#xff0c;为大一新生提供“职业导学” 和“岗位认知”的综合性实践课程&#xff0c;帮助学生提升对于网络安全专业和未来职业的认知水平&#xff0c;进一步深化校企合作人才培养的持续…

基于SSM的高校疫情防控出入信息管理系统(有报告)。Javaee项目。

演示视频&#xff1a; 基于SSM的高校疫情防控出入信息管理系统&#xff08;有报告&#xff09;。Javaee项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项目介绍&#xff1a; 采…

CodeWhisperer--轻松使用一个超级强大的工具!

CodeWhisperer 简介 CodeWhisperer 是亚⻢逊出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。 CodeWhisperer 有以下几个主要用途&#xff1a; 解决编程问题&#xff0c;提供代码建议&#xff0c;学习编程知识等等&#xff0c;并且 CodeWhisperer 会…

MySQL(7):单行函数

不同DBMS函数的差异 内置函数&#xff1a; 系统内置的通用函数。 自定义函数&#xff1a; 根据自己的需要编写的函数。 大多数 DBMS 使用&#xff08;||&#xff09;或者&#xff08;&#xff09;来做拼接符&#xff0c;而在 MySQL 中的字符串拼接函数为concat()。 大部分 D…

【java】【MyBatisPlus】【四】【完】MyBatisPlus一些实战总结(枚举、翻页、sql、组合条件、自增主键、逻辑删除)

目录 一、枚举 1、数据库type字段是Integer 类型枚举 2、创建一个该字段的枚举类 TypeEnum 3、修改实体类 4、配置文件新增mybatis-plus的配置 5、检验&#xff1a; 5.1 查询显示 5.3 库里验证 二、自增主键不是id字段处理 三、逻辑删除字段不是delete字段处理 1、实…