复合表达式的求值

含有两个或更多操作符的表达式称为复合表达式(compound expression).在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值.

表达式的结果会因为操作符和操作数的分组结合方式的不同而不同.

操作数的分组结合方式取决于操作符的优先级和结合性.也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数.如果程

序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特殊的分组.

--------------------------------------------------------我是华丽的分割线---------------------------------------------------

注解:优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序.在大多数情况下,操作数一般以最方便的次序求解.

-------------------------------------------------------我是华丽的分割线----------------------------------------------------

5.10.1  优先级

表达式的值取决于其子表达式如何分组.例如,下面的表达式,如果纯粹从左向右计算,结果为20:

6+3*4/2+2;

想像中其他可能的结果包括9、14、36.在C++中,该表达式的值应为14.

乘法和除法的优先级高于加法操作,于是它们的操作数先于加法操作的操作数计算.但乘法和除法的优先级相同.当操作符的优先级相同时,由其

结合性决定求解次序.算术操作具有左结合性,这意味着它们从左向右结合.因此上面表达式等效于:

int temp=3*4;   //12
int temp2=temp/2  //6
int temp3=temp2+6; //12
int result=temp3+2 //14

圆括号凌驾于优先级之上

我们可以使用圆括号推翻优先级的限制.使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先计算,其他部分则以普通的优先级规

则处理.例如,下面的程序在前述表达式上添加圆括号,强行更改其操作次序,可能得到四种结果:

//parentheses on this expression match default precedence/associativity
cout<<((6+((3*4)/2))+2)<<endl;  //prints 14
//parentheses result in alternative groupings
cout<<(6+3)*(4/2+2)<<endl;    //prints 36
cout<<((6+3)*4)/2+2<<endl;    //prints 20
cout<<6+3*4/(2+2)<<endl;      //prints 9

我们已经通过前面的例子了解了优先级规则如何影响程序的正确性.例如,考虑下面这个表达式:

*iter++;

其中++的优先级高于*操作符,这就意味着iter++先结合.而操作符*的操作数是iter做了自增操作后的结果.如果我们希望对iter所指向的值做

自增操作,则必须使用圆括号强制实现我们的目的:

(*iter)++;  //increment value to which iter refers and yield unincremented value

圆括号指明操作符*的操作数是iter,然后表达式以*iter作为++操作符的操作数.看下面一个例子,while循环条件:

while((i=get_value())!=42){

赋值操作上的圆括号是必需的,这样才能实现预期的操作:将get_value的返回值赋给i,然后检查刚才赋值的结果是否为42.如果赋值操作上没有

加圆括号,结果将是先判断get_value的返回值是否为42,然后将判断结果true或false值赋给i,这意味着i的值只能是1或0.

5.10.2  结合性

结合性规定了具有相同优先级的操作符如何分组.我们已经遇到过涉及结合性的例子.其中之一使用了赋值操作的右结合性,这个特性允许将多

个赋值操作串接起来:

ival=jval=kval=lval   //right associative
(ival=(jval=(kval=lval)))//equivalent,parenthesized version

该表达式首先将ival赋给kval,然后将kval的值赋给jval,最后将jval的值再赋给ival.

另一方面,算术操作符为左结合.表达式

ival=jval=kval=lval   //left associative
(((ival*jval)/kval)*lval) //equivalent,parenthesized version

先对ival和jval做乘法操作,然后乘积除以kval,最后再将其商与ival相乘.

5.10.3 求值顺序

在前面,我们讨论了&&和||操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数.根

据这个原则,可编写如下代码:

//iter only dereferenced if it isn't at end
while(iter!=vec.end() && *iter!=some_val)

C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符.除此之外,其他操作符并未指定其操作数的求值顺序.

例如,表达式    f1() * f2();    在做乘法操作之前,必须调用f1函数和f2函数,毕竟其调用结果要相乘.然而,我们却无法知道到底是先调用f1还是先调用f2.

--------------------------------------------------------我是心痛的分割线-----------------------------------------------------

注解:其实,以什么次序求解操作数通常没有多大关系.只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果.

---------------------------------------------------------我是心痛的分割线----------------------------------------------------

如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就会变得相当重要:

//opps!language does not define order of evaluation
if(ia[index++]<ia[index])

此表达式的行为没有明确定义.问题在于:<操作符的左右操作数都使用了index变量,但是,左操作数更改了该变量的值.假设index初值为0,编译器可以用下面两种方式之一求该表达式的值:

if(ia[0]<ia[0])  //execution if rhs is evaluated first
if(ia[0]<ia[1])  //execution if lhs is evaluated first

可以假设程序员希望先求左操作数的值,因此index的值加1.如果是这样的话,比较ia[0]和ia[1]的值.然而,C++语言不能确保从左到右的计算次序.事实上,这类表达式的行为没有明确定义.一种实现可能是先计算右操作数,于是ia[0]与自己做比较,要不然就是做完全不同的操作.

-----------------------------------------------------------------我是心痛的分割线----------------------------------------

建议:复合表达式的处理

初学C和C++的程序员一般很难理解求值顺序、优先级和结合性规则.误解表达式和操作数如何求解将导致大量的程序错误.此外,除非程序员已经完全理解了相关规则,否则这类错误很难发现,因为仅靠阅读程序是无法排除这些错误的.

下面两个指导原则有助于处理复合表达式:

1.如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合.

2.如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数,如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它.

第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该字表达式的结果用于另一个子表达式,这样则是安全的.例如,*++iter表达式的自增操作修改了iter的值,然后将iter(修改后)的值用作*操作符的操作数.对于这个表达式或其他类似的表达式,其操作数的计算次序无关紧要.而为了计算更复杂的表达式,改变操作数值的子表达式必须首先计算.这种方法很常用,不会产生什么问题.

小心:一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作.

--------------------------------------------------------------我是心痛的分割线----------------------------------------------

以一种安全而且独立于机器的方式重写上述比较两个数组元素的程序:

if(ia[index]<ia[index+1]){
 //do whatever
}
++index;

现在,两个操作数的值不会相互影响.
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值