C++特性——引用与指针详解

文章目录

  • 引用
    • 1. 引用的作用
      • 1.1 引用可以做函数参数:
      • 1.2 引用做函数返回值:
    • 2 常引用
    • 3 引用和指针
      • 3.1 引用和指针在语法层面和底层的异同
      • 3.1 引用和指针的不同

引用

简单来说,引用就是给一个变量起一个别名。例如:

int a = 1;
int& b = a;

对于上面的代码,我们就说ba的别名,我们可以看看ba的地址:

在这里插入图片描述

我们可以发现,别名ba共用一块地址,不会开辟新的空间,我们可以将下面三段代码进行比较:

//代码一
int a = 1;
int& b = a;//代码二
int a = 1;
int b = a;//代码三
int a = 1;
int* b = &a;
  • 代码一:ba别名,前面已经讲过,这里不再赘述

  • 代码二:创建了一个新的整形变量b,并将a的值赋值给b

  • 代码三:创建了一个指针变量b指向变量a

我们可以画图来更加明确他们的关系:

在这里插入图片描述

1. 引用的作用

1.1 引用可以做函数参数:

既然我们知道引用就表示给一个变量取别名,那么我们就可以用引用做函数实参,来简化用C语言写时较为复杂的代码,例如:

  • 交换两个数的值Swap
/*形参a,b是两个别名,和实参共用一个地址。因此形参的改变就会影响实参的改变故可以不要像C语言一样传实参的地址
*/
void Swap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
  • 无哨兵位单链表的尾插SLPushBack
typedef struct ListNode
{struct ListNode* next;int val;
}SL;//如果不用别名,那么形参就应该是二级指针,只有这样才能改变原本为一级指针的实参
void SLPushBack(SL*& phead, int data)
{assert(phead);SL* newNode = (SL*)malloc(sizeof(SL));newNode->val = data;newNode->next = NULL;if (phead == NULL)phead = newNode;else{SL* cur = phead;while (cur->next)cur = cur->next;cur->next = newNode;}
}

总结:

引用做函数实参,有以下好处:

  1. 形参的改变可以影响实参,这样可以降低函数的复杂度并提高函数的可读性
  2. 对于大型的实参,如果将它的别名作为形参传入函数,就可以大大提高函数效率

1.2 引用做函数返回值:

先来看一串代码:

int& Add(int a, int b)
{int c = a + b;return c;
}int main()
{int& ret = Add(1, 2);cout << ret << endl;Add(3, 4);cout << ret << endl;return 0;
}

大家来想一想,这两次打印的ret的结果分别是什么?

在这里插入图片描述

第一次为3大家可能认为没问题,但为什么第二次明明ret没有接收函数Add(3, 4)的返回值,为什么他的值会变成7呢?

原因是:

  • Add函数的返回值类型是一个引用,即局部变量c的别名,而接收对象ret也是引用,因此ret也是局部变量c的别名
  • 所以尽管第二次ret没有接受返回值,但是c的改变也会影响其别名ret的改变,所以最后ret的值也会变为7。

其实,上面的分析并不完全正确

  • 应该清楚,随着函数栈帧的销毁,其函数内部的局部变量也会被销毁
  • 因此ret打印出的结果应该是不确定的
  • 这里之所以可以打印出看似正确的结果3和7,可能因为博主用的编译器并没有清理函数Add的栈帧

而要使代码正确,我们可以将变量c设置为静态变量,这样就不会随着函数栈帧的销毁而被清除了。

清楚了这一点,再来看下面的两串代码:

代码一:

int& Add(int a, int b)
{static int c = a + b;return c;
}int main()
{int& ret = Add(1, 2);cout <<"ret = " <<  ret << endl;Add(3, 4);cout << "ret = " << ret << endl;return 0;
}

代码二:

int& Add(int a, int b)
{static int c;c = a + b;return c;
}int main()
{int& ret = Add(1, 2);cout <<"ret = " <<  ret << endl;Add(3, 4);cout << "ret = " << ret << endl;return 0;
}

大家认为,这两串代码打印的结果一样吗?

在这里插入图片描述

如果想错了的小伙伴,可能忽略了一点:静态变量只能被初始化一次

因此,对于代码一,定义静态变量c时,将其初始化为a + b = 3,那么之后就不会被初始化,ret的值也就不会变了。

经过上面的一系列分析,可以得出结论:

