c++入门——引用
在这一节当中我们要了解以下几个问题:
1. 引用的使用
2. 引用的具体逻辑
3.引用的注意事项
4.引用应用的实例
一、引用的具体使用
在c++之父,对指针的的复杂性和指针动不动就出现野指针的情况已经深恶痛绝很长时间了,所以就产生了引用这个东西,因为引用这个东西可以避免解引用等复杂操作。
就比如,swap函数:
void swap(int * a,int * b)
{
int tem = *a;
*a = *b;
*b = tem;
}//在C语言中我们只能这样用指针来进行相应的修改,容易出现空指针等错误
void swap(int& a,int& b)
{
int tem = a;
a = b;
b = tem;
}
//可以发现我们使用了一种新的语法,进行相应的参数设置,可以直接传入对应的变量并不用传入地址之类的东西方便了很多
调用它的时候我们就可以这样操作:
可以看到我们直接将变量传入,用我们新的语法引用即将数据类型加上取地址符号就能得到对应的
即可接收,所谓引用就是对一块空间进行取别名,我们通过这个别名就能对空间的内容进行改变,无论是在函数内部还是在主函数的局部域都可以进行相应的更改。
那如此好用的语法我们该如何使用呢?
基础用法
谈及用法我们就应该回归最简单的 例子,对变量起别名就是引用方法:
类型名+& + 别名
一些问题
应当注意的是以下情况:
- 进行变量取别名的时候,是否用初始化??
- 在进行相应的操作的时候能不能使得这个别名的指向空间发生改变?
- 能不能对不同空间起同一个别名?
首先,第一个问题,引用在定义的时候就要进行相应的初始化因为空引用,在c++中是不被允许的。其次,在你已经对一个空间去了别名的时候,你就已经无法决定这个别名的其他归属了,就比如下面这段代码。
其中的c = b
语句并不是将别名的指向改成b变量而是将b的值赋给c别名指向的a空间,因为引用的特性就是直接操纵别名的空间内容并不需要类似于指针的解引用,所以引用的别名所指定的空间,只能在一开始就进行确定不可以,中途改变,因为第一次使用即定义的时候 是确定方向,其他的时候,拿出来就直接开始用了。
然后,是最后一个问题,同名引用,这个更加不靠谱了,同名变量都不能有,同名引用会把编译器搞崩溃的。
使用场景
作为函数输出型参数进行使用
就类似于在oj题中的returnsize一样,可以返回到主程序中进行使用也就是在函数中进行修改,在外边的值也会发生改变。
就比如全局变量,静态变量传入指针等。当然也可以对指针进行引用。
作为大类型的数据传参进行使用,有助于深浅拷贝等的传参效率
就比如一个类型特别大的结构体,我们需要将这个数据传入函数的话,正常流程需要,拷贝后再传入,但是因为数据类型过大,传参的效率当然低下,所以要用引用进行传入,这样不会产生较大的拷贝消耗只会有别名的消耗,极大的提升了效率。
作为函数的返回值进行返回
因为在函数进行返回的时候,无论是返回全局变量,静态变量,还是临时变量,都需要产生临时变量将函数中的值带回去,一般是小对象在寄存器中,大对象消耗内存。所以说为了避免消耗
#include<iostream>
#include<vector>
using namespace std;
vector<int>arr;
int n;
vector<int>& change(vector<int>&arr)
{
for(int i = 0;i < n;i++)
{
arr.push_back(i);
}
return arr;
}
int main()
{
cin >> n;
vector<int>heh = change(arr);
for(auto& j : arr)
{
j++;
}
for(auto i : arr)
{
cout << i << endl;
}
return 0;
}//就比如这段代码其中使用了vector它的类型就很大,如果是直接返回原本类型的话则会对这个类型进行复制,会影响程序效率
但是值得注意的是,并不是所有函数都能用这种函数引用的返回,因为函数栈帧返回引用的时候并不会管所指向的空间是否归还 给了编译器,只是产生临时变量,返回回去,在主函数中还是能访问,只不过这是已经销毁的栈帧,如果再调用函数建立栈帧的话,会将这块空间覆盖,产生随机值。所以说,返回引用的时候一定要考虑,返回之后这块空间是否还存在。
二、引用的具体注意事项
其实所谓引用底层是根据常指针进行相应的实现的,所以会有一些让人匪夷所思的场景发生,我们需要注意:
1.关于常引用问题
所谓常引用问题就是在正常引用前加上const关键字来对相应的权限进行限制,那常引用应该怎么进行初始化呢??这里就牵扯出了关于 权限缩放的问题,就是正常的变量一般有这两种权限一种是访问权限,一种是修改权限。众所周知,在变量加上了const修饰之后变量就无法进行相应的修改了,只能进行访问。所以说在引用中加上了const进行修饰就会使得引用无法进行更改,也就是用于接收无法进行更改的变量以及常数之类的数据。
那就会有以下代码
const int& x = 0;
const int& n = x;
因为,加了const变量我们称作常变量,所以说,常引用用于接收常数也是可以的
所以就有:
const int& a = 3;
这个可以的原因是,在程序中出现相应的常量的时候,编译器会将常量找一个地址放起来,类似于常变量。
了解了上边的情况之后,那接下来我们来看看一个有趣的现象:
我们可以看到,当我们用整形的引用来接受双精度浮点型的数据的时候我们报错了,那这个原因是什么呢,可能这时候就有长得帅的读者说了,想咩呀,类型不一样可定不能接受呀。那您看看下边这个例子呢:
我们可以看到在用了常引用的时候我们的编译器并没有报错,那是为什么呢?下面我们从用int类型接收doubble类型进行讲起。在用int接收的时候,会发生类型转换但是这个类型转换,也是会产生临时变量的,具体过程见下图
其中临时变量也相当于一个常数被编译器找到个地址随即存放,所以在类型转换的时候引用性质被判断为常性,所以要用常引用进行接收。
三、引用的具体逻辑和实现
在语法逻辑上,引用是不占用空间的,是和原来变量使用一块空间的
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& c = a;
cout << "a的地址" << &a << endl;
cout << "&a的地址" << &c <<endl;
return 0;
}
我们可以发现引用的地址和变量的地址是一样的,在逻辑上是使用同一块空间的,但是引用的底层逻辑是用指针进行实现的,所以说还是有占用空间的。
对比两段代码,我们可以发现,汇编代码基本一致,所以我们可以猜测引用的底层逻辑是指针。
四、指针和引用的区别
- 指针较为复杂有多级指针,但是引用没有多级引用
- 指针可以存在空指针的情况,但是引用没有空引用
- 引用是变量的别名,而指针存放的是变量的地址
- 引用必须初始化,但是指针不用
- 引用一旦确定就无法进行更改,而指针可以随意更改指向
- sizeof引用时结果是引用类型的大小,而指针时就只有固定大小
在三十二位平台是四个字节大小,64位是八个字节
- 引用自增是对引用的自增,而指针自增则是移向下一个类型大小后的地址。
五、后记
本人学识有限,如有疏漏请指正