学习过C语言的小伙伴会同时接触这两种概念,指针&引用。其定义为:指针是所指内存的地址,引用是别名,引用必须初始化。
接下来我们站在汇编的角度来分析指针和引用。
#include<stdio.h>
int main(){
char *p; //声明一个指针p
char a = 'a';
p = &a; // 引用a的值赋值给p
return 0;
}
上面C语言对应的汇编程序:
0x0000000000401531 <+1>: mov rbp,rsp ;栈顶栈底指向同一位置
0x0000000000401534 <+4>: sub rsp,0x30 ;分配栈空间,向下分配
0x0000000000401538 <+8>: call 0x4020e0 <__main> ;调用main函数
0x000000000040153d <+13>: mov BYTE PTR [rbp-0x9],0x61 ; 初始化一个字节空间,起始地址为rbp-0x9,a对应的ASCII码为0x61
0x0000000000401541 <+17>: lea rax,[rbp-0x9] ;将rbp-0x9的对应的地址值赋值给rax。此时rbp-0x9的对应的地址值也就是变量a在栈中对应的地址。可以将rbp-0x9看做&a。
0x0000000000401545 <+21>: mov QWORD PTR [rbp-0x8],rax ;为指针分配空间,该空间内的值为rax记录的地址值。可以将rbp-0x8看做p。
0x0000000000401549 <+25>: mov eax,0x0 ;准备return
0x000000000040154e <+30>: add rsp,0x30
0x0000000000401552 <+34>: pop rbp
0x0000000000401553 <+35>: ret
从上述代码可以看到,在汇编语言角度,引用和指针没有区别,可以使用相同的运算符(地址运算符)来表示。
But 在C++层面:
- 引用区别于指针的特性都是编译器约束完成的,在汇编层面引用和指针是相同的,都是内存地址。
- 指针是实实在在的变量,有自己的内存存储空间(可以存储在数据段中),它可以指向任何有效的变量;引用变量本身没有自己的实际存储空间(可以存储在代码段中),操作引用变量,就是在操作实际变量。
- 指针可以不初始化,通过赋值可以指向任意同类型的内存;但是引用必须初始化,而且引用一旦引用一块内存,再也不能引用其它内存了,即引用不能被改变。
历史渊源:
本来之父也和你一样想的,有指针就够了,一样能实现相应的功能,没必要再多一个语法设施。但后来为了加运算符重载,没有引用的话,前自增的语义就难以说明清楚,这是引入引用概念的历史背景。后来你可以发现,线性容器所重载的下标运算符,迭代器和智能指针所重载的间接访问运算符,输入流和输出流的链式调用,这一切都是离不开引用语义的。 抛开运算符重载的历史因素,引用在大多数场合下完全可以视作指针的语法糖——在底层的汇编的层面讲他们是一样实现的。不过有了引用以后,确实写代码可以方便很多。比如最常见的手法就是利用引用免掉一阶的指针。在 C 里面,你要在函数中修改一个一级指针,形参里得声明成二级指针,但在 C++ 里形参声明成一级指针的引用就可以了。别看只是降了一阶,但人类的思维理解高维的概念很困难,问题降一阶以后考虑问题就可以轻松很多。 有了引用以后,代码也变得简练。以前在 C 里,如果要设计一个函数处理大对象,则不得不以指针做参数,那就不得不声明临时变量去存储中间结果。 Matrix a, b, c; getMatrixA(&a); getMatrixB(&b); addMatrix(&a, &b, &c); printMatrix(&c); 而在 C++ 里,则完全不需要考虑这些,完全可以以引用当参数,代码又清晰,又不用担心会拷贝而带来效率瓶颈。 print(addMatrix(getMatrixA(), getMatrixB()));
在Java层面:
可以参考本文的回答:java的引用明明和指针没什么本质区别,java为什么还宣称没有指针并把这个当作语言的优点?