【C++入门】引用

目录

6.引用

6.1引用概念 

6.2引用的写法 

6.3引用的特性

6.4常引用

6.5引用的使用场景

6.5.1引用做参数

6.5.2引用做返回值❗❗

🎇值做返回值

🎇引用做返回值

🎇引用在顺序表做返回值

6.5.3传值、传引用效率比较(参数)

6.5.4值和引用的作为返回值类型的性能比较(返回值)

6.6引用和指针

6.6.1引用替换指针的场景

6.6.2引用和指针的区别 


6.引用

6.1引用概念 

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

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

6.2引用的写法 

 引用的写法格式:

  • 类型& 引用变量名(对象名) = 引用实体;
  • 可以给变量的别名取别名,所有别名都是公用同一块内存空间。
  • 注意:引用类型必须和引用实体是同种类型的
//类型& 引用变量名(对象名) = 引用实体;
void TestRef()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

 【验证变量和变量的引用(别名)是否是同一块内存空间】

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	int& b = a;
	int c = a;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << b << endl;
	cout << c << endl;

	b++;//b++的同时a++了吗?c++了吗?
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

【别名的别名&类型一致原则】 

int main()
{
	int a = 0;
	int& b = a;
	int& c = b;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;

	//❌double& b=a;类型必须一致
	return 0;
}

6.3引用的特性

  • 1. 引用在定义时必须初始化
  • 2. 一个变量可以有多个引用
  • 3. 引用一旦引用一个实体,再不能引用其他实体

大家可以自己动手验证下。

int main()
{
	int a = 0;
	//1.引用必须初始化
	//int& b;
	//b = a;

	//2.引用定义后,不能改变指向
	int& b = a;
	int c = 2;
	b = c;//这是不是改变指向,而是赋值
	cout << a << endl;//2
    cout << b << endl;//2
	
	//3.一个变量可以有多个引用,多个别名
	//定义别名的别名
	int& x = a;
	int& d = c;


	return 0;
}

6.4常引用

void TestConstRef()
{
    const int a = 10;
    //int& ra = a;   // 该语句编译时会出错,a为常量
    const int& ra = a;
    // int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    double d = 12.34;
    //int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d;
}

6.5引用的使用场景

6.5.1引用做参数

 引用做参数:

  • 输出型参数
  • 输入型参数
  • 对象比较大,减少拷贝,提高效率
  • 指针可以做,但是引用的更方便,更有性价比
//输出型参数
int* preorderTraversal(struct TreeNode* root, int* returnSize)
int* preorderTraversal(struct TreeNode* root, int& returnSize) 

//交换两个变量

#include<iostream>
using namespace std;

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
int main()
{
	int x = 10;
	int y = 20;
	cout << "x=" << x << endl;
	cout << "y=" << y << endl;
	Swap(&x, &y);
	cout << "x=" << x << endl;
	cout << "y=" << y << endl;
	return 0;
}


void Swap(int& p1, int& p2)
{
	int tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int x = 10;
	int y = 20;
	cout << "x=" << x << endl;
	cout << "y=" << y << endl;
	Swap(x, y);
	cout << "x=" << x << endl;
	cout << "y=" << y << endl;
	return 0;
}
//不能交换地址

 

6.5.2引用做返回值❗❗

🎇值做返回值

 请问return a意味着把a的值传给func吗?回顾:函数栈帧的创建与销毁-CSDN博客

  • 函数调用结束,函数栈帧就销毁了,return需要返回值,会再函数栈帧销毁之前拷贝放到临时空间里面。给ret接收。 
  • 数据小,临时空间是寄存器。数据大。
#include<iostream>
using namespace std;
int func()
{
	int a = 0;
	return a;
}

int main()
{
	int ret = func();
	return 0;
}