如果将局部变量的引用作为返回值,那么出了函数作用域,返回对象就被销毁了,不能用引用返回,否则结果就是不正确的。

接下来我们举一个正确使用引用做返回值的例子:

//功能:将顺序表的每个值++typedef struct SeqList
{int data[100];int size;
}SL;int& SLPosModify(SL* sl, int pos)
{return (sl->data)[pos];//返回顺序表每个位置的别名,从而改变返回对象就可以改变顺序表内的内容
}int main()
{SL* sl = (SL*)malloc(sizeof(SL));sl->size = 100;memset(sl->data, 0, sizeof(int) * sl->size);for (int i = 0; i < sl->size; i++)SLPosModify(sl, i)++;return 0;
}

可以看到,引用做函数返回值有以下好处:

  1. 可以直接修改返回对象
  2. 可以简化代码,提高代码可读性
  3. 可以提高效率

2 常引用

常引用(const reference)是一种引用变量的方式,用于表示引用的目标对象不可被修改

需要注意以下几点:

  1. 取别名时不能将原来的权限放大,只能权限平移或者权限缩小。例如:

    const int a = 1;
    int& b = a;	//权限放大
    

    会有如下报错信息:

    在这里插入图片描述

    而下面的写法就是正确的:

    int a = 1;
    const int& b = a;	//权限缩小
    const int& c = b;	//权限平移
    
  2. 可以给常量取别名。例如:

    const int& d = 10;
    
  3. 当引用涉及到类型转换时,要注意临时变量的常性。例如:

    int a = 1;
    double& b = a;
    

    错误的。而

    int a = 1;
    const double& b = a;
    

    就是正确的

    • 这是由于

      当给整型变量a取一个浮点型的别名b时,就会发生隐式转换,中间就会产生一个临时变量
      而由于临时变量具有常性(const),因此由上面我们所说的取别名不能放大权限,因此正确的写法为const double& b = a;

    • 注:具体的,临时变量到底是怎么产生的以及他有什么特性,感兴趣可以看看👉C/C++陷阱——临时变量的产生和特性

3 引用和指针

在上面的介绍中,我们例举了很多引用替代指针来实现指针功能的例子。那么我们是否可以这样认为:C++中,引用可以完全取代指针?

答案是:不能!

首先我们应该清除引用有两点特性:

  1. 引用在定义时必须被初始化

  2. 引用不能被修改指向,只能修改其值(例如,如果c原来是a的引用,那么c就只能是a的引用)

例如:

在这里插入图片描述

而我们又清楚,指针可以随意改变它的指向,这是引用无法做到的,因此在C++中,引用是无法完全取代只针的,应该说,引用和指针是互补的。

3.1 引用和指针在语法层面和底层的异同

  1. 语法上引用就是给一个变量取别名,不会开辟额外空间,和原来的变量共用一个地址。而指针就会开辟空间用来存储这个指针变量。
  2. 底层上,引用和指针一样,都是用汇编实现的。因此我们可以说,实际上声明和指针都是传地址

那么日常情况下,我们是以语法为主还是底层为主呢?

答案是以语法为主。我们来看一个例子:

char a = 1;
char& b = a;cout << sizeof(b) << endl;

32位系统,如果输出的是4,那就说明以底层为主,如果输出为1,那就说明以语法为主。

在这里插入图片描述

我们可以看到,输出的结果是1,因此日常情况下,以语法为主

3.1 引用和指针的不同

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址

  2. 引用在定义时必须初始化,指针没有要求

  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

  4. 没有 NULL 引用,但有 NULL 指针

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

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

  7. 有多级指针,但是没有多级引用(一个变量可以有多个引用)

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

  9. 引用比指针使用起来相对更安全,但不是绝对安全


本篇完。

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

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

相关文章

阿里云云服务器实例使用教学

目录 云服务器免费试用 详细步骤 Xshell 远程连接 云服务器免费试用 阿里云云服务器网址&#xff1a;阿里云免费试用 - 阿里云 详细步骤 访问阿里云免费试用。单击页面右上方的登录/注册按钮&#xff0c;并根据页面提示完成账号登录&#xff08;已有阿里云账号&#xff09;…

vue 自定义指令 -- 指令的值

