1、引言
在引出这个基本概念之前,我们还是以一个具体的例子来加以探讨,实现一个简单的交换函数。
用指针的方式实现
我们通常情况下的实现方式如下:
void Swap(int *a,int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(&a,&b);
}
从上面的代码我们可以发现他传递的是实参的地址。但是这种情况下可能会理解成为交换a和b的地址。
所以我们引出了另外一种更加易于理解和实现的方式
用引用的方式实现
void Swap(int &left,int &right)
{
int tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a,b);
}
这种方式就是通过引用的方式通过形参来修改实参的值。
1、引用的概念
简单来说,引用就是别名,在上一个swap交换函数的例子中,我们可以理解成给a和b重起了一个名字叫left和right.但是他们都指向的是同一个内存单元。
2、引用的特点
特点一、引用一定要初始化
简单理解一下,既然我们引用就是起一个别名,如果我们不对其进行初始化,也就不知道我们到底是要把哪一块儿内存单元叫成这个名字。
2.1引用的处理流程
1、从汇编代码上研究
为了探究引用的实质,我们使用下面这两段代码的反汇编进行研究。
int &b = a;
b = 20;
int *p = &a;
*p = 30;
反汇编结果如下:
从汇编代码中我们可以清楚的看到,引用的处理和指针在底层的处理是一模一样的。引用初始化和指针初始化以及引用的赋值和指针的赋值在底层处理没有区别。
2、引用的本质
其实在此,我们可以以更加容易理解的方式来解释一下引用,引用的本质其实就是语法糖——逻辑上简单,将复杂的过程交给编译器,但是看起来很好理解
【举个栗子】
从上述代码中我们可以清晰的看出引用的使用方法,c在底层实质上就是一个自身为常性的指针。下面我们再来看一个引用逻辑和底层的对比
2.2 引用和指针的区别
有了上述关于引用处理流程的探讨,我们发现引用其实和指针有着千丝万缕的联系,下面,我们就来总结一下他们之间的区别吧
从汇编角度分析
:引用就是指针操作,定义一个引用变量,相当于定义了一个指针,然后把引用内存的地址写到这个指针里面,当通过引用变量修改它所引用的内存时,它先访问了指针里面的地址,然后在这个地址的内存里面修改值。从初始化角度
:指针可以不初始化,通过赋值可以指向任意同类型的内存。但是引用必须初始化,而且引用一旦引用过一块内存过后,就不能再引用其他内存了从sizeof操作角度
:izeof指针在32位的操作系统下永远都是4字节,而sizeof引用时要通过计算得到它所引用的内存大小。级别上
:引用只有一级引用,没有多级引用,指针可以有一级指针,也可以有多级指针
具体的参见博文c++中指针和引用的区别
特点二、引用 不能引用 不能取地址的数据
因为在引用的处理流程中,我们从汇编的角度分析得到了,定义一个引用变量就相当于定义了一个指针,把引用内存的地址写到这个指针里面去.
【举个栗子】
int a = 20;
int &b = a;
所以当我们引用了一个不能取地址的数据,这个引用就不知道自己到底引用的是哪儿块内存了。进而也不能进行后面的修改操作。
一般的,像立即数还有寄存器都是不能引用的
特点三、引用是不能改变的
也就是说引用一旦引用过一块内存过后,就不能再引用其他内存了。就可以理解成为同一个指针不能同时指向两块不同的内存
特点四、引用变量只能操作引用变量所引用的内存单元
通过如下的代码来理解一下
int a = 10;
int &b = a;
cout<<&b<<endl;
上述代码的输出结果为a的地址。从中可以看出,针对引用的操作,所有的操作只能操作引用变量所引用的内存单元(在这例子中,所有的操作都是操作的a,不能操作b本身的这个内存单元)
3、const 和引用的结合
本身const 和引用的结合就会有优化产生,将其结合的产物叫做常引用,可以引用不能取地址的数据。比如下面这个代码就是正确的
int main()
{
const int& b = 10;
return 0;
}
在这个例子中像10这样子的立即数不能取地址的数据都可以被常引用引用了。
因为他是把不能取地址的数据放在一个临时量中,常引用引用的是临时量
4、普通变量指针和引用
分析下面这段代码是否正确
int getValue()
{
int tmp = 10;
return tmp;
}
int main()
{
int a = getVaule();
int& b = getVaule();
int* p = getVaule();
return 0;
}
上面这个代码 int& b = getVaule();错误,因为引用不能引用不能取地址的数值。int* p = getVaule();也是错误的,因为寄存器没有办法取地址
【改进一】
int* getValue()
{
static int tmp = 10;
return &tmp;
}
int main()
{
int a = *getVaule();
int& b = *getVaule();
int* p = getVaule();
int *&pb = getVaule();
return 0;
}
只有int *&pb = getVaule();是错误的。因为整个getValue函数可以表示成下列的方式
getVaule()的过程就相当于通过当前内存存放的地址找到对应的内存地址。所以getVaule()相当于tmp,getVaule()相当于eax;
对应关系如下:
【改进二】
int& getValue()
{
static int tmp = 10;
return &tmp;
}
int main()
{
int a = getVaule();
int& b = getVaule();
int* p = &getVaule();
return 0;
}
这种方式使用都是正确的,因为以引用的方式返回就是把别名返回出去了,可以理解成为整个getValue的返回就是tmp的别名
对应关系如下: