引用到底在我们的背后做了什么?
最近项目代码中有用到后值引用,一直想钻研一翻,但是有两个基础的问题始终没有找到答案,今天在借助度娘的实力后,再稍加研究,终于明白,在此记录下来,以在以后需要查看的时候留下痕迹,也给正在学习的朋友相互交流的机会;
1.首先我们看一下非常量左值引用,代码如下:
int
_tmain
(
int
argc
,
_TCHAR
*
argv
[
]
)
{
int
i
=
100
;
const
int
n
=
200
;
int
&
rLeft_a
=
i
;
//左值引用
int
&
rLeft_a2
=
100
;
//error C2440: “初始化”: 无法从“int”转换为“int &”
int
&
rLeft_a3
=
n
;
//error C2440: “初始化”: 无法从“const int”转换为“int &”
return
0
;
}
编译,有两个错误,很多同学一看就知道是什么问题,但是我们还是分析一下,加深一下印象:
1)第一个错误,就是用int &rLeft_a2 = 100; 编译器的错误提示,不是很友善,无法从int转换为int &, 但是我们左值引用不就是把type&来绑定type类型吗?是的,但是我们看一下引用的定义,左值引用需要绑定的是一个对象(左值) ,
a>什么是左值呢?左值 就是恒久存在的
b>100在这,它是一个字面常量,他是没有内存空间的,只能放在"="右边的,所以它应该称做是右值; 为什么我们的左值引用不能绑定到右值上呢?右值通常是临时的对象(如果把每个数据类型看作一个数据模型的话,那通过数据模型创造出来的每个变量我们就姑且称它为数据模型的对象,个人觉得如此容易理解记忆),它可能在出现所在的语句结束后就会释放(析构), 用左值引用,再次访问,可能访问到已经释放(析构)的内存对应的空间,那将可能会导致一个未知的错误,这也是编译器绝对不允许的;
2)第二个错误, int &rLeft_a3 = n; 编译器给的提示相对友好,无法从 “const int” 转 换 为 “int &”, 从上面的语句 const int n = 200;我们可以看出n 是一个具有const属性的常变量,也有人直接称之为常量,const的作用是让它值不可改变, 如果我们定义一个非常量左值引用绑定到一个常变量上,那我们在改变引用的同时,是不是就等于改变到常变量的属性上了呢!那const属性就没有起到相应的作用, 因此一个非const引用也是绝对不可以绑定到一个常变量的引用上的;
2. 常量左值引用 const &:
int
_tmain
(
int
argc
,
_TCHAR
*
argv
[
]
)
{
int
i
=
100
;
const
int
n
=
200
;
const
int
&
const_rLeft_a
=
i
;
const
int
&
const_rLeft_a2
=
n
;
const
int
&
const_rLeft_a3
=
100
;
return
0
;
}
上面的代码编译,完全通过没有问题,const常量左值引用可以直接绑定到常量左值, 非常量左值,和常量右值上;
对于前面的两行代码, const常量左值引用可以直接绑定到常量左值, 非常量左值,很容易推演出来,第三行,有人就要问了,为什么一个const左值引用就可以绑定到一个字面值常量上呢,一个字面值常量都没有放在内存中,没有空间,绑定又是怎么实现的呢?下面我们通过Visual Stdio 的反汇编功能查看一下汇编代码,如下:
对于前面的两行代码, const常量左值引用可以直接绑定到常量左值, 非常量左值,很容易推演出来,第三行,有人就要问了,为什么一个const左值引用就可以绑定到一个字面值常量上呢,一个字面值常量都没有放在内存中,没有空间,绑定又是怎么实现的呢?下面我们通过Visual Stdio 的反汇编功能查看一下汇编代码,如下:
int
i
=
100
;
001113
D8
mov
dword
ptr
[
i
]
,64
h
const
int
n
=
200
;
001113
DF
mov
dword
ptr
[
n
]
,0
C8h
const
int
&
const_rLeft_a
=
i
;
001113E6
lea
eax
,
[
i
]
001113E9
mov
dword
ptr
[
const_rLeft_a
]
,
eax
const
int
&
const_rLeft_a2
=
n
;
001113
EC
lea
eax
,
[
n
]
001113
EF
mov
dword
ptr
[
const_rLeft_a2
]
,
eax
const
int
&
const_rLeft_a3
=
100
;
001113
F2
mov
dword
ptr
[
ebp
-
48h
]
,64
h
001113
F9
lea
eax
,
[
ebp
-
48h
]
001113
FC
mov
dword
ptr
[
const_rLeft_a3
]
,
eax
const常量左值引用绑定到常量左值, 非常量左值 ,都是直接把变量的地址存至寄存器eax中, 再将eax寄存器中的地址对应的内容,赋值给const引用变量; 我们再看看const int &const_rLeft_a3= 100;这一句反汇编后,代码的和前面略有不同, 他是先将100(也就是64h,十六进制64就是100)存在ebp-48h这样一个临时的地址中,我对汇编不太懂,暂且把它理解为,编译器偷偷开辟了一个临时空间,地址为ebp-48的偏移 值,用于存放100这个字面常量,此时的字面值常量已经完全的转化成了一个临时对象,再将地址传至寄存器eax中,再将eax寄存器地址对应的空间数据存放在const_rLeft_a3的地址对应的空间中;其实它之所以可以绑定到一个字面值常量上,是因为编译器在背后所做的手脚,将字面值常量偷偷转成了一个临时对象,这个临时对象的生命周期应该也会根据cont & 的绑定,生命周期将延长至引用对象的生命周期一致,在C++11之前没有右值引用,通过const左值引用来实现对一个字面值常量来实现绑定;
const常量左值引用绑定到常量左值, 非常量左值 ,都是直接把变量的地址存至寄存器eax中, 再将eax寄存器中的地址对应的内容,赋值给const引用变量; 我们再看看const int &const_rLeft_a3= 100;这一句反汇编后,代码的和前面略有不同, 他是先将100(也就是64h,十六进制64就是100)存在ebp-48h这样一个临时的地址中,我对汇编不太懂,暂且把它理解为,编译器偷偷开辟了一个临时空间,地址为ebp-48的偏移 值,用于存放100这个字面常量,此时的字面值常量已经完全的转化成了一个临时对象,再将地址传至寄存器eax中,再将eax寄存器地址对应的空间数据存放在const_rLeft_a3的地址对应的空间中;其实它之所以可以绑定到一个字面值常量上,是因为编译器在背后所做的手脚,将字面值常量偷偷转成了一个临时对象,这个临时对象的生命周期应该也会根据cont & 的绑定,生命周期将延长至引用对象的生命周期一致,在C++11之前没有右值引用,通过const左值引用来实现对一个字面值常量来实现绑定;
个人理解:(欢迎大神给个更权威,更通俗易懂的解释)
左值:持续存在,强调的是内存所在的空间; 右值:临时对象,即将被销毁,生命周期短,强调的是对象的值;
左值:持续存在,强调的是内存所在的空间; 右值:临时对象,即将被销毁,生命周期短,强调的是对象的值;