这是一个老生常谈的问题了,废话不多说,开始!
那么指针与引用有什么区别呢?
答:几乎没有区别
相信学过c++的人几乎都可以回答:
1. 指针是所指内存的地址
2. 引用是别名
3. 引用必须初始化,并且初始化后不能重新引用其它变量
但是引用是别名这是 C++ 语法规定的语义。那么到底引用在汇编层面和指针有什么区别呢?
答:无区别
下面我们从汇编层面解析代码,指针版与引用版本的交换函数:
// 指针版
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 引用版
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
引用版汇编:
__Z4swapRiS_: ## @_Z4swapRiS_
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, -8(%rbp) # 传入的第一个参数存放到%rbp-8 (应该是采用的寄存器传参,而不是常见的压栈)
movq %rsi, -16(%rbp) # 第二个参数 存放到 %rbp-16
movq -8(%rbp), %rsi # 第一个参数赋给 rsi
movl (%rsi), %eax # 以第一个参数为地址取出值赋给eax,取出*a暂存寄存器
movl %eax, -20(%rbp) # temp = a
movq -16(%rbp), %rsi # 将第二个参数重复上面的
movl (%rsi), %eax
movq -8(%rbp), %rsi
movl %eax, (%rsi) # a = b
movl -20(%rbp), %eax # eax = temp
movq -16(%rbp), %rsi
movl %eax, (%rsi) # b = temp
popq %rbp
retq
.cfi_endproc
## -- End function
指针版汇编:
__Z4swapPiS_: ## @_Z4swapPiS_
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -8(%rbp), %rsi
movl (%rsi), %eax
movl %eax, -20(%rbp)
movq -16(%rbp), %rsi
movl (%rsi), %eax
movq -8(%rbp), %rsi
movl %eax, (%rsi)
movl -20(%rbp), %eax
movq -16(%rbp), %rsi
movl %eax, (%rsi)
popq %rbp
retq
.cfi_endproc
## -- End function
看到了吧,几乎没有区别,可以说引用只是一层语法糖。
总结:
- 1、引用只是c++语法糖,可以看作编译器自动完成取地址、解引用的常量指针
- 2、用区别于指针的特性都是编译器约束完成的,一旦编译成汇编就和指针一样
- 3、由于引用只是指针包装了下,所以也存在风险,比如如下代码:
int *a = new int;
int &b = *a;
delete a;
b = 12; // 对已经释放的内存解引用
- 4、引用由编译器保证初始化,使用起来较为方便(如不用检查空指针等)
- 5、尽量用引用代替指针
- 6、引用没有顶层 const (引用本身不可变) 即int & const,因为引用本身就不可变,所以在加顶层 const 也没有意义;但是可以有底层 const ()即 const int&,这表示引用所引用的对象本身是常量
- 7、指针既有顶层const(int * const–指针本身不可变),也有底层const(const int *–指针所指向的对象不可变)
- 8、有指针引用–是引用,绑定到指针, 但是没有引用指针–这很显然,因为很多时候指针存在的意义就是间接改变对象的值。但是引用本身的值我们上面说过了是所引用对象的地址,但是引用不能更改所引用的对象,也就当然不能有引用指针了。
- 9、指针和引用的自增(++)和自减含义不同,指针是指针运算, 而引用是代表所指向的对象对象执行++或–