💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 练习13:Switch语句 > 原文:[Exercise 13: Switch Statement](http://c.learncodethehardway.org/book/ex13.html) > 译者:[飞龙](https://github.com/wizardforcel) 在其它类似Ruby的语言中,`switch`语句可以处理任意类型的表达式。一些语言比如Python没有`switch`语句,因为带有布尔表达式的`if`语句可以做相同的事情。对于这些语言,`switch`语句比`if`语句更加灵活,然而内部的机制是一样的。 C中的`switch`语句与它们不同,实际上是一个“跳转表”。你只能够放置结果为整数的表达式,而不是一些随机的布尔表达式,这些整数用于计算从`swicth`顶部到匹配部分的跳转。下面有一段代码,我要分解它来让你理解“跳转表”的概念: ```c #include <stdio.h> int main(int argc, char *argv[]) { if(argc != 2) { printf("ERROR: You need one argument.\n"); // this is how you abort a program return 1; } int i = 0; for(i = 0; argv[1][i] != '\0'; i++) { char letter = argv[1][i]; switch(letter) { case 'a': case 'A': printf("%d: 'A'\n", i); break; case 'e': case 'E': printf("%d: 'E'\n", i); break; case 'i': case 'I': printf("%d: 'I'\n", i); break; case 'o': case 'O': printf("%d: 'O'\n", i); break; case 'u': case 'U': printf("%d: 'U'\n", i); break; case 'y': case 'Y': if(i > 2) { // it's only sometimes Y printf("%d: 'Y'\n", i); } break; default: printf("%d: %c is not a vowel\n", i, letter); } } return 0; } ``` 在这个程序中我们接受了单一的命令行参数,并且用一种极其复杂的方式打印出所有原因,来向你演示`switch`语句。下面是`swicth`语句的工作原理: + 编译器会标记`swicth`语句的顶端,我们先把它记为地址Y。 + 接着对`switch`中的表达式求值,产生一个数字。在上面的例子中,数字为`argv[1]`中字母的原始的ASCLL码。 + 编译器也会把每个类似`case 'A'`的`case`代码块翻译成这个程序中距离语句顶端的地址,所以`case 'A'`就在`Y + 'A'`处。 + 接着计算是否`Y+letter`位于`switch`语句中,如果距离太远则会将其调整为`Y+Default`。 + 一旦计算出了地址,程序就会“跳”到代码的那个位置并继续执行。这就是一些`case`代码块中有`break`而另外一些没有的原因。 + 如果输出了`'a'`,那它就会跳到`case 'a'`,它里面没有`break`语句,所以它会贯穿执行底下带有代码和`break`的`case 'A'`。 + 最后它执行这段代码,执行`break`完全跳出`switch`语句块。 > 译者注:更常见的情况是,gcc会在空白处单独构建一张跳转表,各个偏移处存放对应的`case`语句的地址。Y不是`switch`语句的起始地址,而是这张表的起始地址。程序会跳转到`*(Y + 'A')`而不是`Y + 'A'`处。 这是对`swicth`语句工作原理的一个深究,然而实际操作中你只需要记住下面几条简单的原则: + 总是要包含一个`default:`分支,可以让你接住被忽略的输入。 + 不要允许“贯穿”执行,除非你真的想这么做,这种情况下最好添加一个`//fallthrough`的注释。 + 一定要先编写`case`和`break`,再编写其中的代码。 + 如果能够简化的话,用`if`语句代替。 ## 你会看到什么 下面是我运行它的一个例子,也演示了传入命令行参数的不同方法: ```sh $ make ex13 cc -Wall -g ex13.c -o ex13 $ ./ex13 ERROR: You need one argument. $ $ ./ex13 Zed 0: Z is not a vowel 1: 'E' 2: d is not a vowel $ $ ./ex13 Zed Shaw ERROR: You need one argument. $ $ ./ex13 "Zed Shaw" 0: Z is not a vowel 1: 'E' 2: d is not a vowel 3: is not a vowel 4: S is not a vowel 5: h is not a vowel 6: 'A' 7: w is not a vowel $ ``` 记住在代码的开始有个`if`语句,当没有提供足够的参数时使用`return 1`返回。返回非0是你提示操作系统程序出错的办法。任何大于0的值都可以在脚本中测试,其它程序会由此知道发生了什么。 ## 如何使它崩溃 破坏一个`switch`语句块太容易了。下面是一些方法,你可以挑一个来用: + 忘记写`break`,程序就会运行两个或多个代码块,这些都是你不想运行的。 + 忘记写`default`,程序会在静默中忽略你所忘记的值。 + 无意中将一些带有预料之外的值的变量放入`switch`中,比如带有奇怪的值的`int`。 + 在`switch`中是否未初始化的值。 你也可以使用一些别的方法使这个程序崩溃。试着看你能不能自己做到它。 ## 附加题 + 编写另一个程序,在字母上做算术运算将它们转换为小写,并且在`switch`中移除所有额外的大写字母。 + 使用`','`(逗号)在`for`循环中初始化`letter`。 + 使用另一个`for`循环来让它处理你传入的所有命令行参数。 + 将这个`switch`语句转为`if`语句,你更喜欢哪个呢? + 在“Y”的例子中,我在`if`代码块外面写了个`break`。这样会产生什么效果?如果把它移进`if`代码块,会发生什么?自己试着解答它,并证明你是正确的。