校长讲堂第六讲

  • 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);  }