初识C++ (三)

发布于:2022-10-29 ⋅ 阅读:(735) ⋅ 点赞:(0)

在这里插入图片描述
如果有什么想要去做的事情就立马去做
全部做完了收收心 继续投入学习!

引用

一. 引用的概念

“引用(Reference)是 C++ 相对于C语言的又一个扩充。引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据。

具体是什么意思呢?

我们这里来举个例子

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

那你叫铁牛 他会答应你

你叫黑旋风 他也会答应你

在这里插入图片描述

代码演示

我们下面来写一段代码试试

int main()
{
	int a = 10;
	int& b = a;
	return 0;
}

这段代码是什么意思呢?

我们假设这个a是李逵

那么这个b就是黑旋风的意思了!

我们打印这两个变量的地址来看看

在这里插入图片描述
它们的地址一样的 都是李逵!

在这里插入图片描述

注意:引用类型必须和引用实体是同种类型的

比如说像我们这样子

在这里插入图片描述
这里就是一个错误代码

二. 引用特性

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

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

在这里插入图片描述
这里如果不初始化就会报错 不用多讲了

2. 一个引用可以有多个实体

我们有代码如下

int main()
{
	int a = 10;
	int& b = a;
	int& c = a;


	//char& c = a; 不能改变类型

	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", &c);
	return 0;
}

在这里插入图片描述

这里它们的地址全部都一模一样 就可以说明它们都是一个地址的别名了

3. 引用一旦引用一个实体,再不能引用其他实体

这句话是什么意思呢?

就拿我们的c来说

他已经引用了a了 还能不能引用其他变量呢

在这里插入图片描述

我们可以发现 它们的地址是没有变化的 那么

    c=d;

这一步代码 到底改变了什么呢?

我们画图来看看

在这里插入图片描述

我们来验证下我们的理论正确不正确

在这里插入图片描述
这里就可以发现 a b c的值全部都变成20了

三. 使用场景

1 . 做参数
2 . 做返回值

1. 做参数

我们来看下面的代码

要求: 交换两个变量的值

void swap(int x, int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}

可是上面这段代码真的能够交换两个变量的值嘛?

看过我的这两篇博客的同学应该知道 答案是 不能

函数栈帧(上)

函数栈帧(下)

为什么呢?

因为x y只是我们要交换的函数的临时拷贝

交换它们的值并不会对要交换的值有什么影响

那么结合我们今天学到的知识

同学们有没有想到一种巧妙的解法呢?

没错! 就是引用传参

我们写出下面这样子的代码

void swap(int& x, int& y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}

将它们的别名传进去 就可以啦

思考题:
我们都知道 在单链表头插尾插的时候 为了防止头指针为空的情况 我们就需要传递一个二级指针进去
这样子很麻烦
那么使用我们的引用机制如何修改它呢?

答案就是! 将指针的别名(引用)作为参数传递进去 那么修改引用参数是不是就可以了?

如果不能理解的话这样子

李逵吃饱了是不是就等于黑旋风吃饱了?

我们之后再来看以下代码

#include <time.h>
struct A { int a[10000]; };

void TestFunc1(A a) {}
void TestFunc2(A& a) {}

void TestRefAndValue()
{
	A a;
	int begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1(a);
	int end1 = clock();

	int begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2(a);
	int end2 = clock();


	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();

	return 0;
}

在这里插入图片描述
我们可以发现使用两个函数运行的时间竟然相差很多!

这是为什么呢?

这里就设计到一个传值和传引用的区别(大家类比下传值和传址)
传值需要将整个a拷贝一份
而我们使用引用的话不会拷贝 时间就差在这里

2. 做返回值

还是一样 我们先来看这么两段代码

int& Count()
{
   static int n = 0;
   n++;
   // ...
   return n; }
int Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

在这里插入图片描述
这两段代码的返回值都是1

但是它们返回的方式可不同

我们先来看第二个代码

int Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

当我们调用count()这个函数的时候 它会开辟一个新的内存空间在这里临时空间里面设置一个n变量

将n的值加加

到了这一步的时候

return n;

将n的值放到寄存器里面返回

再来看看你第一个代码

int& Count()
{
   static int n = 0;
   n++;
   // ...
   return n; }

前面的过程几乎一样

注意! 这时候它的返回参数是 int&

也就是说 它直接是把n返回过来了!

不知道大家能不能看出来区别

一个是返回到寄存器中 一个值直接返回n的值

那么这里就引出一个很危险的操作!

int& Count()
{
   int n = 0;
   n++;
   // ...
   return n; }

我们将static去掉 这个时候n彻底变成局部变量了!

这个时候我们打印一下试试看
在这里插入图片描述

咦?

竟然还是1 难道说局部全局变量没有影响嘛?

当然不是!!!

我们再来写出以下代码

void test()
{
	cout << "hello world" << endl;
}

int main()
{
	int& ret = Count();
	cout << ret << endl;
	test();
	cout << ret << endl;

	return 0;
}

在这里插入图片描述
我们发现! 竟然ret变成随机值了!

这是为什么呢?

因为我们实际上得到的ret是n的别名 但是呢在函数结束调用之后所有的参数就被销毁了(这其中也包括n) 当我们运行另外的一个函数来调用栈空间的时候 有可能就将n地址的内容改变了 所以说造成了这个现象

那么这里就引用出两个问题

内存销毁后空间还在吗?

空间还在 但是已经不属于我们了

内存销毁后我们还能访问嘛?

可以访问 但是里面的数据的读写我们都不能确定

结论

1 出了函数作用域,返回变量不存在了,不能用引用返回,因为引用返回的结果是未定义的。
2 出了函数作用域,返回变量存在,才能使用引用返回。

优点

可以修改返回值

比如说

在这里插入图片描述
如果说我们使用 int 来接受ret的话那么只能够每次都给ret赋值了

四. 传值传引用的效率比较

参考做参数 使用场景1中的举例

五. 引用与重载函数

我们来看以下代码
在这里插入图片描述

这两个函数显然是构成重载函数的

但是我们却不建议这么写

因为很有可能造成歧义

比如说你打出下面的代码

test(10);

那么你究竟是想调用谁呢?

六. 常引用

这里牢记一个概念就好
我们引用一个变量的时候所具有的权限只能小于等于该变量

例如

int main()
{
	// a具有读写能力
	int a = 10;
	int& b = a;
	// 可以
	const int& b = a;
	// 权限缩小 可以

	const int c = 20;
	int& d = 20;
	// 权限放大 不可以
	const int& d = 20;
	// 权限相同 可以




	return 0;
}

在这里插入图片描述

右值为常数问题

常数是不可以被改变的 所以说没有写权限

其实不是不能引用而是权限不匹配

如果一定要可以用以下方式写代码

    const int& a = 10;
	// 读写权限匹配
	cout << a << endl;

在这里插入图片描述

总结

在这里插入图片描述

以上就是关于c++引用博主一些浅薄的理解啦
如果出现错误希望大佬们指正!
阿尼亚 哇酷哇酷!


网站公告

今日签到

点亮在社区的每一天
去签到