vue 自定义指令 – 指令的值 **创建 工程&#xff1a; H:\java_work\java_springboot\vue_study ctrl按住不放 右键 悬着 powershell H:\java_work\java_springboot\js_study\Vue2_3入门到实战-配套资料\01-随堂代码素材\day05\准备代码\05-自定义指令-指令的值 vue --vers…

k8s-13 存储之secret

Secret 对象类型用来保存敏感信息&#xff0c;例如密码、OAuth 令牌和 ssh key。 敏感信息放在 secret 中比放在 Pod 的定义或者容器镜像中来说更加安全和灵活 。 Pod 可以用两种方式使用 secret:作为 volume 中的文件被挂载到 pod 中的一个或者多个容器里 当 kubelet 为 pod 拉…

HugeGraph 部署和Hubble1.0.0的数据导入Bug修复

背景 HugeGraph 安装部署了最新版本1.0.0&#xff0c;发现它的 Web 工具 Hubble 有一个大 Bug。数据导入的时候&#xff0c;配置节点属性映射这个选项时&#xff0c;下拉框只有一个选项&#xff0c;但实际上&#xff0c;元数据配置中的属性有3个&#xff0c;这个 Bug 是怎么产…

从零开始搭建第一个django项目

目录 配置环境创建 Django 项目和 APP项目组成  ‍子目录文件组成应用文件组成 配置 settings.py启动项目 数据表创建models.pyDjango-models的常用字段和常用配置 Django-admin 引入admin后台和管理员外键views.pyurls.pypostman接口测试 QuerySetInstance功能APIView 的概念…

java学生通讯录管理系统

设计要求 本课程设计&#xff0c;涉及输入输出、GUI设计、数据库操作等本课程重要概念和编程技能&#xff0c;全面巩固和加深学生对java程序设计的相关概念的理解&#xff0c;全面强化java编程技能&#xff0c;培养学生综合运用所学知识和技能分析问题和解决问题的能力。培养学…

python web开发(四): Bootstrap

1.初步了解 别人已经写好的CSS样式&#xff0c;我们可以直接引用 下载 Link-BootStrap 解压&#xff0c;并放入到当前项目中 引用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</tit…

chatglm2微调—ptuning

Freeze: 即参数冻结&#xff0c;对原始模型部分参数进行冻结操作&#xff0c;仅训练部分参数&#xff0c;以达到在单卡或不进行TP或PP操作&#xff0c;就可以对大模型进行训练。 P-Tuning: 在输入的embedding层前&#xff0c;将prompt转换为可学习的额外一层embedding层. P-T…

SQL数据库管理工具RazorSQL mac中文版特点与功能

RazorSQL mac是一款功能强大的SQL数据库管理工具&#xff0c;它支持多种数据库&#xff0c;包括MySQL、Oracle、Microsoft SQL Server、SQLite、PostgreSQL等。 RazorSQL mac 软件特点和功能 多种数据库支持&#xff1a;RazorSQL支持多种数据库&#xff0c;用户可以通过一个工…

【ELK 使用指南 3】Zookeeper、Kafka集群与Filebeat+Kafka+ELK架构(附部署实例)

EFLKK 一、Zookeeper1.1 简介1.2 zookeeper的作用1.3 Zookeeper的特点1.5 Zookeeper的数据结构1.6 Zookeeper的应用场景1.7 Zookeeper的选举机制&#xff08;重要&#xff09;1.7.1 第一次启动时1.7.2 非第一次启动时 二、Zookeeper集群部署2.1 安装前准备2.2 安装 ZookeeperSt…

【音视频流媒体】 3、ffmpeg、ffplay、ffprobe 超详细介绍

文章目录 一、ffmpeg1.1 安装1.2 基本参数 二、ffprobe2.1 查编码格式2.2 查视频时长 五、视频转流5.1 MP4转H2645.2 H264转MP45.3 AVI转MP45.4 MP4转H265 六、视频文件6.1 播放6.2 filter 过滤器6.2.1 crop 6.3 视频截取6.4 视频拼接6.5 获取分辨率 七、视频和图7.1 视频抽帧7…

C++11——lambda表达式

文章目录 1. C98对自定义类型的排序2. lambda表达式语法2.1 捕捉列表 3. lambda底层原理 1. C98对自定义类型的排序 在C98中&#xff0c;想要对自定义类型就行排序&#xff0c;我们得自己写仿函数来表明我们相对哪一项进行排序 struct Student {Student(string name, long id…