🎇引用做返回值
  • 函数调用结束,函数栈帧就销毁了,return返回a的别名,给ret接收。 
  • ret接收到了之后,后面访问a的别名,也就是访问a的空间,但是a的空间已被销毁,就会造成非法访问,野引用的问题。非法访问!!!特别注意
  1. 如果a的内存空间在函数调用之后没有被清理,去访问可能访问到原值。但是如果后序定义其他的变量,这个内存位置可能就被操作系统分配给其他的变量。
  2. 如果a的内存空间在函数调用之后被清理了,访问到了随机值。

  • 注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
  • 返回变量出了函数作用域就生命周期(局部变量),不能用引用返回。返回变量出了函数作用域就生命周期到了就销毁,不能用引用返回。
  • 非法访问!!野引用!!对程序存在安全隐患。
  • C++中临时变量具有常量(不可被修改)

    全局变量/静态变量static/堆上变量等就可以引用做返回值。

【示例一】 

#include<iostream>
using namespace std;
int& func()
{
	int a = 0;
    //✔static int a=0;
	return a;
}

int main()
{
	int ret = func();
	return 0;
}

【示例二】 

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


🎇引用在顺序表做返回值
  • 不需要typedef 就可以直接使用SeqList
  • 结构体(类)中可以包含成员函数
  • 域不同,不用再区分名称
  • 开辟的空间在堆上,出了作用域,函数栈帧没有销毁(可以用引用做返回值)

返回引用

  • 引用具有了读写双重功能
  • 引用中间没有产生临时变量

引用做返回值

  • 修改返回对象
  • 减少拷贝提高效率

这个部分到后面还会细讲

//C++
#include<iostream>
#include<assert.h>
using namespace std;
struct SeqList
{
	//成员变量
	int* a;
	int size;
	int capacity;

	//成员函数
	void Init()
	{
		a = (int*)malloc(sizeof(int) * 4);
		// ...
		size = 0;
		capacity = 4;
	}

	void PushBack(int x)
	{
		 //... 扩容
		a[size++] = x;
	}

	 //读写返回变量
	int& Get(int pos)
	{
		assert(pos >= 0);
		assert(pos < size);

		return a[pos];
	}

	int& operator[](int pos)
	{
		assert(pos >= 0);
		assert(pos < size);

		return a[pos];
	}
};

int main()
{
	SeqList s;
	s.Init();
	s.PushBack(1);
	s.PushBack(2);
	s.PushBack(3);
	s.PushBack(4);

	for (int i = 0; i < s.size; i++)
	{
		//cout << s.Get(i)<< " ";
		cout << s[i] << " ";
		//cout << s.operator[](i) << " ";
	}
	cout << endl;

	for (int i = 0; i < s.size; i++)
	{
		/*if (s.Get(i) % 2 == 0)
		{
			s.Get(i) *= 2;
		}*/
		if (s[i] % 2 == 0)
		{
			s[i] *= 2;
		}
	}
	cout << endl;

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

	return 0;
}

6.5.3传值、传引用效率比较(参数)

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

  • clock只能计算出整数位。0ms表示小于1ms,不代表没有,代表时间极其短。
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void main()
{
	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;
}

6.5.4值和引用的作为返回值类型的性能比较(返回值)

通过上述下述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。 

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void main()
{
	// 以值作为函数的返回值类型
	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;
}

 

6.6引用和指针

6.6.1引用替换指针的场景

  • 指针和引用的功能是类似的,重叠的。
  • C++的引用,对指针使用比较复杂的场景进行一些替换【场景替换】,让代码更简单易懂,但是不能完全替代指针。
  • C++的引用不能完全替代指针的元婴就有:引用定义之后,不能改变指向,但是指针可以改变指向。
  • 在Java python 等其他语言里没有指正,只有引用,且其他语言的引用是可以改变指向的实现链表等。注意区别C++的引用是不能改变指向的。
  • C++中90%的场景还是使用引用,但是在一些特殊场景,我们还是使用指针。(例如:链表,二叉树,指向一段动态内存开辟的空间)

【链表中二级指针】

//双向无头链表
typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}LNode,*PNode;

//void PushBack(PNode* phead, int x)
void PushBack(PNode& phead, int x)
{
	//
}
int main()
{
	PNode pphead;
	PushBack(pphead, 1);
}

【输出型参数】

OJ输入型参数:直接给数据使用。

OJ输出型参数:给参数需要修改这个参数(指针/别名)

