cpp从0到1----引用&

引用

引用概念的引出

我们都知道,在很多地方都会应用到将两个数交换的函数,

void swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

但是这作为一个函数,却无法真正的实现两个数的交换,
在这里插入图片描述
这个问题的原因就是,在底层,传递的a和b作为实参,被拷贝了一份放到了swap函数中,实际交换的是a的拷贝和b的拷贝,并不是a本身和b本身,如果要让这个函数实现它交换的功能,就应该将函数参数改为指针,

void swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

这样就是将a和b的指向交换了,也就实现了数据交换的功能,
在这里插入图片描述
但是,这样的代码可读性比较差,我要交换的是int类型的数据,必须要求我传个指针,并且指针的安全性系数不高,如果传了个野指针就崩溃了;

那这个swap函数就是哪哪都是缺陷?
传值,无法通过形参修改实参;
传地址,可读性差,安全性不高;

这时候就要用到一个新概念–引用

引用,可以理解为别名,并不是一个新变量,而是给已经存在的变量取了个别名;结合了他们两个的优势,可以按照传值的方式,保证了安全性和可读性,但是可以同时实现通过形参修改实参;

引用的使用规则

##引用定义的时候必须要初始化

int main()
{
	int a = 1;
	int& ra;
}

在这里插入图片描述
不初始化是会报错的;
其实可以理解为,取别名必须要有具体的物体才能取别名,不然这个别名取的有什么意义…

一个变量可以有多个引用

int main()
{
	int a = 1;
	int& ra = a;
	int& rra = a;
	int& rrra = a;
	int& rrrra = a;
}

这样也是行得通的,不过是一个物体多了个几个别名而已,无伤大雅。

某个引用变量一旦引用一个实体,就不可以再引用其他实体

其实与其说是不可以再引用,不如说不知道如何再去引用;
假如说现在

	int a = 1;
	int& ra = a;

有一个a变量,ra是他的引用变量;那如果要让ra再去引用一个b变量,该怎么写???

ra = b;

这样是修改变量a的值,不是再让ra去引用b;

&ra = b;

这样写是将b赋值给ra变量的地址,更加离谱了…
所以其实压根就没办法修改一个引用的引用对象;

常引用

首先要明确什么是常量;

常量只有数字??
我们都知道,在定义数组的时候,数组的长度只能用常量来定义,所以给定一个确定的数字是绝对ok的,

	const int n = 100;
	int a[n];

但其实这样写,也不会报错;
所以常量分为两种,一种是数字,一种是const类型的引用

在常引用,也就是const类型的引用中,有几点需要注意的

const类型变量的引用也必须是const类型的

	const int a = 10;
	int& ra = a;

这样是100%报错的,因为ra是a的别名,可以通过ra来修改a,但是a是一个const类型的变量,是不能修改的,这样无法保证const的常性;

引用类型的“怪异”

我们都知道在定义引用变量的时候,肯定是将其定义成 和要引用的变量相同类型的形式,
比如,要定义int类型的变量a的引用变量ra,那肯定给ra定义成int&,不会往char&,double&这样去想,但是如果这样乱定义能不能行呢?

int main()
{
	double d = 12.34;
	const int& rd = d;
}

在这里插入图片描述
这样居然是不报错的…

为什么???????

这样是正确的,那rd输出出来是多少呢?
在这里插入图片描述
rd不是d的别名吗?为什么输出的不是d呢?

那如果再把代码改成

int main()
{
	double d = 12.34;
	const int& rd = d;
	d = 459812375.324570;
	cout << "rd = " << rd << endl;
}

这个时候rd变不变呢?
在这里插入图片描述
他居然没变

这里是因为,再定义rd这个引用变量的时候,因为double和int可以发生隐式类型转化,所以d这12.34转化成了一个不知名变量12,而且rd指向的是12,并不是d;所以d修改之后rd并没有变化,

而且,既然叫隐式类型转化,我们就肯定看不见中间过程,也就无法知道它的转化之后的变量和该变量的地址,也就无法修改这个变量,不就实现了常性吗~?

在这里插入图片描述

引用的应用

简化复杂的结构体变量嵌套

struct A
{
	int a;
	int b;
};
struct B
{
	A c;
	int d;
};
struct C
{
	B e;
	int f;
};
int main()
{
	struct C t;
	t.f = 1;
	t.e.d = 2;
	t.e.c.b = 3;
	t.e.c.a = 4;
}

如果有三个结构体嵌套,就已经能混乱成这样…
而且还是在变量和结构体名只有abc的情况下…
如果再多一点,名字再复杂一点,看的人都要疯了

