在C语言程序开发中,一些有经验的程序员常常会说出一些比较奇怪的词,例如:“短路表达式(short-circuit evalution)”、“最小表达式(minimal evaluattion)”,这些词其实是程序员在程序开发实战中总结出的一些非常好用的概念。
C语言程序开发中的“短路表达式”
以“短路表达式”为例,只有当表达式的第一个参数不足以确定表达式的值时,程序才会执行接下来的参数。例如,AND运算的第一个参数为 false 时,可以断定整个表达式为 false,OR 运算的第一个参数为 true 时,可以断定整个表达式为 true。此时,AND 或者 OR 运算的后续参数都不会被执行,看起来表达式就像被“短路”了一样。下面是一段C语言代码示例:
if( (i=0) && (i=3) )
...;
if( (j=1) || (j=3) )
...;
上述C语言代码执行后,i 是等于 0 的,原因就在于“短路效应”,if() 的前一个表达式 i(此时等于0)足以断定整个表达式为 false,后一个表达式(i=3)就没必要执行了。同样的道理,上述C语言代码执行后,j 等于 1。
在某些编程语言(例如 Lisp,Perl,Haskell)中,因为延迟计算机制,一般的布尔运算符是短路的。在其他一些编程语言(如Ada,Java,Delphi)中,短路和标准布尔原酸都是可用的。不过对于某些布尔运算,例如 XOR 运算,就不太可能产生短路效果,因为需要确定两个操作数才能确定结果。
实际上,短路运算符并不能只看作是简单的算术运算符,它也是一种控制结构。在C语言中,表达式的“副作用(side effect)”很重要,短路运算会引入一个序列点——在处理第二个参数之前,程序会首先完全执行第一个参数,包括任何“副作用”。
“序列点”的概念可参考本专栏第一节。
“短路表达式”的应用
正如前文所说,短路运算同时也是一种控制结构,因此在C语言程序开发中,可以基于这一特性简化代码,下面是一个例子:
if(denom != 0){
if( num/denom !=0 ){
fun();
}
}
上述C语言代码很简单,fun() 函数需要在 num/demon 不等于 0 的情况下执行,但是 denom 作为除数是不能为 0 的,因此最外层多了一个 if(denom!=0) 的判断。这样就出现了两个 if 嵌套,看起来有些啰嗦和凌乱。现在,我们基于“短路运算”对上述代码稍作修改,修改后的C语言代码如下,请看:
if( denom!=0 && num/denom){
fun();
}
这样的C语言代码风格显然更加简洁。同样的道理还可以应用于函数调用,例如下面这段C语言代码:
int a = 0;
if (a != 0 && myfunc(b))
{
do_something();
}
在这个例子中,“短路运算”保证了 myfunc() 永远不会被调用,因为 a!=0 是 false 的。总结一下,“短路运算”可以产生两个有用的编程结构:
- 第一个轻量级的子表达式结果可以决定第二个消耗资源较多的子表达式是否执行。
- 如果第二个子表达式依赖第一个子表达式,并且在第一个子表达式结果为 false 时,第二个子表达式运行出错,那么使用“短路运算”无疑是简洁有效的。
再来看两段C语言代码示例。下面这段C语言代码检查 p 指向的第一个字符是否字母,但是显然这样的代码并不安全,因为传入的 p 可能为 NULL:
bool is_first_char_valid_alpha_unsafe(const char *p)
{
return isalpha(p[0]); // SEGFAULT highly possible with p == NULL
}
要解决这样的不安全问题,可以使用“短路运算”,例如:
bool is_first_char_valid_alpha(const char *p)
{
return p != NULL && isalpha(p[0]);
// 1) no unneeded isalpha() execution with p == NULL
// 2) no SEGFAULT risk
}
小结
本文主要讨论了C语言程序开发中的“短路表达式”,实际上,这不仅在C语言中应用广泛,在其他一些编程语言中也是非常值得推崇的一种应用。不过,在C语言中“短路表达式”并不是什么高深的新知识,细思一下,应该能够发现,所谓的“短路表达式”不过是C语言中其他基础知识,以及一些基本准则的延伸而已。