|
2楼 1.3可修改的左值 在语义上需要修改左值对应的对象的表达式中,左值必须是一个可修改的左值。比如赋 值(包括复合赋值)表达式中的左操作数,必须是一个可修改的左值表达式;自增/减运算 符的操作数等。 Ex1.3 const int a[2], i; //NOTE: a unintialized. legal in C, illegal in C++. i++; //error, i is an lvalue of type const int. a[0]--;//error, a[0] is an lvalue of const int. 1.4右值 与左值相对应的另一个概念是右值(rvalue)。在C中,右值也用表达式的值(value of the expression)来表达。即右值强调的不是表达式本身,而是该表达式运算后的结果。这 个结果往往并不引用到某一对象,可以看成计算的中间结果;当然它也可能引用到某一对 象,但是通过该右值表达式我们不能直接修改该对象。 1.4.1右值的存储位置 Ex1.4 int i; i=10; 10在这里是一个右值表达式,上句执行的语义是用整型常量10的值修改i所引用的对象。 从汇编语言上看,上述语句可能被翻译成: mov addr_of_i,10; 10这个值被硬编码到机器指令中; 右值也可以存储在寄存器中: int i,j,k; k=i+j; i+j表达式是个右值,该右值可能存储在寄存器中。 mov eax, dword ptr[addr_of_i]; mov ebx, dword ptr[addr_of_j]; add eax, ebx; mov dword ptr[addr_of_k], eax; 在这里,i+j表达式的结果在eax中,这个结果就是i+j表达式的值,它并不引用到某一对象 某些情况下,一个右值表达式可能也引用到一个对象。 struct S{ char c[2];}; struct S f(void); void g() { f().i; f().c[1]; // (*) } f()表达式是个函数调用,该表达式的类型是f的返回类型struct S,f()表达式为右值表 达式,但是在这里往往对应着一个对象,因为这里函数的返回值是一个结构,如果不对应着 一个对象(一片存储区域),用寄存器几乎不能胜任,而且[]操作符语义上又要求一定引用 到对象。 右值虽然可能引用到对象,然而需要说明的是,在右值表达式中,是否引用到对象及引用 得对象的生存期往往并不是程序员所能控制。 1.4.2 为什么需要右值?右值表示一个表达式运算后的值,这个值存储的地方并没有指定;当我 们需要一个表达式运算后的值时,即我们需要右值。比如在赋值运算时,a=b;我们需要用表 达式b的值,来修改a所代表的对象。如果b是个左值表达式,那么我们必须从b所代表的对象 中取出(fetch)该对象的值,然后利用该值来修改a代表的对象。这个取出过程,实际上就是 一个由左值转换到右值的过程。这个过程,C中没有明确表述;但在C++中,被明确归纳为标 准转换之一,左值到右值转换(lvalue-to-rvalue conversion)。回头看看上面的代码,i+j 表达式中,+运算符要求其左右操作数都是右值。行1和2,就是取出左值表达式i,j的对应的 对象的值的过程。这个过程,就是lvalue-to-rvalue conversion。i+j本身就是右值,这里 不需要执行lvalue-to-rvalue conversion,直接将该右值赋值给k。 1.4.3右值的类型 右值表达式的类型是什么? 在C中,右值总是cv-unqualified的类型。因为我们对于右值, 即使其对应着某个对象, 我们也无从或不允许修改它。而在C++中,对于built-in类型的右 值,一样是cv-unqualified,但是类类型(class type)的右值,因为C++允许间接修改其对 应的对象,因此右值表达式与左值一样同样有cv-qualified的性质。(详细见后) Ex1.5 void f(int); void g() { const int i; f(i); //OK. i is an lvalue.After an lvalue-to-rvalue conversion, the rvalue's //type is int. } 1.5 在理解了左值和右值的概念后,我们就能够更好理解为什么有些运算符需要右值,而某些 |
|
0