前言
相信大家都看过水浒传,里面的英雄人物除了自己的名字外都有自己的称号,比如:花和尚——鲁智深,豹子头——林冲……,这里我们发现我们人都有自己的别名,那么变量呢,其实变量也有给自己取别名的功能,那么小编今天就给大家介绍一下这个功能。
引用
1.引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间
首先给大家讲解一下类的声明与定义方式:
类型& 引用变量名(对象名) = 引用实体;
那么这里小编简单的给大家演示一下:
#include<iostream> using namespace std; int main() { int a = 10; int& c=a;//这里指的是c是a的引用类型 cout << c << endl; }
此时我们看一下输出c的结果是:
注意:引用类型必须和引用实体是同种类型的 。
2.引用特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
这里小编简单的给大家演示一下:
#include<iostream> using namespace std; int main() { int a = 10; int& c=a; int& b = a; int& d = a; cout << c << endl; cout << b << endl; cout << d << endl; }
这里我们运行一下:
3. 引用一旦引用一个实体,再不能引用其他实体
3.引用的权限
对于引用的权限,我们只能进行权限的平移或者缩小,不能进行放大操作,那么这里具体指的是什么意思呢?这里大家跟着我一起理解一下。
这里我们要使用到const关键字,这里的作用就是让变量具有常数性质,也就是我们不能够去改变此变量的权限,也就是将一个变量的权限缩小了。
#include<iostream> using namespace std; int main() { int a = 10; int& c=a;//权限平移 const int& d = a;//权限缩小 cout << c << endl; cout << d << endl; }
这里我们运行一下看看有没有什么问题:
那么这里我们再看看权限放大会出现什么情况:
#include<iostream> using namespace std; int main() { const int a = 10; int& b = a; }
这里我们运行看看结果:
这里我们看到这里出现了问题,所以我们这里的是不能进行权限放大的操作的.
但是这里还有一个特殊场景:
这里小编给大家写一串代码:
#include<iostream> using namespace std; int fun(int a, int b) { return a + b; } int main() { double a = 1.1; int& b = a; return 0; }
运行一下,我们发现:
这里涉及到隐式转换,大家不明白的话可以去看看小编另外一篇关于隐式转换文章的介绍,这里我们看到这里提示我们是非常量限定,这是什么原因呢?在C语言发生隐式转换的时候,该实际上是创建一个临时变量,然后将我们需要转换的值拷贝到临时变量,然后赋值给其他变量,但是我们的临时变量具有常数性质,既然临时变量具有常数性质,那么我们发生这样也就使其权限变大,这样就导致了其错误产生。所以我们在发生整型转换的过程中使用引用就需要我们加上const,也就是:
4. 使用场景
既然我我们已经学习
4.1 做参数
#include<iostream> using namespace std; void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } int main() { int a = 10; int b = 20; Swap(a, b); cout << a << endl; cout << b << endl; return 0; }
这里我们用引用类型写一个交换函数,这里我们看结果:
4.2 做返回值
#include<iostream> using namespace std; int& Count() { static int n = 0; n++; return n; } int main() { cout << Count()<< endl; return 0; }
这里我们返回值是n的引用类型,所以我们得到的是和n使用同一块地址的引用类型,该返回结果如下:
这里我们再看一个例子:
#include<iostream> using namespace std; int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :" << ret << endl; return 0; }
这里按照函数逻辑,大家可能都知道这里我们得到的值是3,那么我们这里运行一下:
这里我们发现我们虽然我们接收的是我们第一次函数的返回值,但是我们得到的是第二次调用的返回值,这是什么原因呢?大家接下来请听小编细细给大家解释:
首先我们要明白,这里的返回值是变量c的别名,这里我们由于我们的ret变量也是引用类型,所以这里我们ret也是变量c的别名,所以我们ret实际上和c共用同一块空间,但是这里我们需要考虑一点就是当函数结束调用后,我们变量c是会被销毁的,但是这里ret依然记录的是原先c变量的址,那么我们这里就需要考虑编译器调用结束后是否会马上对函数栈帧进行清理,如果没进行清理那么该得到的值将会恰好正确,但是如果进行了清理那么我们得到的值就是随机的。
那么这里很明显,这里我们并没有对函数栈帧进行清理,那么我们又是怎么得到:7这个值的呢?这里我们由于ret记录的是变量c的地址,由于我们的编译器没有及时清理函数栈帧,所以我们得到ret地址的值是5,由于再次调用同个函数,由于这次调用的函数和之前的函数是同一个函数,所以我们ret的地址和该函数中变量c是恰好是同一个地址,所以ret的内容也就是此时变量c的内容,所以得到了7这个值,那么如果我们调用了其他函数后我们ret的值是多少呢?
#include<iostream> using namespace std; int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); printf("haha"); cout << "Add(1, 2) is :" << ret << endl; return 0; }
运行之后看结果:
这里就很明显的说明了ret是变量c引用,但是c是其他函数的函数栈帧中。
所以这里我们对于其使用做一个小小的总结:
1.基本任何场景都可以用引用传参。
2.谨慎使用引用做返回值,出了作用域,对象不在了,就不能用引用返回,还在就可以使用引用返回
既然这里我们涉及到引用类型做形参以及返回类型,那么对于我们正常的传参和返回,它的优势又在哪里呢?
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
5.引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
这里我给大家罗列一些其区别:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全