int* preorderTraversal(struct TreeNode* root, int* returnSize)
int* preorderTraversal(struct TreeNode* root, int& returnSize) 

6.6.2引用和指针的区别 

引用和指针的不同点:

  • 1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  • 2. 引用在定义时必须初始化,指针没有要求。
  • 3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
  • 4. 没有NULL引用,但有NULL指针。
  • 5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
  • 6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
  • 7. 有多级指针,但是没有多级引用。
  • 8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
  • 9. 引用比指针使用起来相对更安全 。
  • 底层是底层,语法是语法,二者不可混为一谈。

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

int main()
{
	int a = 10;
	int& ra = a;
	cout << "&a = " << &a << endl;
	cout << "&ra = " << &ra << endl;
	return 0;
}

底层实现上实际是有空间的,因为引用是按照指针方式来实现的。 

int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;
	int* pa = &a;
	*pa = 20;
	return 0;
}

引用和指针的区别:

【语法】

  • 引用是别名,不开空间;指针是地址,需要开空间存地址。
  • 引用必须初始化,指针可以初始化可以不用初始化。
  • 引用不能改变指向,指针可以改变指向。
  • 引用相对更加安全,没有空引用;但有空指针。
  • 容易出现野指针;但不容易出现野引用。
  • 其他方面。

【底层】

  • 汇编层面上,没有引用,只有指针,引用编译后也转换成指针了。

🙂感谢大家的阅读,若有错误和不足,欢迎指正。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OSPF NSSA实验简述

OSPF NSSA实验简述 1、OSPF NSSA区域配置 为解决末端区域维护过大LSDB带来的问题&#xff0c;通过配置stub 区域或totally stub区域可以解决&#xff0c;但是他们都不能引入外部路由场景。 No so stuby area &#xff08;区域&#xff09;NSSA 可以引入外部路由&#xff0c;支持…

【Linux】ecs 挂载分区

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 详细步骤&#xff1a; 结语 我的其他博客 前言 在Linux系统中&#xff0c;挂载分区是连接额外存储空间到文件系统的重要步骤之一…

【计算机网络】IO多路转接之epoll

文章目录 一、epoll的相关系统调用二、epoll工作原理三、epoll的优点(和 select 的缺点对应)四、epoll工作方式五、epoll服务器1.Sock.hpp2.Log.hpp3.Err.hpp4.epollServer.hpp5.epollServer.cc 一、epoll的相关系统调用 按照man手册的说法: 是为处理大批量句柄而作了改进的po…

iOS小技能:苹果开发者申请材料

文章目录 引言I 个人账号申请资料II 公司账号申请所需资料III duns资料提交操作步骤IV 续费引言 https://developer.apple.com/cn/programs/enroll/ 申请过程只能使用同一台设备注册苹果开发者的Apple ID可以转让。注册苹果开发者的在验证身份证信息的时候,必须使用法定姓名拼…

信呼OA普通用户权限getshell方法

0x01 前言 信呼OA是一款开源的OA系统&#xff0c;面向社会免费提供学习研究使用&#xff0c;采用PHP语言编写&#xff0c;搭建简单方便&#xff0c;在中小企业中具有较大的客户使用量。从公开的资产治理平台中匹配到目前互联中有超过1W的客户使用案例。 信呼OA目前最新的版本是…

Docker_设置docker服务以及容器开机自启

本文目录 docker服务开机自启动查询docker服务开机自启动状态将docker服务设置为开机自启动取消docker服务开机自启动 容器开机自启动修改docker容器为自启动容器启动时设置自启动-docker版容器启动时设置自启动-docker-compose版 docker服务开机自启动 查询docker服务开机自启…

git 命令怎么回退到某个特定的 commit 并将其推送到远程仓库?

问题 不小心把提交的名称写错提交上远程仓库了&#xff0c;这里应该是 【029】的&#xff0c;这个时候我们想回到【028】这一个提交记录&#xff0c;然后再重新提交【029】到远程仓库&#xff0c;该怎么处理。 解决 1、首先我们找到【028】这条记录的提交 hash&#xff0c;右…

【web安全】实战 批量横扫springboot命令执行漏洞

