校长讲堂第六讲
- 2020 年 4 月 10 日
- 笔记
句法缺陷
要理解 C 语言程序,仅了解构成它的关键字是不够的。还要理解这些关键字是如何构成声明、表达式、语句和程序的。尽管我们可以很清楚的找到这些关键字的定义以及用法,但这些定义有时候是有悖于直觉的。 在这一节中,我们将着眼于一些不明显句法构造。
2.4 switch 语句
通常 C 中的 switch 语句中的 case 段可以进入下一个。例如,考虑下面的 C 和 Pascal 程序片断:
switch(color) { case 1: printf ("red"); break; case 2: printf ("yellow"); break; case 3: printf ("blue"); break; } case color of 1: write ('red'); 2: write ('yellow'); 3: write ('blue'); end
这两个程序片断都作相同的事情:根据变量 color 的值是 1、2 还是 3 打印 red、yellow 或 blue(没有新行符)。这两个程序片断非常相似,只有一点不同:Pascal 程序中没有 C 中相应的 break 语句。
C 中的 case 标签是真正的标签:控制流程可以无限制地进入到一个 case 标签中。看看另一种形式,假设 C 程序段看起来更像 Pascal:
switch(color) { case 1: printf ("red"); case 2: printf ("yellow"); case 3: printf ("blue"); }
并且假设 color 的值是 2。则该程序将打印 yellowblue,因为控制自然地转入到下一个 printf()的调用。
这既是 C 语言 switch 语句的优点又是它的弱点。说它是弱点,是因为很容易忘记一个 break 语句,从而导致程序出现隐晦的异常行为。说它是优点,是因为通过故意去掉 break 语句,可以很容易实现其他方法难以实现的控制结构。尤其是在一个大型的 switch 语句中,我们经常发现对一个 case 的处理可以简化其他一些特殊的处理。
例如,设想有一个程序是一台假想的机器的翻译器。这样的一个程序可能包含一个 switch 语句来处理各种操作码。在这样一台机器上,通常减法在对其第二个运算数进行变号后就变成和加法一样了。因此,最好可以写出这样的语句:
case SUBTRACT: opnd2 = -opnd2; /* no break; */ case ADD: ...
另外一个例子,考虑编译器通过跳过空白字符来查找一个记号。这里,我们将空格、制表符和新行符视为是相同的,除了新行符还要引起行计数器的增长外:
case 'n': linecount++; /* no break */ case 't': case ' ': ...
2.5 函数调用
和其他程序设计语言不同,C 要求一个函数调用必须有一个参数列表,但可以没有参数。因此,如果f 是一个函数,
f();
就是对该函数进行调用的语句,而
f;
什么也不做。它会作为函数地址被求值,但不会调用它。
2.6 悬挂 else 问题
在讨论任何语法缺陷时我们都不会忘记提到这个问题。尽管这一问题不是 C 语言所独有的,但它仍然伤害着那些有着多年经验的 C 程序员。考虑下面的程序片断:
if(x == 0) if(y == 0) error(); else { z = x + y; f(&z); } 。
希望这段程序能够按照实际的情况运行,应该这样写:
if(x == 0) { if(y == 0) error(); else { z = x + y; f(&z); } }
发生时什么也不做。如果要达到第一个例子的效果,应该写:
if(x == 0) { if(y ==0) error(); } else { z = z + y; f(&z); }