引用变量
在C++ Primer Plus中,最开始介绍的是变量和引用变量。变量实际就包含了左值类型的变量和右值类型的变量。
引用变量
引用是C++引入的概念,引用变量是已定义的变量的别名(alias)。引用的创建示例如下:
int a = 10;
int &b = a;
通过compiler explorer分析其汇编:
第一句int a = 10
对应2句汇编:
movs r3, #10 ;把立即数10存到寄存器r3
str r3, [r7] ;将r3寄存器的值存到r7寄存器里面的那个内存地址,而r7在第4行被设置为sp + 0,
;sp即栈指针寄存器。也就是说此时r7存储的内存地址就是栈顶。总的看来就是在栈顶压入了一个变量,变量值为10。
;这也是局部变量分配内存和存储的方式。
第二句int &b = a;
对应:
mov r3, r7 ;将r7存储的内容(还是sp + 0,也即栈顶内存地址)存储到寄存器r3
str r3, [r7, #4];将r3内容(此刻的栈顶)存储到栈顶偏移4字节的地方,也就是栈上申请了4个字节空间存储局部变量a的地址。
第三句b = 5;
对应:
ldr r3, [r7, #4];加载变量a的地址到r3中
movs r2, #5 ;r2存入立即数5
str r2, [r3] ;r2里面的值存储到r3里面内存地址指向的地方,也就是变量a的存储空间。
可以看到,在汇编中,操作引用和对指针的解引用是一回事。可以进一步验证:
对比可以看到foo和bar的汇编代码一模一样。
引用的初始化
根据上面的分析,我们可以看到普通的变量和引用建立绑定关系实际就是他们各自的内存空间里面存的同一个内存地址。引用在定义时必须初始化,这点和const限定的变量很像,为什么需要初始化呢?这一点我觉得是语法规则,记不记无所谓,反正IDE会提示。这样做的好处:
-
防止对未绑定变量的引用赋值,导致未定义行为。
假定引用可以不初始化,那么它的内存里面就是一个随机地址,赋值行为相当于对随机指针解引用操作(*p = val)。 -
保证绑定关系一直不变。
假设引用可以更改绑定关系,那么如果传递引用到一个函数里面,函数里面修改了绑定关系,返回后,caller根本就不知道,接下来它操作这个引用到底操作的谁,谁也说不清楚。
为什么需要引用
-
提高传递参数的效率
引用最直观的效果是提高传递参数的效率,这和传递指针一样,特别是对于大的数据类型。如果是拷贝传参,你的完整的复制一份参数的拷贝过去。 -
修改作为参数的变量的值
既然指针可以实现,为什么还需要引用?因为要方便操作符重载 -
方便操作符重载
假设没有引用的概念,为了提高效率,使用指针。提供了如下操作符重载:
BigData *operator+(const BigData *phs)
{
d += phs->d;
return this;
}
调用处为:
Bigdata a(1);
Bigdata b(2);
BigData c;
c = *(a + &b);
这样写起来有点复杂,特别是级联后,更复杂:
BigData a(1);
BigData b(2);
BigData c(3);
BigData d;
d = *(*(a + &b) + &c);
如果使用引用来实现操作符重载:
BigData &operator+(const BigData &rhs)
{
d += rhs.d;
return *this;
}
-------------------------------------------
BigData a(1);
BigData b(2);
BigData c(3);
BigData d;
d = a + b + c;
简单太多了。