struct A
{
	int a;
	int b;
};
struct B
{
	A c;
	int d;
};
struct C
{
	B e;
	int f;
};
int main()
{
	struct C t;
	

	struct B& rb = t.e;
	rb = { {1,2},3 };
	t.f = 4;
	
}

这样写就会舒服一点

通过形参修改实参

这就是swap函数的后续了

void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

在这里插入图片描述
可以通过传值的方式,通过形参修改实参

作为函数返回值

引用其实之所以好用,就是它别名的属性,就像上一条里面的swap函数,看似是传值,其实是传地址,传了个别名上去;

这里作为函数返回值也是一样,它可以省一次拷贝,如果我们用一个类型的变量接收函数返回值

在这里插入图片描述
在这里插入图片描述
但是需要注意两点:
1、函数返回值 是引用类型,必须用引用类型接受
2、引用作为函数返回值,一定注意不能返回函数栈上的空间,局部变量(上面的图只是做个例子,不要模仿)

具体是为什么,可以看一下这里

int& add(int l, int r)
{
	int ans = 0;
	ans = l + r;
	return ans;
}

int main()
{
	int& res = add(10, 20);
	printf("%d\n", res);
	printf("%d\n", res);
}

它的输出结果是
在这里插入图片描述
为什么会这样呢??

这里涉及到部分调用约定的知识,我们先看一下这个函数的调用约定是什么;
在这里插入图片描述
可见调用约定是: _cdcel

_cdcel有一条约定是:函数结束运行之后的栈帧中的垃圾数据不需要清理,下一个函数进来运行之前先清理;

可以理解为在食堂吃饭,吃完不需要把吃完的垃圾倒掉,下一个人来的时候要吃饭会先收拾你吃的垃圾;

那其实画个图就很好理解了;
1.
当还没走到main函数中的add(10,20),整个栈是这样的;
在这里插入图片描述
2.
走到add(10,20)之后,将add函数入栈

res指向结果30
在这里插入图片描述
3.add函数结束之后,并不会清理自己栈帧中的数据,所以这个时候ans30还在
只不过是ebp和esp位置变了
在这里插入图片描述
4.
现在要运行printf函数了,但是要这个时候还有add函数的数据

也就是说,printf将res这个参数入栈的时候,res指向的数据还是存在的;所以第一次会输出30;
在这里插入图片描述

但是当第一个printf运行完之后,add函数的数据就肯定被销毁了,这个时候res指向的值也随之销毁,成了随机值,所以会输出随机值;

所以还是不要将引用返回局部变量;

传值,传指针,传引用的效率对比

#define  _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
struct A
{
	int a[100000];
	int b;
};

void f1(A a)
{
	
}

void f2(A* a)
{
	
}

void f3(A& a)
{

}

int main()
{
	
	A a;
	size_t b1 = clock();

	for (int i = 0; i < 10000; ++i)
	{
		f1(a);
	}
	size_t e1 = clock();

	size_t b2 = clock();

	for (int i = 0; i < 10000; ++i)
	{
		f2(&a);
	}
	size_t e2 = clock();

	size_t b3 = clock();

	for (int i = 0; i < 10000; ++i)
	{
		f3(a);
	}
	size_t e3 = clock();

	cout << e1 - b1 << endl << e2 - b2 << endl << e3 - b3 << endl;
}

在这里插入图片描述
可以看出传值和传引用传指针的效率差距巨大,
而传值和传引用的效率差不多,这是为什么??

int main()
{
	int a = 1;
	int* pa = &a;
	int& ra = a;
}

看这个代码的反汇编,看看能不能找到什么信息

在这里插入图片描述
我们在定义引用和指针的时候,反汇编居然显示结果相同??!

也就是说底层逻辑中,引用就是指针…

引用和指针的区别

1、使用方式有区别
指针可以不初始化但是引用不可以

2、赋值有区别
引用类似于 类型* const 的指针,无法改变它的指向,但是有的指针可以改变指向有的不行;

3、++结果也不同
对引用类型++,结果是对其引用的变量++,但是对指针++,会在一块连续的内存中向后移动一个空间,或者直接访问未申请的空间runtime error

4、概念区别
引用的概念实体变量的一个别名,是和实体共用一个空间,但是指针就是一个指针变量,自己占一个空间

5、sizeof的区别
sizeof引用的结果是 引用类型的大小 比如在32位操作系统下 int大小位4,char为1,double为8;但是sizeof指针在所有32位操作系统下 大小都是4;

6、有多级指针,但没有多级引用

7、访问实体方式不同
引用不需要显式解引用,但是指针需要;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值