C++的引用是个挺好用的东西,相当于指针的另一种写法,虽然引用有一些约束,比如必须初始化而且不能修改引用目标,但确实非常大地增加了编程的清晰度和灵活性。
引用主要有这些价值:
- 简化指针访问,“.”比“->”少一个字符,而且更容易理解
- 使指向指针的指针更容易理解,“int * &”比“int **”容易理解多了
- 函数传参和重构灵活,可以通过重新定义参数来轻易地在指针和引用之间转换,避免对整个代码进行“.”和“->”的替换。
当然,凡事有利就有弊,引用本质是个指针,那么就是两个本质相同的东西拥有不同的表达方式,必然会带来一点混乱。
比如一个以前是“int”类型的参数,现在被改成了“int &”并且在函数里面修改了值,那么原来的程序会出错,而编译器不会发现任何问题。当然,严格地说,这是函数修改者的错误,这种改变接口的行为的变更是不可以轻易进行的,可是事实却是调用者的代码无辜出错了。
以上是理论,现在我们用代码看看引用到底是什么:
#include <stdio.h>
#include <typeinfo>
void f(int& x)
{
printf("-------------------------------\n");
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
printf("%p %d\n", &x, x);
}
void f2(int* x)
{
printf("-------------------------------\n");
printf("sizeof(x) : %2zd : typeid : %s\n", sizeof(x), typeid(x).name());
printf("%p %p\n", &x, x);
}
int main()
{
int a = 111;
printf("a %p %d\n", &a, a);
int& r_a = a;
printf("r_a %p %d\n", &r_a, r_a);
int* p_a = &a;
printf("p_a %p %p\n", &p_a, p_a);
f(a);
f(r_a);
f(*p_a);
f2(&a);
f2(&r_a);
f2(p_a);
return 0;
}
这是VS2022的64位C++控制台程序代码(debug版),在main函数任何一处设置断点,调试运行,在程序运行到断点处停下来之后,打开反汇编窗口,看看反汇编代码的样子:
两个红色方框出的代码分别是给引用和指针赋值,即使完全不懂汇编也能看出来——毫无区别。
下面是调用"f(int&x)"的反汇编代码:
“f(r_a)”和“f(*p_a)”的汇编代码相同是能看出来的。
下面是调用“f2(int* x)”的反汇编代码:
看得出来,跟前面的一样啊,这就很明白了,“引用”其实就是“指针”,不过语法上被当作“别名”,我们看一下这个程序的输出:
红线指向的就是“f(int &x)”的参数类型,分明写着“int”啊,所以“引用”在语法层面是“别名”,内部实现为指针。
(这里是结束)