推荐的形式应该是:
|
操作数的评估求值顺序
操作数的评估求值顺序也不固定,如下面的代码所示:
a = p() + q() * r(); |
三个函数p()、q()和r()可能以6种顺序中的任何一种被评估求值。乘法运算符的高优先级只能保证q()和r()的返回值首先相乘,然后再加到p()的返回值上。所以,就算加上再多的括号依旧不能解决问题。
幸运的是,使用显式的、手工指定的中间变量可以解决这一问题,从而保证固定的子表达式评估求值顺序:
|
这样,上述代码就为p()、q()和r()三个函数指定了唯一的计算顺序:p()→q()→r()。
另外,有一些运算符自诞生之日起便有了明确的操作数评估顺序,有着与众不同的可靠性。例如下面的表达式:
(a < b) && (c < d) |
C/C++语言规定,a < b首先被求值,如果a < b成立,c < d则紧接着被求值,以计算整个表达式的值。但如果a大于或等于b,则c < d根本不会被求值。类似的还有||。这两个运算符的短路算法特性可以让我们有机会以一种简约的、符合习惯用法的方式表达出很复杂的条件逻辑。
三目条件运算符 ?: 也起到了把参数的评估求值次序固定下来的作用:
expr1 ? expr2 : expr3 |
第一个表达式会首先被评估求值,然后第二个和第三个表达式中的一个会被选中并评估求值,被选中并评估求值的表达式所求得的结果就会作为整个条件表达式的值。
此外,在建议6中将会详细介绍的逗号运算符也有固定的评估求值顺序。
请记住:
表达式计算顺序是一个很繁琐但是很有必要的话题:
针对操作符优先级,建议多写几个括号,把你的意图表达得更清晰。
注意函数参数和操作数的评估求值顺序问题,小心其陷阱,让你的表达式不要依赖计算顺序。
建议4:小心宏#define使用中的陷阱
C语言宏因为缺少必要的类型检查,通常被C++程序员认为是“万恶之首”,但就像硬币的两面一样,任何事物都是利与弊的矛盾混合体,宏也不例外。宏的强大作用在于在编译期自动地为我们产生代码。如果说模板可以通过类型替换来为我们产生类型层面上多样的代码,那么宏就可以通过符号替换在符号层面上产生的多样代码。正确合理地使用宏,可以有效地提高代码的可读性,减少代码的维护成本。
不过,宏的使用中存在着诸多的陷阱,如果不注意,宏就有可能真的变成C++代码的“万恶之首”。
(1)用宏定义表达式时,要使用完备的括号。
由于宏只是简单的字符替换,宏的参数如果是复合结构,那么替换之后要是不用括号保护各个宏参数,可能会由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,而产生意想不到的情形。但并不是使用了括号就一定能避免出错,我们需要完备的括号去完备地保护宏参数。
如下代码片段所定义的宏要实现参数a和参数b的求和,但是这三种定义都存在一定风险:
#define ADD( a, b ) a + b #define ADD( a, b ) (a + b) #define MULTIPLE( a, b ) (a * b) #define ADD( a, b ) (a) + (b) |
例如,ADD(a,b) * ADD(c,d)的本意是对(a+b)*(c+d)求值,在采用了上面定义的宏之后,代码展开却变成了如下形式,其中只有第2种方式“碰巧”实现了原本意图:
|
之所以说“碰巧”,是因为第2种方式中括号的使用也非完备的。例如:
#define MULTIPLE( a, b ) (a * b) |
在计算(a+b)×c时,如果采用上述宏MULTIPLE(a+b,c),代码展开后,我们得到的却是a+b×c的结果。
要避免这些问题,要做的就是:用完备的括号完备地保护各个宏参数。正确的定义应为:
|