C++入门-----引用

1. 引用的概念

引用是给对象起一个别名,对引用进行操作相当于对对象进行相同操作

  • 注意

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。 但是在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

引用并没有开辟新空间,只是给原来的变量取了一个别名而已

下列输出结果是一样的

void Test()
{
  int a = 10;
  int& ra = a;//定义引用类型
  printf("%p\n", &a);
  printf("%p\n", &ra);
}
void Test()
{
  int a = 10;
  int* p1 = &a;
  int*& p2 = p1;//对a的地址引用
}

2. 引用的特性

  1. 引用在定义时必须初始化,必须指定是哪个对象的引用
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体(对比指针)
void TestRef()
{
  int a = 10;
  // int& ra;// 该条语句编译时会出错,没有初始化
  int& ra = a;
  int& rra = a;//可以有多个引用
}

3. 使用场景

3.1 做函数参数

下列三个函数构成重载,但是在调用的时候会有歧义,编译器调用不明确。
(为什么构成,解析在函数重载博客最后一项,修饰出来的函数名不一样)

//构成函数重载
void Swap(int x, int y)//传值
{}
void Swap(int* x, int* y)//传地址
{}
void Swap(int& x, int& y)//传引用
{}

3.2 引用做返回值

3.2.1 传值与传引用对比

普通的传值返回:生成一个返回值的拷贝,一般放在寄存器中。
引用返回:返回变量本身

下列函数返回c本身(引用)的话,就会造成返回随机值,因为Add调用结束时函数栈帧已经销毁并且c的栈帧空间也随之销毁,这时候再返回c的引用其实就是返回它本身,万一该地址里的数据被别的变量覆盖或者被编译器清理后,那么就可能会返回一个随机数,但是这些情况不发生的话就会返回原来的数据。

但是编译器不一定会检查得出来。

//传值返回。返回的是临时变量
int Add(int a,int b)
{
   int c = a + b;
   return c;
}
int& Add(int a,int b)
{
   int c = a + b;
   return c;
}
int main()
{
  Add(1,2);
  return 0;
}

关于下列代码

用ret来接收c,其实此时ret就是c,所以输出的就是c = ret = 3,那么再次调用Add的话,建立新栈帧,c被覆盖成了30,所以ret被改成30。因为c对应的栈帧空间不是自己的,所以别人随时可以覆盖你,会有被改动的风险(cout也会创建栈帧,说明没有覆盖到,调用printf可能就会把它覆盖掉)。

int& Add(int a,int b)
{
   int c = a + b;
   return c;
}
int main()
{
  int& ret = Add(1,2);
  cout<<ret<<endl;//ret = 3,编译器未清理
  Add(10,20);
  cout<<ret<<endl;//ret = 30,数据被覆盖
  return 0;
}
  • 引用对比指针优点

使用指针要考虑空指针野指针等问题,过于灵活,相对来说没有引用那么安全。

void f1(int* p)
{
  *p = 10;
}
void f2(int& r)
{
   r = 10;
}
int main()
{
    f1(NULL);
    f1(0);//上述方式有错但是不会发现,运行崩溃
    
    f2(NULL);
    f2(0);//编译层直接就发现了

    int a = 0;
    f1(&a);
    f2(a);
}

引用比较专一,初始化不可改变,而指针可以。在链表方面指针要优于引用。

3.2.2 有用的地方:输出型参数

观察下列代码,可以把函数返回值变成可读可写。

//传引用返回可以修改返回变量
//相当于对数组元素进行修改
int& A(int i)
{
	static int a[10];
	return a[i];
}
int main()
{
	for (size_t i = 0; i < 10; ++i)
	{
		A(i) = 10 + i;//写
	}
	for (size_t i = 0; i < 10; ++i)
	{
		cout << A(i) << " ";//读
	}
}

如果改成传值返回,那么就会报错,因为传值返回的方式是放在临时变量中
,这时候去修改的话,临时变量是右值,是常量,不可被改变;所以会报错。

而传引用返回是直接就是数组元素的别名,直接对其元素修改。

3.2.3 效率对比

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

#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}

数组a是一万个int数据,如果是值返回的话每次都要拷贝一万个int数据,而传引用返回是返回它别名;那么如果都调用一万次,对比之下传引用返回就能提高效率。

4. 常引用

  • 变量定义的时候
const int a = 10;
//a->b从只读不写到可读可写
int& b = a;//权限的放大,不可以
const int& c = a;//权限不变,可以

//权限缩小,可以
int d = 10;
const int& e = d;
  • 做形参的时候

const Type是通吃的

void Swap(const int& a)//如果不想改变a,就加const
  • 了解了权限的放大和缩小,再看看下列代码
double d = 11.11;
int i1 = d;//隐式转化,正确
int& i2 = d;//不正确
const int& i3 = d;//正确

为什么i2不可以,i3又可以引用?

都知道不同类型的变量可以赋值,会有隐式转换,它里面的原理是这样的:
d赋值给一个临时变量,临时变量进行隐式转化(截断)后再赋值给i1,这时候d是没有变化的。
在这里插入图片描述

所以这时候如果给d取别名,其实取的应该是临时变量的别名而不是d的别名,
而临时变量是右值,是常量,不可被改变;所以会报错。但是i3加了const被修改成只读不写,就满足了其要求,可以引用。

下面例子同理,赋值不是直接把值交给ret,而是创建临时变量拷贝给ret,
这时候取引用就会报错。

int x1 = 1;
int x2 = 2;
int& ret = x1 + x2;//取的是临时变量的引用

5. 引用和指针

5.1 不同点:

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

5.2 相同点

int main()
{
	int a = 1;
	int* p = &a;
	int& b = a;
	return 0;
}

转到反汇编观察
在这里插入图片描述

可以看出在汇编下,指针和引用的初始化和访问都是一样的操作,由此可以得出结论:引用在底层是用指针实现的。

  • 结论

在语法层:指针和引用完全是两个概念:

1.指针是开空间,存储变量地址。
2.而引用不开空间,仅仅是对变量取别名。

底层汇编:引用是用指针实现的

6. 总结

引用的作用主要体现在传参和传返回值

  1. 引用传参和传返回值,有些场景下面可以提高性能(大对象+深拷贝对象)—后续补充
  2. 引用传参和传返回值,输出型参数和输出型返回值。通俗一点说,有些场景下面,形参的改变可以改变实参,有些场景下面,引用返回。可以改变返回对象。—后续补充
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值