1.2 表达式和语句
名字的合理选择可以帮助读者理解程序,同样,我们也应该以尽可能一目了然的形式写好表达式和语句。应该写最清晰的代码,通过给运算符两边加空格的方式说明分组情况,更一般的是通过格式化的方式来帮助阅读。这些都是很琐碎的事情,但却又是非常有价值的,就像保持书桌整洁能使你容易找到东西一样。与你的书桌不同的是,你的程序代码很可能还会被别人使用。用缩行显示程序的结构。采用一种一致的缩行风格,是使程序呈现出结构清晰的最省力的方法。下面这个例子的格式太糟糕了:
![]()
重新调整格式,可以改得好一点:

更好的是把赋值作为循环体,增量运算单独写。这样循环的格式更普通也更容易理解:

使用表达式的自然形式。表达式应该写得你能大声念出来。含有否定运算的条件表达式比较难理解:
![]()
在两个测试中都用到否定,而它们都不是必要的。应该改变关系运算符的方向,使测试变成肯定的:
![]()
现在代码读起来就自然多了。用加括号的方式排除二义性。括号表示分组,即使有时并不必要,加了括号也可能把意图表示得更清楚。在上面的例子里,内层括号就不是必需的,但加上它们没有坏处。熟练的程序员会忽略它们,因为关系运算符(< <= == != >= >)比逻辑运算符(&&和||)的优先级更高。
在混合使用互相无关的运算符时,多写几个括号是个好主意。C语言以及与之相关的语言存在很险恶的优先级问题,在这里很容易犯错误。例如,由于逻辑运算符的约束力比赋值运算符强,在大部分混合使用它们的表达式中,括号都是必需的。
![]()
字位运算符(&和|)的优先级低于关系运算符(比如==),不管出现在哪里:
![]()
实际上都意味着
![]()
这个表达式所表达的肯定不会是程序员的本意。在这里混合使用了字位运算和关系运算符号,表达式里必须加上括号:
![]()
如果一个表达式的分组情况不是一目了然的话,加上括号也可能有些帮助,虽然这种括号可能不是必需的。下面的代码本来不必加括号:
![]()
但加上括号,代码将变得更容易理解了:
![]()
这里还去掉了几个空格:使优先级高的运算符与运算对象连在一起,帮助读者更快地看清表达
式的结构。分解复杂的表达式。C、C++和Java语言都有很丰富的表达式语法结构和很丰富的运算符。因此,在这些语言里就很容易把一大堆东西塞进一个结构中。下面这样的表达式虽然很紧凑,但是它塞进一个语句里的东西确实太多了:
![]()
把它分解成几个部分,意思更容易把握:

要清晰。程序员有时把自己无穷尽的创造力用到了写最简短的代码上,或者用在寻求得到结果的最巧妙方法上。有时这种技能是用错了地方,因为我们的目标应该是写出最清晰的代码,而不是最巧妙的代码。
下面这个难懂的计算到底想做什么?
![]()
最内层表达式把bitoff右移3位,结果又被重新移回来。这样做实际上是把变量的最低3位设置为0。从bitoff的原值里面减掉这个结果,得到的将是bitoff的最低3位。最后用这3位的值确定subkey的右移位数。
上面的表达式与下面这个等价:
![]()
要弄清前一个版本的意思简直像猜谜语,而后面这个则又短又清楚。经验丰富的程序员会把它写得更短,换一个赋值运算符:
![]()
有些结构似乎总是要引诱人们去滥用它们。运算符?:大概属于这一类:
![]()
如果不仔细地追踪这个表达式的每条路径,就几乎没办法弄清它到底是在做什么。下面的形式虽然长了一点,但却更容易理解,其中的每条路径都非常明显:

运算符?:适用于短的表达式,这时它可以把4行的if-else程序变成1行。例如这样:或者下面这样:
![]()
但是它不应该作为条件语句的一般性替代品。
清晰性并不等同于简短。短的代码常常更清楚,例如上面移字位的例子。不过有时代码长一点可能更好,如上面把条件表达式改成条件语句的例子。在这里最重要的评价标准是易于理解。当心副作用。像++ 这一类运算符具有副作用,它们除了返回一个值外,还将隐含地改变变量的值。副作用有时用起来很方便,但有时也会成为问题,因为变量的取值操作和更新操作可能不是同时发生。C和C++ 对与副作用有关的执行顺序并没有明确定义,因此下面的多次赋值语句很可能将产生错误结果:
![]()
这样写的意图是给str中随后的两个位置赋空格值,但实际效果却要依赖于i的更新时刻,很可能把str里的一个位置跳过去,也可能导致只对i实际更新一次。这里应该把它分成两个语句:
![]()
下面的赋值语句虽然只包含一个增量操作,但也可能给出不同的结果:
![]()
如果初始时i的值是3,那么数组元素有可能被设置成3或者4。不仅增量和减量操作有副作用,I/O也是一种附带地下活动的操作。下面的例子希望从标准输入读入两个互相有关的数:
![]()
这样做很有问题,因为在这个表达式里的一个地方修改了yr,而在另一个地方又使用它。这样,除非yr的新取值与原来的值相同,否则profit[yr]就不可能是正确的。你可能认为事情依赖于参数的求值顺序,实际情况并不是这样。这里的问题是:scanf的所有参数都在函数被真正调用前已经求好值了,所以&profit[yr]实际使用的总是yr原来的值。这种问题可能在任何语言里发生。纠正的方法就是把语句分解为两个:
![]()
下面的练习里列举了各种具有副作用的表达式。
练习1-4 改进下面各个程序片段:

练习1-5 下面的例子里有什么错?

练习1-6 列出下面代码片段在各种求值顺序下可能产生的所有不同的输出:
![]()
在尽可能多的编译系统中试验,看看实际中会发生什么情况。







