赋值操作符

赋值操作符的左操作数必须是非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++都可以.

 

你可能感兴趣的:(赋值操作符)