IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++入门】烦人的引用 -> 正文阅读

[C++知识库]【C++入门】烦人的引用

1、引用的概念

??引用不是定义一个新的变量,而是给已有的变量定义一个别名,编译器不会给引用开辟内存空间,它和它引用的变量共用一块空间。

类型& 引用变量名(对象名) = 引用实体;

比如:

int a = 10;
int& ai = a;
//正如一个人可以取多个外号,实体也可以有多个引用。
int& aii = a;

ai 就是 a的别名,ai 是 a的另一个称号。
在这里插入图片描述
所以有很明显的几个规则:

  1. 得先有本体,再有别名,所以引用不能空引用,且引用必须有初始值
  2. ai作为a的别名后,不能再作为其它的别名,不然会产生歧义。
  3. 如果改变ai的值,那么a的值也会改变,因为它们都是同一个。
  4. 正如一个人可以取多个外号,实体也可以有多个引用。
  5. 引用类型必须和引用实体是同种类型的

?

2、引用的价值

2.1 做参数

看代码:
输出型参数

void swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
//一些OJ题中,参数返回值
int* func(int* a, int size, &returnSize)
{}

有效避免二级指针的使用

typedef struct ListNode
{
	struct ListNode* next;
	int val;
}LTNode, *PLTNode;

//struct ListNode*& phead
void SlistPushBack(PLTNode& phead, int x)
{}

可见,有些指针能实现的地方,引用也能实现。
?

2.1 做返回值

引用的正确使用:

传值返回
先看一串代码:

int Count()
{
	static int n = 0;
	n++;

	return n;
}

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

要想清晰的理解这段代码,我们得借助函数栈帧知识。
?
在图中栈帧往下开辟,静态变量n存储在静态区,Count函数在返回后销毁,销毁之前会为一个临时变量开辟空间,返回的n=1作为一个临时变量放在寄存器中(数据较小将放入CPU的寄存器中,如果数据较大就会在上一个栈帧中开好空间存放),再将寄存器的内容返回给ret。

为什么要返回临时变量?
因为如果是单单普通的Int n 返回,函数中销毁后n也销毁,获取的n也就没有意义了。
在这里插入图片描述

引用返回

int& Count()
{
	static int n = 0;
	n++;

	return n;
}

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

Count栈帧销毁后,由于n存储在静态区,直接返回n的引用,这一过程虽然产生了临时变量但是返回引用所以不开辟空间。
在这里插入图片描述
?
引用的错误使用:

传值返回

int Count()
{
	int n = 0;
	n++;

	return n;
}

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

与之前传值相比,n的值存放在栈区随着Count函数销毁也会被销毁,但销毁之前,也会传一个临时变量给ret,这个临时变量开辟空间。

注意:这里的销毁指的不是开辟Count的那块区域被销毁了,而是依然能访问也能修改,只是非法的,并且那块区域的数据已经不受保护了,读取的数据都是不确定的,相当酒店退房一样。
在这里插入图片描述

?

引用返回
这是一个错误示范

int& Count()
{
	int n = 0;
	n++;

	return n;
}

int main()
{
	int ret = Count(); //非法访问
	return 0;
}

由于引用返回产生的临时变量不开辟空间,那么在n销毁后,返回的n的引用也就构成了非法访问。
在这里插入图片描述

总结

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

?

3、引用的意义

3.1 传引用做参数的意义

减少拷贝,提高效率,能修改参数
对函数传参,并不会对函数直接传实参,而是会传递一个临时拷贝的形参,尤其当数据量大时,造成效率低下。通过传引用不需要拷贝,很好的解决效率问题。

#include <time.h>
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 < 1000000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 1000000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

4w字节的数据,100w次的调用。
在这里插入图片描述

3.2 传引用返回值的意义

?
减少拷贝,提高效率,能修改参数
由于函数在返回值的时候,需要返回一个开辟空间的临时变量,在数据量足够多的时候,效率是比较低下的,而返回引用值的时候,返回的临时变量不需要开辟内存空间,这就提高了效率,但是得正确使用引用

#include <time.h>
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;
}

当数据量或返回次数越多,这个差距也会越大。
在这里插入图片描述

4、引用和const

4.1 引用的权限

const使得变量不能修改,使得变量只有只读权限,如果引用该变量,那么对应的引用类型也必须带上const,权限也只有只读。(权限不能放大)

如果变量有读和写的权限,那么引用的权限可以只读。(权限的缩小)

int main()
{
	int a = 0;

	// 权限平移
	int& ra = a;

	// 指针和引用赋值中,权限可以缩小,但是不能放大。
	const int b = 1;

	//拷贝
	a = b;

	//权限放大不行
	//int& rb = b;

	//权限缩小可以
	const int& rra = a;
	//rra++   //err
	a++;  //改变rra和a
	return 0;

}

const引用避免传参的限制
看代码:
因为权限的问题,在对参数是引用的函数进行传参,需要考虑实参权限是否可以对应。
为了在多种场景下都能使用该函数,许多的函数实现在设计引用参数时都加上了const起到保证作用。

//void Func(int& x){}

void Func(const int& x){}

int main()
{
	int a = 0;
	// 权限平移
	int& ra = a;
	// 指针和引用赋值中,权限可以缩小,但是不能放大。
	const int b = 1;
	//拷贝
	a = b;
	//权限放大不行
	//int& rb = b;
	//权限缩小可以
	const int& rra = a;

	//权限平移
	const int& rb = b;

	Func(a);
	Func(ra);
	Func(rra);
	Func(b);
	Func(10); //下一节会提到为什么可以

	return 0;

}

以上的函数调用都能在func(const int&x)中成功。

并且上面两个函数构成函数重载。
?

4.2 const 引用修饰常量

?
const 引用可以初始化引用常量

const int& b = 10;

所以之前Func(10)为什么可以调用,也就解决了。
?
截断、强转和整型提升都会产生临时变量

	double d = 12.34;

	cout << (int)d << endl;//打印临时变量

	int i = d; //(截断) 实际临时变量12赋予给了i

?
临时变量有常量的特性

int Count()
{
	int n = 0;
	n++;

	return n;
}


int main()
{
	const int& b = 10;

	double d = 12.34;

	//int& ri = d; //不行

	const int& ri = d; //引用临时变量 临时变量具有常性

	const int& ret = Count(); //函数返回临时变量
}

5、引用和指针区别

语法概念上引用和本体共用一块空间,指针需要开辟一块内存存储地址。
底层实现中,有空间的,它们实现都是一样的。本质引用和地址都是传地址,只是引用是由编译器做。

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

在这里插入图片描述
具体不同点:

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

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

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

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

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

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

  7. 有多级指针,但是没有多级引用

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

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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:08:17  更:2022-09-21 00:12:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 12:20:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码