C++练级之初级:第四篇

文章介绍了C++中的引用概念,作为已有变量的别名,不占用额外内存。引用常用于输出型参数,提高效率,特别是在处理大对象时。引用不能改变引用对象,且在定义时必须初始化。对比指针,引用更安全,无空引用问题。常引用是不能修改的引用。文章还探讨了引用的底层实现以及引用作为返回值时的注意事项。
摘要由CSDN通过智能技术生成

C++练级之初级:第四篇

1.引用

1.1引用的介绍

🤔首先还是一个问题,引用是解决C语言什么不足?

在这里插入图片描述
指针在,这里传参和访问变量有点麻烦, 能不能简单一点? 于是C++的大佬就发明了一种简单的语法:引用;

🤔🤔什么是引用?

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

在这里插入图片描述
就像李逵的外号叫“黑旋风”,“铁牛”,“李鬼”等等,他们都是一个人,对别名操作就是对其本身操作;

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

🤔🤔🤔那么我们在用引用的时候有什么问题要注意的吗?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


总结:

  • 引用是对某个实体起别名,操作的是同一块空间
  • 引用类型必须和引用实体是同种类型的;
  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,再不能引用其他实体,即对这个实体一直引用到最后;

注意: 这里不要和java搞混:java中的引用是可以改变指向的,C++中的引用是改变不了的,即一直是一个实体的别名;


1.2引用的使用场景

🤔既然了解了什么是引用,那么引用一般都用在什么地方呢?

  1. 引用做参数(输出型参数)

注解: 🤔🤔什么是输出型参数?
输出型参数:形参的改变影响实参;
输入型参数:形参的改变不影响实参;

在这里插入图片描述
在C语言中我们要实现这种输出型参数就一定要用指针,但是指针书写太麻烦,于是在C++中我们可以用引用类型作为形参来实现输出型参数;


1.1 引用做参数,可以提高效率(大对象/深拷贝类对象)

#include<iostream>
#include <time.h>
using namespace std;
struct A 
{ 
	int a[10000]; //40000byte
};
void TestFunc1(struct A a)
{

}
void TestFunc2(struct A& a)
{

}
void TestRefAndValue()
{
	struct A arr;//40000byte
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(arr);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(arr);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();
	return 0;
}

在这里插入图片描述

🤔🤔为什么在大对象或者深拷贝类对象,引用或者指针比传值调用的效率更高?

我们知道形参是对实参的一份临时拷贝,那么对于大对象而言,比如40000byte的数组,那么形参就会开销拷贝所用的时间,效率肯定要比传引用或者指针来的慢,因为传引用只需传别名,指针也只要传一个指针变量指向这块空间;


  1. 引用做返回值(提高效率),返回后可以对返回值进行修改
#include<iostream>
#include <time.h>
using namespace std;
struct A 
{ 
	int a[10000]; 
};
struct A a;
struct A TestFunc1()
{
	return a;
}
struct A& TestFunc2()
{
	return a;
}
void TestRefAndValue()
{
	// 以值作为的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

在这里插入图片描述

🤔🤔为什么在传大对象或者深拷贝类对象时,值返回的效率比引用返回的效率低?

注意:编译器是根据返回值类型来判断是否为值返回,若是值返回则都需要创建临时变量,临时变量可以是寄存器(返回的对象较小)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

因为值返回需要创建临时变量,而引用返回不需要创建临时变量,直接返回别名,所以在返回大对象时,引用返回的效率比值返回的效率高


🤔🤔🤔那么我们在引用返回时有什么注意点吗?

👇这两段代码有什么区别?

在这里插入图片描述

在这里插入图片描述

😁进入正题:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🤔🤔🤔这是为什么?

在这里插入图片描述

这个与这段代码类似:
在这里插入图片描述

解析:这里的ret接收到了n的地址,但是n这块空间被销毁了,只是vs并没有清理栈帧中的数据,所以通过n的地址可以访问到n,但这是侥幸访问的,因为右边的图可以看出,我们调用其他函数时,其他函数创建栈帧时就会覆盖那个栈帧的数据(覆盖),所以通过n的地址访问就是随机值,这种问题一般称为野指针的访问;


上面的问题类似(ret2访问的是一个销毁的变量)

  • 如果test2函数结束,函数栈帧销毁(使用权还给操作系统),没彻底清理栈帧,那么ret2的结果侥幸是正确的;
  • 如果test2函数结束,函数栈帧销毁(使用权还给操作系统),彻底清理栈帧,那么ret2的结果随机的;
    在这里插入图片描述
    总而言之,函数返回值为引用类型时,在vs中,函数栈帧销毁后(还给操作系统),函数栈帧不会清理(变成0xcccccccc),所以用值来接收是侥幸正确的,因为是简单的赋值操作,但在其他编译器中可能是错的(函数栈帧销毁后直接清理),那么用值来接收就会是随机值,在vs中如果用引用来接收更不推荐,因为下面一旦调用了其他函数,其他函数的栈帧就会覆盖上去,那么引用就会是随机值

总结引用的引用场景

1.基本上所有情况下都有可以用引用类型传参;
2.谨慎用引用类型做函数的返回值,除了对象出了函数作用域还在的情况下可以用;

下面就举几个正确的使用引用做函数返回值的正确应用:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3常引用

🤔常引用是什么?
引用的对象不能改变的引用,一般写法:在引用类型前加const,注意:常引用是不能改变的

错误的引用:
在这里插入图片描述
正确的引用:
在这里插入图片描述
在这里插入图片描述


🤔🤔这段代码是什么意思呢?

在这里插入图片描述
从上面的引用的注意点我们知道引用的类型必须相同,所以报错;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

所以当两个类型不同时,但是你一定要放进去就会发生类型转换,发生类型转化的时候会创建一个临时变量,这个临时变量存放的是一个转换过后的值(常性),所以要用常引用;

常性和常量是不同的
常性:const修饰的变量,本质是变量,具有长属性(不可改变);
常量:本质是常量,不可改变;

🤔🤔为什么运算符两边的数据类型不一致时要产生临时变量?

在这里插入图片描述

因为我们不能改变 i 本身的值,如果不建立临时变量直接进行类型转换的话,i的值就会变;

在这里插入图片描述

在这里插入图片描述


🤔🤔🤔那如果我就直接在正确的引用前加上const会发生什么?

在这里插入图片描述
在这里插入图片描述
那我们对a++,b++,c会不会变呢?

在这里插入图片描述

总而言之,就是如果在正确的引用前加上const,会导致加上const这个引用的权限缩小(不能改变a),但是本质上还是同一块空间,只不过加上const的那个引用改不了;

2.引用的底层

🤔引用的底层是什么?(现在先简单了解一下)

在这里插入图片描述

在这里插入图片描述


汇编层面:

在这里插入图片描述
底层上引用还是要开空间的
对a进行取别名本质上就是把a的地址给ra


注意:但是在写代码时我们要遵循语法,还是要认为引用没开空间;


3.引用的与指针的比较

🤔那么引用和指针有什么不同呢?

引用和指针的不同点:

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

在这里插入图片描述

总结

今天主要学习了什么是引用,引用的场景,引用的底层,引用和值得比较,引用和指针的比较,引用做返回值时的注意点,常引用等,如果喜欢本篇不妨留下一个❤️❤️❤️,下篇见!

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值