3.2 多项选择问题
在编程时,常常会遇到多项选择问题。例如根据候选人是否来自6所不同大学中的一所,来选择一组不同的动作。另一个例子是根据某一天是星期几,来执行某组语句。在C语言中,有两种方式处理多项选择问题。一种是采用else-if形式的if语句,这是处理多项选择的最常见方式。另一种是switch语句,它限制了选择某个选项的方式,但在使用switch语句的场合中,它提供了一种非常简洁且便于理解的解决方案。下面先介绍else-if语句。
3.2.1 给多项选择使用else-if语句
从一组选项中选择一项的else-if语句如下:
if(choice1)
/* Statement or block for choice 1 */
else if(choice2)
/* Statement or block for choice 2 */
else if(choice3)
/* Statement or block for choice 2 */
/* … and so on … */
else
/* Default statement or block */
每个if表达式可任意组成,只要其结果是true或false即可。如果第一个if表达式choice1是false,就执行下一个if。如果choice2是false,就执行下一个if。继续下去,直到找到一个结果为true的表达式为止。此时,就执行该if中的语句或语句块。然后结束这个执行序列,执行else-if语句后面的语句块。
如果所有的if条件都是false,就执行最后一个else后面的语句或语句块。可以忽略这个else,此时,如果所有的if条件都是false,这个else-if语句序列就什么也不做。下面是一个例子:
if(salary<5000)
printf("Your pay is very poor."); /* pay < 5000 */
else if(salary<15000)
printf("Your pay is not good."); /* 5000 <= pay < 15000 */
else if(salary<50000)
printf("Your pay is not bad."); /* 15000 <= pay < 50000 */
else if(salary<100000)
printf("Your pay is very good."); /* 50000 <= pay < 100000 */
else
printf("Your pay is exceptional."); /* pay > 100000 */
注意,在第一个if语句后,不需要测试if条件中的下限,因为如果执行到某个if,前面的测试就一定是false。
任意逻辑表达式都可以用作if条件,所以这个语句非常灵活,可以从任意多个选项中选择一项。switch语句没有这么灵活,但在许多情况下使用起来更简单。下面看看switch语句。
3.2.2 switch语句
switch语句允许根据一个整数表达式的结果,从一组动作中选择一个动作。下面用一个简单的例子来说明其工作原理。假定在一家彩票销售点,数字35可赢得一等奖,数字122可赢得二等奖,数字78可赢得三等奖。使用switch语句可以检查购买彩票者是否获奖:
switch(ticket_number)
{
case 35:
printf("Congratulations! You win first prize!");
break;
case 122:
printf("You are in luck - second prize.");
break;
case 78:
printf("You are in luck - third prize.");
break;
default:
printf("Too bad, you lose.");
}
在关键字switch的后面,括号中表达式的值是ticket_number,它确定执行括号中的哪些语句。如果ticket_number的值与某个case关键字后面的指定值匹配,就执行该case后面的语句。例如,如果ticket_number的值是122,就显示如下信息:
You are in luck - second prize.
printf()后面的break语句的作用是跳过括号中的其他语句,执行闭括号后面的语句。如果省略了某个case后面的break语句,就继续执行下一个case的语句。如果ticket_number的值不对应任何一个case值,就执行default关键字后面的语句,生成默认的信息。default和break都是C语言中的关键字。switch语句的一般形式如下:
switch(integer_expression)
{
case constant_expression_1:
statements_1;
break;
....
case constant_expression_n:
statements_n;
break;
default:
statements;
}
上述代码的测试基于integer_expression的值。如果该值对应于相关值constant_expression_n定义的某个case值,就执行该case值后面的语句。如果integer_expression的值不同于所有的case值,就执行default后面的语句。我们无法选择多个case,所以所有的case值都必须互不相同。否则,在编译程序时就会得到一个错误信息。case值必须是常量表达式,即可以在编译期间计算的表达式,这意味着case值不能依赖程序执行时确定的值。当然,测试表达式可以是任意的,只要它等于某个整数即可。
可以忽略default关键字及其相关的语句。如果没有case值匹配,就什么也不做。但要注意,constant_expression对应的所有case值必须互不相同。break语句会跳转到闭括号后面的语句上。
注意标点符号和格式。在第一个switch表达式的结尾处没有分号。语句体用括号括起来。case的constant_expression值后跟一个冒号,后面的每条语句都以分号结束,这与一般的语句相同。
enumeration类型是整数类型,所以可以使用enumeration类型的变量控制switch。下面是一个例子:
enum Weekday {Monday, Tuesday, Wednesday,
Thursday, Friday, Saturday, Sunday};
enum Weekday today = Wednesday;
switch(today)
{
case Sunday:
printf("Today is Sunday.");
break;
case Monday:
printf("Today is Monday.");
break;
case Tuesday:
printf("Today is Tuesday.");
break;
case Wednesday:
printf("Today is Wednesday.");
break;
case Thursday:
printf("Today is Thursday.");
break;
case Friday:
printf("Today is Friday.");
break;
case Saturday:
printf("Today is Saturday.");
break;
}
这个switch语句选择对应于today变量值的case,在本例中,显示的信息是“Today is Wednesday”。在这个switch语句中没有默认的case,但可以添加一个,以防止出现today的无效值。
可以把多个case值与一组语句联系起来。还可以使用计算结果为char值的表达式作为switch的控制表达式。假定从键盘上将一个字符读入char类型的变量ch中,在switch语句中测试这个字符,如下所示:
switch(tolower(ch))
{
case 'a': case 'e': case 'i': case 'o': case 'u':
printf("The character is a vowel.");
break;
case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': case 'j':
case 'k':case 'l': case 'm': case 'n': case 'p': case 'q': case 'r':
case 's': case 't': case 'v': case 'w': case 'x': case 'y': case 'z':
printf("The character is a consonant.");
break;
default:
printf("The character is not a letter.");
break;
}
这里使用了在<ctype.h>头文件中声明的tolower()函数,将ch的值转换为小写,所以只需测试小写字母。如果ch包含的字符码表示一个元音,就输出一条信息。有5个case值对应元音,对它们要执行相同的printf()语句。同样,当ch包含辅音时,也输出一条相应的信息。如果ch包含的字符码既不是辅音,也不是元音,就执行默认的case。
注意,默认case后面的break语句,它不是必要的,但也是有作用的。总是把一个break语句放在最后一个case的末尾,可以确保以后在末尾添加一个新的case时,switch总是正确工作。
使用另一个在<ctype.h>头文件中声明的isalpha()函数,可以简化这个switch。如果作为参数传入的字符是字母,isalpha()函数就返回一个非零整数(true),否则就返回0(false)。因此,下面的代码会生成与前面switch相同的结果:
if(!isalpha(ch))
printf("The character is not a letter.");
else
switch(tolower(ch))
{
case 'a': case 'e': case 'i': case 'o': case 'u':
printf("The character is a vowel.");
break;
default:
printf("The character is a consonant.");
break;
}
if语句测试ch是否不是字母,如果不是,就输出一条信息。如果ch是一个字母,switch语句就测试它是元音还是辅音。5个元音case值会生成一个结果,默认的case生成另一个结果。在执行switch语句时,ch包含的是一个字母,所以如果ch不是元音,就一定是辅音。
除了前面介绍的tolower()、toupper()和isalpha()函数之外,<ctype.h>头文件还声明了其他几个函数来测试字符,如表3-4所示。
表3-4 测试字符的函数
|
函 数 |
测 试 内 容 |
|
islower() |
小写字母 |
|
isupper() |
大写字母 |
|
isalnum() |
大写或小写字母 |
|
iscntrl() |
控制字符 |
|
isprint() |
可打印字符,包括空格 |
|
isgraph() |
可打印字符,不包括空格 |
|
isdigit() |
十进制数字('0'~'9') |
|
isxdigit() |
十六进制数字('0'~'9','A'~'F', 'a'~'f')) |
|
isblank() |
标准空白字符(空格,'\t') |
|
isspace() |
空位字符(空格,'\n', '\t', '\v', '\r', '\f') |
|
ispunct() |
Isspace() 和isalnum()返回false的可打印字符 |
如果这些函数找到了它们希望的字符,就返回一个非零整数(表示true),否则返回0(false)。
下面用一个例子来演示switch语句。
试试看:选择幸运数字
这个例子假定,在抽奖活动中有三个幸运数字,参与者要猜测一个幸运数字,switch语句会结束这个猜测过程,给出参与者可能赢得的奖励。
/* Program 3.8 Lucky Lotteries */
#include <stdio.h>
int main(void)
{
int choice = 0; /* The number chosen */
/* Get the choice input */
printf("\nPick a number between 1 and 10 and you may win a prize! ");
scanf("%d",&choice);
/* Check for an invalid selection */
if((choice>10) || (choice <1))
choice = 11; /* Selects invalid choice message */
switch(choice)
{
case 7:
printf("\nCongratulations!");
printf("\nYou win the collected works of Amos Gruntfuttock.");
break; /* Jumps to the end of the block */
case 2:
printf("\nYou win the folding thermometer-pen-watch-umbrella.");
break; /* Jumps to the end of the block */
case 8:
printf("\nYou win the lifetime supply of aspirin tablets.");
break; /* Jumps to the end of the block */
case 11:
printf("\nTry between 1 and 10. You wasted your guess.");
/* No break - so continue with the next statement */
default:
printf("\nSorry, you lose.\n");
break; /* Defensive break - in case of new cases */
}
return 0;
}
这个程序的输出如下:
Pick a number between 1 and 10 and you may win a prize! 3
Sorry, you lose.
或:
Pick a number between 1 and 10 and you may win a prize! 7
Congratulations!
You win the collected works of Amos Gruntfuttock.
如果输入无效数字:
Pick a number between 1 and 10 and you may win a prize! 92
Try between 1 and 10. You wasted your guess.
Sorry, you lose.
代码的说明
开始的代码与前面的程序相同,也是声明一个整型变量choice,接着要求用户输入一个1~10之间的数字,把该值存储在choice中:
int choice = 0; /* The number chosen */
/* Get the choice input */
printf("\nPick a number between 1 and 10 and you may win a prize! ");
scanf("%d",&choice);
在执行其他操作之前,检查用户是否输入了一个1~10之间的数字:
/* Check for an invalid selection */
if((choice>10) || (choice <1))
choice = 11; /* Selects invalid choice message */
如果值不在1~10之间,就自动把它改为11,这不是必要的,但为了确保用户不出错,把choice变量设置为11,对于这个case值,printf()语句会生成错误信息。
接着是switch语句,它根据choice的值从括号之间的case中选择:
switch(choice)
{
...
}
如果choice的值是7,就执行该值对应的case:
case 7:
printf("\nCongratulations!");
printf("\nYou win the collected works of Amos Gruntfuttock.");
break; /* Jumps to the end of the block */
执行两个printf()调用,之后break语句跳到闭括号后面的语句上(这里是结束程序,因为闭括号后面没有语句了)。
下面的两个case也是这样:
case 2:
printf("\nYou win the folding thermometer-pen-watch-umbrella.");
break; /* Jumps to the end of the block */
case 8:
printf("\nYou win the lifetime supply of aspirin tablets.");
break; /* Jumps to the end of the block */
它们对应于choice的值是2或8的情况。
下一个case有点不同:
case 11:
printf("\nTry between 1 and 10, you wasted your guess.");
/* No break – so continue with the next statement */
它没有break语句,所以在显示了信息后,继续执行默认case的printf()。其结果是,如果choice设置为11,会得到两行输出。这对于本例完全合适,但一般应在每个case的最后添加break语句。从程序中删除break语句,再输入7,看看结果如何。每个case都会得到所有的输出信息。默认case如下:
default:
printf("\nSorry, you lose.\n");
break; /* Defensive break - in case of new cases */
如果choice的值不对应任何一个case值,就选择这个默认case。这里也有一个break语句,尽管它是不必要的,但许多程序员仍总是把break语句放在默认case语句的后面,或者switch语句的最后一个case后面,这便于以后提供更多的case语句。如果忘记在默认case的后面加上break语句,switch语句就不会按照希望的那样执行。switch语句中的case顺序可任意,default不一定是最后一个case。
试试看:是或否
下面的switch语句由用户输入的char变量值来控制。程序提示用户为一个动作输入值'y'或'Y',为另一个动作输入'n'或'N'。这个程序其实没有什么用,但许多程序常常会问一个问题,再执行每个动作(例如保存一个文件)。
/* Program 3.9 Testing cases */
#include <stdio.h>
int main(void)
{
char answer = 0; /* Stores an input character */
printf("Enter Y or N: ");
scanf(" %c", &answer);
switch(answer)
{
case 'y': case 'Y':
printf("\nYou responded in the affirmative.");
break;
case 'n': case 'N':
printf("\nYou responded in the negative.");
break;
default:
printf("\nYou did not respond correctly...");
break;
}
return 0;
}
这个程序的输出如下:
Enter Y or N: y
You responded in the affirmative.
代码的说明
把answer变量声明为char类型时,还把它初始化为0。接着要求用户输入一个值,并存储该值:
char answer = 0; /* Stores an input character */
printf("Enter Y or N: ");
scanf(" %c", &answer);
switch语句使用存储在letter中的字符选择case:
switch(answer)
{
...
}
switch语句中的第一个case要求用户输入Y的大写字母或小写字母:
case 'y': case 'Y':
printf("\nYou responded in the affirmative.");
break;
输入值'y'和'Y',都会执行相同的printf()。通常,可以把任意多个这样的case组合在一起。注意其标点符号:两个case放在一起,用一个冒号隔开。
否定的输入以相同的方式处理:
case 'n': case 'N':
printf("\nYou responded in the negative.");
break;
如果输入的字符不对应所有的case值,就选择默认的case:
default:
printf("\nYou did not respond correctly...");
break;
注意默认case的printf()语句后面的break语句以及合法的case值。与以前一样,break语句会使执行过程在此中断,并从switch语句后面的语句继续执行。另外,没有break语句,会执行后续case中的语句,除非有效case的前面有break语句,否则就会执行后面的语句(包括default语句)。
当然,也可以使用toupper()或tolower()函数简化switch中的case。使用这两个函数之一,可以使case个数减半:
switch(toupper(answer))
{
case 'Y':
printf("\nYou responded in the affirmative.");
break;
case 'N':
printf("\nYou responded in the negative.");
break;
default:
printf("\nYou did not respond correctly...");
break;
}
如果使用toupper()函数,就需要用#include指令包含<ctype.h>。
3.2.3 goto语句
If语句允许根据测试的结果选择执行两个语句块中的一个。这是一个强大的工具,可以改变程序的自然执行顺序,程序不再从A执行到B,再到C和D,而可以执行到A,再决定是否跳过B和C,直接执行D。
goto语句是一个比较生硬的指令,它可以无条件地改变程序流——不必通过Go,也不必缴纳$200,而是直接进监狱。程序在遇到goto语句时,也会无条件地跳转。goto语句会直接跳到某个指定的位置,无须检查任何值,或者要求用户考虑这是否是他希望执行的操作。
这里仅简要介绍goto语句,因为它并不像初看起来那么强大。goto语句的问题是它看起来太简单了,这似乎不太恰当,但重要的是“看起来”这个词。goto语句很简单,可以使用它到达任何地方,其实此时使用另一个语句会更好。goto语句会产生非常难以理解的代码。
在使用goto语句时,会跳转到代码中用语句标签指定的位置。语句标签的定义方式与变量名相同,也是一组字母和数字,其中第一个字符必须是字母。语句标签后跟一个冒号(:),将它与它标记的语句分开。它看起来类似于switch中的case标签。case标签就是语句标签。
与其他语句一样,goto语句也用分号结束:
goto there;
目标语句必须有与goto语句相同的标签,在上面的例子中,该标签是there。如前所述,标签写在它所应用的语句之前,其后的冒号将该标签和语句的其他部分隔开,如下面的例子所示:
there: x=10; /* A labeled statement */
goto语句可以与if语句一起使用,如下面的例子所示:
...
if(dice == 6)
goto Waldorf;
else
goto Jail; /* Go to the statement labeled Jail */
Waldorf:
comfort = high;
...
/* Code to prevent falling through to Jail */
Jail: /* The label itself. Program control is sent here */
comfort = low;
...
这段代码在掷骰子。如果掷出了6,就跳转到Waldorf,否则就跳转到Jail。这似乎很不错,但它很容易出现混淆。要理解执行的顺序,需要找出目标标签。假定代码使用了许多goto语句,就很难理解,甚至在出错时都无法修改。所以应尽可能避免使用goto语句。在理论上,总是可以避免使用goto语句,但在一两种情况下,这是一个有用的选项。第4章在介绍循环时会提到,从嵌套了许多层循环的最内层中退出时,使用goto语句比采用其他机制简单得多。





