赋值操作符的左操作数必须是非const的左值.下面的赋值语句是不合法的:
int i,j,ival;
const int ci=i; //ok:initialization not assignment
1024=ival; //error:literals are rvalues
i+j=ival; //error:arithmetic expressions are rvalues
ci=ival; //error:can't write to ci
数组名是不可修改的左值:因此数组不可用作赋值操作的目标.而下标和解引用操作符都返回左值,因此当将这两种操作用于非const数组时,其结果可作为赋值操作的左操作数:
int ia[10];
ia[0]=0; //ok:subscirpt is an lvalue
*ia=0; //ok:dereference also is an lvalue
复制表达式的值是其左操作数的值,其结果的类型为左操作数的类型.
通常,赋值操作将其右操作数的值赋给左操作数.然而,当左、右操作数类型不同时,该操作实现的类型转换可能会修改被赋的值.此时,存放在左、右操作数里值并不相同:
ival=0; //result:type int value 0
ival=3.14159; //result:type int value 3
上述两个赋值语句都产生int类型的值,第一个语句中ival的值与右操作数的值相同:但是在第二个语句中,ival的值则与右操作数的值不相同.
5.4.1 赋值操作的右结合性
与下标和解引用操作符一样,赋值操作也返回左值.同理,只要被赋值的每个操作数都具有相同的通用类型,C++语言允许将这多个赋值操作写在一个表达式中:
int ival,jval;
ival=jval=0; //ok:each assigned 0
与其他二元操作符不同,赋值操作具有右结合特性.当表达式含有多个赋值操作符时,从右向左结合.上述表达式,将右边赋值操作的结果(也就是jval)赋给ival.多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型:
int ival;int *pval;
ival=pval=0; //error:cannot assign the value of a pointer to an int
string s1,s2;
s1-s2="OK"; //ok:"OK" converted to string
第一个赋值语句是不合法的,因为ival和pval是不同类型的对象.虽然0值恰好都可以赋给这两个对象,但该语句仍然错误.因为问题在于给pval赋值的结果是一个int*类型的值,不能将此值赋给int类型的对象.另一方面,第二个赋值语句则是正确的.字符串字面值可以转换为string类型,string类型的值可赋给s2变量.右边赋值操作的结果为s2,再将此结果赋给s1.
5.4.2 赋值操作具有低优先级
另一种通常的用法,是将赋值操作写在条件表达式中,把赋值操作用作表达式的一部分,这种做法可缩短程序代码并阐明程序员的意图.例如,下面的循环调用函数get_value,假设该函数返回int数值,通过循环检查这些返回值,直到获得需要的值位置-------这里是42:
int i=get_value(); //get_value returns an int
while(i!=42){
//do something...
i=get_value();
}
首先,程序将所获得的第一个值存储在i中,然后建立循环检查i的值是否为42,如果不是,则做某些处理.循环中的最后一条语句调用get_value()返回一个值,然后继续循环.该循环可更简洁地写为:
int i
while ((i=get_value())!=42)
{
//do something ...
}
现在,循环条件更清晰地表达了程序员的意图:持续循环直到get_value返回42为止.在循环条件中,将get_value返回的值赋给i,然后判断赋值的结果是否为42.
--------------------------------------------------------我是华丽的分割线------------------------------------------------------
注解:在赋值操作上加圆括号是必需的,因为赋值操作符的优先级低于不等操作符.
-------------------------------------------------------我是华丽的分割线------------------------------------------------------
如果没有圆括号,操作符!=的操作数则是调用get_value返回的值和42,然后将该操作的结果true或false赋给i-------显然这并不是我们想要的.
谨防混淆相等操作符和赋值操作符
可在条件表达式中使用赋值操作,这个事实往往会带来意外的效果:
if(i=42)
此代码是合法的:将42赋给i,然后检验赋值的结果.此时,42为非零值,因此解释为true.其实,程序员的目的显然是想判断i的值是否为42:
if(i==42)
这种类型的程序错误很难发现.有些编译器会为类似于上述例子的代码提出警告.
习题 5.11 请问每次赋值操作完成后,i和d的值分别是多少?
int i;double d;
d=i=3.5;
i=d=3.5;
因为赋值操作具有右结合性,第一个赋值语句中,3.5先是赋给了i,因为i是整型,因此进行类型转换,i=3,然后把3赋给d,因此第一个赋值语句中i跟d均为3.
第二个语句中,3.5先赋给浮点型的d,因此d的值就是3.5,再把3.5赋给int类型的i,i得到3.
习题5.12 解释每个if条件判断产生什么结果?
if(42=i) //.....
if(i=42) //.....
第一个语句中将i赋给42是错误的,42并不是一个左操作数.
第二个语句将42赋给i,然后进行if条件判断,这个是一个永真式,永远都会执行下去.42赋给i之后,i为非零值.
5.4.3 复合赋值操作符
我们通常在对某个对象做某种操作后,再将操作结果重新赋给该对象.例如,考虑下面的求和程序:
int sum=0;
//sum values from 1 up to 10 inclusive
for(int val=1;val<=10;++val)
sum+=val; //equivalent to sum=sum+val
C++语言不仅对加法,而且还对其他算术操作符和位操作符提供了这种用法,称为复合赋值操作.
复合赋值操作符的一般语法格式为:
a op=b;
其中,op=可以是下列十个操作符之一:
+=,-=,*=,/=,%= //arithmetic operators
<<=,>>=,&=,^=,|= //bitwise operators
每个复合赋值操作符本质上等价于:
a=a op b;
这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数.除非考虑可能的性能价值,在很多上下文环境里这个差别不是本质性的.
习题 5.13 下列赋值操作是不合法的,为什么?怎样改正?
double dval; int ival; int *pi;
dval=ival=pi=0;
虽然0可以赋给上述类型,包括int、int指针、double.但pi、ival和dval的类型各不相同,因此要完成赋值必须进行隐式类型转换,但系统无法将int型指针pi的值隐式转换为ival所需的int型值
改为:
double dval;int ival;int *pi;
dval=ival=0;
pi=0;
习题5.14 虽然下列表达式都是合法的,但并不是程序员期望的操作,为什么?怎样修改这些表达式以使其能反映程序员的意图?
a. if(ptr=retrieve_pointer()!=0)
b. if(ival=1024)
c. ival+=ival+1
表达式a应该是把retrieve_pointer()所返回的值赋给ptr,然后判断其是否等于0,因为赋值符号=的优先级较!=低,因此会导致先判断retrieve_pointer()返回的值是否为0,然后把true或者false赋给ptr,我们可以放一个圆括号使得赋值语句先进行就好.
表达式b应该是判断ival是否等于1024,我们改为if(ival==1024)即可
表达式c应该是ival=ival+1,错写为ival+=ival+1,或者写成ival+=1也可以.或++ival,ival++都可以.