1.介绍
引用(int&)是C++中添加的用法,当然我说的是基于C,我其实也不知道是不是它第一个添加的,听我的老师说现在有些语言都没有指针了,只有引用,这是应为它较于指针确实要安全,而且指针嵌起套来确实抽象,即使我写过蛮多次了在看见传入二维指针要求修改他的一维,并构建一个数组这样的操作时依旧会一愣,解引用操作也是麻烦,然而引用它会更加方便
引用的写法很简单我们只需要在常规类型的最后加上&就是引用的类型了
int a = 0;
int& c = a;
引用类型被认为是引用值的别名,就好像你叫林小鸟,人称张大帅,网名六花一样,不同的名字但都是同一个你,引用的核心就在于不同的名字操作共享内存
简而言之就是操作一个所有的名称对应的值都会一起更改,引用类型的特性有很多让我来稍稍总结下:
1,变量必须在定义时初始化(初始化的操作认为是指向那个值,后面无法更改指向)
2,初始化的对象应当是左值(很好理解他应当指向左值,不然无法操作)
3,不会开辟地址,而是使用原先值的地址
4,可以引用除数组以外的所有类型
5,引用变量拥有独立的作用域(特别注意传值)
6,static不会对它起作用(因为会重新初始化,就算能保留生命周期也没一点用)
7,类型的值必须是左值(无法转换右值为引用)
8,const的神奇修饰
其实大家不难发现前两点是使用类的规定,而后面的才更接近特性的范畴,确实从语言的角度来看不是很严谨,但也不必纠结,因为他们同等重要,那么让我先聚焦使用来看看引用
2.使用
对于引用的变量我们在使命时得同步进行初始化
int c = 1;
int& a;
a = c;
//等着报错吧您
int& b = c;
//必须初始化
int arr1[1];
int& arr2 = arr1;
//不行,因为数组不能引用,并且数组变量是常量(右值)
同时初始化的值需要是左值
int c = 0;
int* p = &c;
int& a = c;
//可以
int& b = *p;
//也可以因为是左值
//如果你不知道什么是左值,请去好好了解下,这很重要
//或者你可以去看我的上一篇博客,在后面我有讲到相关内容
修改一个时其他的也会改变,因为本质是存储位置相同的变量,修改存储所在的值,所有名词访问的地址其实是相同的,值也是相同的
int c = 1;
int& a = c;
int& b = a;
//此时
//a==1
//c==1
//b==1
b = 10;
//此时
//a==10
//c==10
//b==10
//同时
//&a == &b == &c
常见的用法便是我们可以将引用类型作为函数传参,来代替指针操作到该变量
void sweap(int& n1 , int& n2)
{
int temp = n1;
n1 = n2;
n2 = n1;
}
//引用的写法
void sweap(int* n1 , int* n2)
{
int temp = *n1;
*n1 = *n2;
*n2 = *n1;
}
//常规指针的写法
//哪个方便一目了然,觉得差不多的人等你需要确认空指针的时候有你好受的
它其实主要就是平替指针的作用,了解指针的很快就能上手,对于这点没什么好说的,大家这么聪明肯定一看就能举一反三
返回引用也是一个常用方法
int& ret()
{
static int a;
return a;
}
//在函数内return的值也需要是一个左值
//返回一个左值,可以用于引用(因为引用的初始化一定要左值)
请注意不要返回局部变量的引用,此时视作(悬空引用)错误,他的生命周期依旧过了你却还在取他的值,如果编译器不会在释放后立刻改变这块内存存储的值那么或许暂时可以正常访问,但对他引用或者取地址却是非常有问题的
3.特性
作用域
首先是对于引用的每一个变量来说他都有属于自己的作用域,作用域是单独存在的,通常来讲由于必须即使初始化的原故,引用的内容总是在之前,其自身作用域和生命周期也是会覆盖,(全局和静态有些许不同,这点请自己去思考,不是很重要,引用变量是静态他的引用不是静态,也是没有关系的,因为作用域相同,就算是在函数内也不会发生悬空引用,因为会重新初始化,而在外面也没有机会用到静态变量,也就是说引用变量的静态修饰几乎没有用,反正我没有看出来有什么用,但无论如何可以总结出通常都是生命周期更长这个简单的道理),不过有两个点比较特殊,一个是函数传值,一个是函数返回值,对于函数传值来说它相当于是扩展作用域,在函数内我们使用定义的引用变量可以访问到该内容,可以看作是对作用域的扩展,不过此时因为是调用函数,还在该函数之内,生命周期还是没有过是作用域的扩展.而函数返回可以返回任意左值,包括局部变量,这就很搞,因为他的生命周期已经过了,但是传值将他的作用域给带出来了,直接诈尸了.这是觉对错误的因为得有生命周期才能有作用域,这样会发生(悬空引用).
int& ret()
{
int a=0;
return a;
}
//你敢接就是引用悬空(不会报错,语法认为正确,十分的抽象)
左右值与强制类型转换
在这里我不准备讲什么是左值,请大家自己去了解
对于引用的变量来说它的初始化需要赋一个左值,在强制类型转换时我们在转换的时候只能将左值转换成引用类型,有人可能不理解这有什么用,实际上在C++中强制类型转换是可以分左右值的,假如对一个左值进行类似于直接转换的写法他会抛出一个错误,因为转换被认为是运算,通常会得到一个右值这点C也是一样的,而我们要加上引用的转换来标记左值,这其实很好理解因为引用的类型必定是左值,假如你想对一个左值进行强制类型转换同时赋值那么需要使用引用的强制转换,你可以将一个左值转换认为是右值,但却不能反之因为右值兼容左,而左值不兼容右
小结一下在,C++强制类型转换的引用版本其实对应左右值的标记有引用的是左反之是右
int c = 0;
(long long&)c = 1;
//好笑吗,越界你就笑不出来了
//相当于一个临时别名,但是可以改类型,新的越界玩法就这么出现了
//只要是左值就可以强制转换,这点不比指针安全
加上const的特性
首先我们可以将无const的引用或者单纯变量,反正是左值就行传给左值
int a;
int *p = &a;
int& b = a;
cosnt int& c = a;
cosnt int& d = *p;
cosnt int& e = b;
//都可以,访问权限的缩小
但无法将有cosnt的左值传给没有的引用
cosnt int a;
cosnt int *p = &a;
cosnt int& b = a;
int& c = a;
int& d = *p;
int& e = b;
//都不行,访问权限的放大
这点指针是一样的,非常的合理,因为传递时可能出现无意间放大权限操作,就算一定要传递也可以使用强制类型转换
抽象的是对于const 类型的引用它可以接受常量值,当我们接受常量值时我个人推测他其实是会偷偷开辟一快空间的,但我不能说不确定只能说是乱猜的,没有依据甚至有反例
const int a = 0;
const int& n1 = a;
cout << a << " " << n1 << endl;
(int&)a = 3;
cout << a << " " << n1 << endl;
(int&)n1 = 2;
cout << a << " " << n1 << endl;
const int& n2 = 1;
(int&)n2 = 2;
cout << n2 << endl;
//大家可以去试一试看看会运行出什么结果出什么结果
//vs上应该是
//0 0
//0 3
//0 2
//2
//去掉a的const就是
//0 0
//3 3
//2 2
//2
//非常的正常
//至于n1的cosnt是单独去不了的,因为属于权限的放大
最离谱的在于当我们修改a时结果n1却被修改了而a却是一直不动,这点我也不理解,如果有大佬知道请不吝赐教我真的好奇,而它在传递正常的非const时也是可以正常的运行的,这就比较抽象