前言
在C语言中,我们学习过一个重要的概念——指针。今天,我们则要学习它在C++中的难兄难弟——引用。(一定要坚持看到最后哦,有甜甜的语法糖哦)
一、什么是引用
首先,我们来看一段代码
int a = 0;
int* pa = &a;
int& ra = a;
显然,我们今天的主角在第三行。
int& ra = a;
ra就是对变量a的引用 可我们要怎么理解引用呢? 举个例子:每个人都有小名,古人甚至还有名,号,子,字… 在C++的世界中,引用就是变量的小名(在上段代码中,ra就是a的小名)。 具体写法是:
引用对象的数据类型& 引用名=引用变量(大名)
二、引用的注意事项
1.一个变量可以有多个引用(一个人可以有很多小名)
int a = 0;
int& ra = a;
int& goudan = a;
int& wangerma = a;
2.引用也可以被引用 eg:李逵——黑旋风 但是我说:黑旋风又叫铁牛,实际上就是李逵叫铁牛。
int a = 0;
int& ra = a;
int& rra = ra;
3.引用必须初始化(他叫铁牛,他是谁??) int& ra; 4.引用一旦只向一个实体就不能再指向另一个实体了!!(劣势) (水浒传中只有李逵能叫黑旋风,宋江就不行…) 既然如此,那请看下面这段代码,并告诉我在这里ra是否指向了b?
int b = 10;
int a = 0;
int& ra = a;
ra = b;
———————————————————————————————————————————— 调试过程中(代码执行到第三行):
第四行执行后: 看到这里,是不是傻了? 其实,由于引用不能改变指向,这里的 ‘=’ 变成了赋值含义。 也就是说,
int& ra = b;
这句话的含义是:把b的值赋给ra,由于ra指向a,因此a的值也随之变化。 (比如我说:黑旋风是傻子,那么同时也就是说铁牛是傻子,李逵也是傻子) 5.从语法的角度上讲,引用不占内存空间 可以说,a是这块内存空间的原名,ra是别名。 6.指针和引用赋值过程中,权限可以缩小或不变,但不能放大。 权限不变:
int a = 0;
int& ra = a;
const int b = 10;
const int& rrb = b;
权限缩小: 可读可写——>可读不可写
int a = 0;
const int& rra = a;
权限放大 :
const int b = 10;
int& rb = b;
ps:权限的缩小仅针对引用而言,对原变量无影响 (引用只可读时,不影响原变量可读可写) 即 **rra++ **,a++正确,且原变量值改变时引用值也会改变!!! 用处: 1).做参数时,如果实参不需要修改,使用 const& 能接受所有类型数据。
void Func(const int& x);
而不加 const 时就不能接受所有类型
void Func(int& x);
但是注意,普通传参则可以接受任何形式数据:
void Func(int x);
why? 这里和前面一样,使用这种方式传参时,创建了一个新的变量x,然后将实参内容拷贝进去,因此不是权限问题(这就是我们所说的,权限的缩小和放大仅针对指针和引用而言) 2).使用const后,就可以引用常量了
const int& b=10;
3).使用const后,就可以实现 引用+缺省参数
void Func(const int& x=0)
4).在有隐式/显式类型转换的赋值过程中,必须使用const
double d=3.1415;
const int&a=d;
而,int& a=d 是错误的! (原因与5).相同) 5).在函数普通传值返回的过程中,必须用const接收返回值
int Count()
{
return 0;
}
const int& tmp=Count();
要想解释4),5),我们需要补充一些背景知识: 类型转换的过程中会产生一个临时变量保存转换后的值,因此表面上看是将d发生隐式类型转换后赋给a,实质上是将该临时变量中的值赋给a。 同理,在函数返回的过程中,也是通过一个临时变量保存返回值(这点我们将在下面提到),再将这个值赋给tmp。 而 该临时变量具有常性 ,因此在使用时必须加const。
三、引用的两大妙用
1.引用传参
原先我们知道的传参方式有两种——传值,传址 其中传址可以达到修改形参的同时修改实参的效果,现在我们有了一种新的方式——引用传参。 好处在于: 1.减少拷贝提高效率(不占用空间) 2.修改形参的同时修改实参 如下两处是等价的:
void Swap(int& left,int&right);
void Swap(int*left,int*right);
2.引用返回(只用于堆区或静态区变量的返回!)
这里我们首先要了解一种更常用的返回方式(传值返回):
int Count
{
return n;
}
我们都知道,函数结束时,函数栈帧会销毁(使用权限还给操作系统),我们还能进行访问,但是其中数据的安全性已经无法保证。那么,返回值是如何拿到的呢? *在传值返回时,程序结束前会创建一个临时变量(寄存器中或上层栈帧中),将返回值拷贝进该临时变量中保存下来。*所以,无论返回值是全局变量还是局部变量都能保证我们拿到正确的数据。 但是,引用返回只能用于堆区或静态区数据的返回!!!
static n=100;
int& Count
{
return n;
}
因为引用返回并没有生成一个新的临时变量,而是将原变量的别名返回,因此当原变量销毁时,它的别名也不复存在,就会导致返回结果未知(拿到原数据/随机值/被覆盖的其他数据) 这样看来,似乎引用返回并没有什么优势呢。 NONO, 1.引用返回可以减少拷贝次数,提高效率。 2.引用返回可以修改返回值(这一点用处可就大了) eg:实现顺序表中元素的展示和修改 现在可以使用一个函数完成
SLDataType& SlAt(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
return psl->a[pos];
}
SLDataType& tmp=SlAt(&sl,5);
tmp=100;
四、引用的底层实现
前面说,从语法的角度讲,引用不占内存空间。 但是,底层上引用是通过指针(指针常量)实现的: int& ra 等价于 int * const pa = &a ps:指针常量——指针指向的对象不可修改(但对象的内容可以修改),书写方式为: 在 * 的右侧加上const 这里,也就解释了为什么引用的对象不能修改。
五、引用 VS 指针(面试题)
六、小彩蛋——送你一块语法糖
今天送大家一块名为auto的语法糖,让你初步感受一下C++的智能性: 1.auto可以根据右侧的数据类型来推测左侧的数据类型
int b=10;
auto a=b;
在这里,auto的这个用法优势体现还不深刻,但是当数据类型过长时,使用auto就可以有效地降低代码冗余度~ 2.auto可自动判断循环结束,自动迭代 打印数组时,原先我们是这样写的:
int arr[] = { 1,3,5,7 };
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i++)
{
cout << arr[i] << ' ';
}
cout << endl;
可是,有了auto,我们就不再需要判断循环结束,自动迭代了(一定程度上避免犯错)
for (auto e : arr)
{
cout << e << ' ';
}
cout << endl;
好了,本期的所有内容就到这里了,下期我们Linux见,bye~
|