天命&#xff1a;这次目标批量横扫&#xff0c;但是没完全成功&#xff0c;也没完全失败 步骤1&#xff1a;磨刀准备 这次先针对漏洞来寻找目标&#xff0c;所以寻找这种 springboot 的目标 利用CVE漏洞&#xff0c;进行命令执行攻击 先找靶场训练一波&#xff0c;叠加反弹sh…

2024年阿里云域名优惠口令更新,亲测有效口令大全

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

【教育部白名单赛事】C语言编程题解析--软件编程邀请赛(决赛)

文章目录 1、保留12位小数的浮点数2、气温统计3.大写字母的判断4、【递归】母鸡的故事5、小白免再排队 1、保留12位小数的浮点数 输入一个双精度浮点数&#xff0c;保留12位小数&#xff0c;输出这个浮点数。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 【输入】 只…

华为机试 字符串最后一个单词的长度

本题中&#xff0c;我们是要从键盘输入一个字符串&#xff0c;然后返回这个字符串最后一个单词的长度。所以我们需要scancer类。我们需要注意的是&#xff0c;hasnext()和hasnextline()这两个函数的区别。 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 pack…

24计算机考研调剂 | 北京语言大学

北京语言大学 刘忠宝教授课题组招收计算机学硕调剂生2名 考研调剂招生信息 学校:北京语言大学 专业:工学->计算机科学与技术->计算机应用技术 年级:2023 招生人数:2 招生状态:正在招生中 联系方式:********* (为保护个人隐私,联系方式仅限APP查看) 补充内容 一、…

Android开发五年,职场中的中年危机

前言 Android确实不是当年盛况&#xff0c;已经不再像前几年前那么火爆。一个新行业如果经历过盛极一时&#xff0c;那么必然有这样的一条曲线&#xff0c;像我们学的正弦曲线先急速上升&#xff0c;然后到达顶点&#xff0c;然后再下降&#xff0c;最后再趋近一个平稳的值。那…

【Python--读获取目录下所有csv文件中的均值与偏态】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; python练习题 读获取目录下所有csv文件中的均值与偏态按照均值和偏态最大值进行排序完整代码 读获取目录下…

RocketMq——Consume相关源码

摘要 RocketMQ只要有CommitLog文件就可以正常运行了&#xff0c;那为何还要维护ConsumeQueue文件呢&#xff1f; ConsumeQueue是消费队列&#xff0c;引入它的目的是为了提高消费者的消费速度。毕竟RocketMQ是基于Topic主题订阅模式的&#xff0c;消费者往往只关心自己订阅的…

184基于matlab的相关向量机(RVM)回归和分类算法

基于matlab的相关向量机&#xff08;RVM&#xff09;回归和分类算法。该算法基于贝叶斯稀疏核⽅法&#xff0c;避免了支持向量机&#xff08;SVM&#xff09;的主要局限性。RVM关键是为每个权参数 都引入一个单独的超参数 &#xff0c;而不是一个共享超参数。程序已调通&#x…

和鲸科技受邀参与湖南省气象信息中心开展人工智能研究型业务支撑平台学术交流

为推进湖南省机器学习统一平台建设&#xff0c;2 月 29 日&#xff0c;湖南省气象信息中心开展学术讲座活动&#xff0c;活动由中心副主任冯冼主持&#xff0c;中心业务骨干、湖南省气象台、湖南分院等技术人员参加。 本次讲座邀请上海和今信息科技有限公司&#xff08;简称“…

MySQL——事务

事务 2024 年 1 月字节后端实习面试&#xff1a;说说对 ACID 的理解&#xff1f; 什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;是数据库管理系统中一个执行单元&#xff08;unit of work&#xff09;&#xff0c;它由一系列的操作&#xff08;例如读取数…

(文末送书)《低代码平台开发实践:基于React》

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&am…

XGboost的整理

XGboost&#xff08;extreme gradient boosting&#xff09;:高效实现了GBDT算法并进行了算法和工程上的许多改进。 XGboost的思路&#xff1a; 目标&#xff1a;建立k个回归树&#xff0c;使得树群的预测尽量接近真实值&#xff08;准确率&#xff09;而且有尽量大的泛化能力…