1.6 控制语句
1.6.1 if语句
如果给定的条件是true,基本的if语句允许程序执行一条语句或一条包含在花括号中的语句块,如图1-1所示。
图1-1 if语句执行情况
下面是if语句的一个简单例子,它测试类型为char的变量letter的值:
if(letter == ' A' ) std::cout << "The first capital, alphabetically speaking.\n"; std::cout << "This statement always executes.\n";
如果letter的值是’A' ,条件就为true,将输出下面的结果:
The first capital, alphabetically speaking. This statement always executes.
如果letter的值不是’A' ,就只输出第二行语句。要测试的条件放在关键字if后面的括号中。注意分号的位置,它位于if和括号中的条件后面的语句之后。在括号中的条件的后面不能有分号,因为if和条件是和后面的语句或语句块绑定在一起的,它们本身是不能单独存在的。
if之后的语句只有在条件为true时才执行。对于程序的编译来说,语句的缩进是不必要的,但这种缩进有助于理解if条件和依赖它的语句之间的关系。有时,简单的if语句还可以写在一行上:
if (letter == ' A' ) std::cout << "The first capital, alphabetically speaking.\n";
一般情况下,最好把语句(或语句块)和if条件放在不同的代码行上,这样会更清楚。
扩展上面的例子,如果letter的值是’A' ,就改变它的值:
if(letter == ' A' ) { std::cout << ' The first capital, alphabetically speaking.\n"; letter = ' a' ; } std::cout << "This statement always executes.\n";
在if条件为true时,就执行块中的所有语句。如果没有加上花括号,则只有第一个语句是if块的内容,而给letter赋予’a’的语句将总是执行。注意在块中每个语句的最后都有一个分号,而在块结束的右花括号后面没有分号。在块中可以放置任意多个语句,甚至还可以嵌套块,因为letter的值是’A' ,所以块中的两个语句都会执行,在输出与前面相同的消息后,它的值将变为’a' 。如果条件为false,将不执行这两个语句。当然,if块后面的语句总是执行。
程序示例——下面试用if语句,创建一个程序,检查从键盘上输入的一个整数值:
//程序的功能是演示使用if #include <iostream> using std::cin; using std::cout; using std::endl; int main() { cout << " Enter an integer between 50 and 100: "; int value=0; cin>> value; if(value<50) cout << " The value is invalid - it is less than 50."<<endl; if(value>100) cout << " The value is invalid - it is greater than 100."<<endl; cout << " You entered"<<value<<endl; return 0; }
输出的值取决于输入的值,对于值在50~100之间的数据,应输出如下语句:
Enter an integer between 50 and 100: 77 You entered 77
如果输入的值在50~100的范围之外,就给出一个消息,说明值是无效的,并显示该值。如果该值小于50,则输出如下所示:
Enter an integer between 50 and 100: 27 The value is invalid - it is less than 50. You entered 27
如果该值大于100,则输出如下所示:
Enter an integer between 50 and 100: 270 The value is invalid - it is greater than 100. You entered 270
对该例的说明如下。
在给出提示,并读取一个值后,第一个if语句就会检查输入的值是否小于下限50:
if(value<50) cout << " The value is invalid - it is less than 50."<<endl;
只有if条件为true时,也就是当value小于50时,才执行以上输出语句。下一个if语句检查上限:
if(value>100) cout <<" The value is invalid - it is greater than 100."<<endl;
如果value大于100,就执行该输出语句。最后一个输出语句是:
cout << " You entered"<<value<<endl;
这个语句总是执行。
1.6.2 嵌套的if语句
在if语句中的条件为true时才执行的语句本身也可以是一个if语句,这种情况称为嵌套的if语句。这种情况下只有外层if的条件为true时,才测试内层if的条件。嵌套在一个if语句中的if语句也可以包含另一个嵌套的if语句。一般情况下,可以继续嵌套if语句,嵌套的次数没有限制。
1.程序示例——使用嵌套的if语句
下面用一个工作示例来演示嵌套的if语句。该示例测试从键盘上输入的字符是否为字母。这个示例很好地使用了嵌套的if语句,但其中一些固有的假设最好避免,读者可以试着找出这些假设。下面是具体代码:
//程序的功能是演示使用嵌套if #include <iostream> using std::cin; using std::cout; using std::endl; int main() { char letter=0; //此变量存储的是输入值 cout << "Enter a letter: "; //输入提示 cin >> letter; //读入一个字符 if(letter >= ' A' ) { //是否等于’A' if(letter <= ' Z' ) { //是否等于’Z' cout << "You entered an uppercase letter." << endl; return 0; } } if(letter >= ' a' ) //是否等于’a' if(letter <= ' z' ) { //是否等于’z' cout << "You entered an lowercase letter." << endl; return 0; } cout<<" You do not enter a letter."<<endl; return 0; }
本例的输出如下所示:
Enter a letter: H You entered an uppercase letter.
本例的说明如下。
这个程序首先是通常的注释行和支持输入输出的头文件<iostream>的#include语句,以及程序中std名称的using声明。在为char变量letter分配内存空间并初始化为0后,函数main()将提示输入一个字母。
之后的if语句检查输入的字符是否为’A’或更大的字母:
if (letter >= ' A' ) { //是否等于’A' if (letter <= ' Z' ) { //是否等于’Z' cout << "You entered an uppercase letter." << endl; return 0; } }
如果letter大于或等于’A' ,嵌套的if就检查输入的字符是否为’Z’或更小的字母。如果该字母是’Z’或更小的字母,就说明该字符是一个大写字母,于是显示一个消息,然后执行return语句,结束程序。因为这两个语句都放在花括号中,所以在嵌套的if条件为true时,这两个语句都会被执行。
这对嵌套的if语句建立在用编码表示字母字符的两个假设的基础上。第一个假设是字母’A'到’Z’用一组编码表示,其中’A’的编码最小,' Z’的编码最大。第二个假设是大写字母的编码是连续的,在’A’编码和’Z’编码之间不存在非字母字符。在代码中建立这样的假设并不好,因为这限制了程序的可移植性。例如,在EBCDIC编码中,字母的字符编码就是不连续的。稍后将介绍如何避免这种限制。
下一个if语句使用了与第一个if语句相同的机制,光检查输入的字符是否为小写,然后显示一个消息并返回。
if (letter >= ' a' ) //是否等于’a' if (letter <= ' z' ) { //是否等于’z' cout << "You entered an lowercase letter." << endl; return 0; }
如果仔细检查,就会注意到小写字符的测试只包含一对花括号,而大写字母的测试包含两对花括号。这里花括号中的代码块属于内层的if语句。实际上,这两种方式都是对的。在C++中, if(condition){…}是一个语句,不需要放在花括号中。如果觉得使用较多的花括号能使代码更加清晰,就可以使用它们。同时,与大写字母测试一样,这些代码隐含了小写字母编码的假设。
只有在输入的字符不是字母时,才执行最后一个if语句块后面的输出语句,它会显示一个消息,然后执行return语句。嵌套的if语句和输出语句之间的关系更容易理解,因为每个if语句块都实现了缩进。在C++中,缩进格式通常用于提供程序逻辑的可视化线索。
如本例开头所述,该程序演示了嵌套的if语句块如何工作,但这并不是测试字符的好方法。使用标准库可以编写出独立于字符编码的程序,下面就介绍这部分内容。
2.不依赖编码的字符处理
标准库提供了许多函数,它们可以在程序中执行许多任务,表1-6列出了这些函数。在程序中包含<cctype>头文件之后,就可以访问一组非常有用的函数集来测试字符。在测试字符时,需要给函数传送一个int类型的变量或常数量。如果传送了char类型的值,编译器会把它自动转换为int类型。
表1-6 测试字符的函数
这些函数都返回一个int类型的值。如果字符的类型与要测试的类型相同,该值就为正(true),否则就为0(false)。为什么这些函数不返回bool类型的值呢?后者看上去似乎更有意义。原因是包含这些函数的C标准库是在bool类型引入C++之前建立的。
<cctype>头文件还提供了两个函数,如表1-7所示。在大写字符和小写字符之间转换,传送给这两个函数的字符应是int类型,返回的结果也为int类型。
表1-7 转换字符的函数
可以使用这些函数实现前面的例子,而无须任何字符编码的假设。不同环境中的不同字符编码总是由标准库函数来考虑的,用户不需要考虑。因为使用标准库函数后,也不需要使用嵌套的if语句,所以代码要比前面简单得多。
注意所有这些字符测试函数,除isdigit()和isxdigit()之外,都在当前环境下测试变元,本地环境确定了本地数据是如何处理的。不同的国家使用不同的字符集表示字母,所以某个字符编码是否解释为字母取决于本地环境。货币单位和小数的显示方式也随着本地环境的不同而不同。调用在<clocale>头文件中声明的setlocale()函数可以设置本地环境。这个函数接受两个变元:第一个变元指定应用本地环境的函数的类别,第二个变元指定本地环境。用于第一个变元的值必须是在<clocale>头文件中声明的值,表1-8列出了这些值。
表1-8 受本地环境影响的类别值
setlocale()的第二个变元是一个指定本地环境的字符串。字符串“C”是默认值,对应于拉丁字母‘A’到‘Z' 。可以用于指定其他本地环境的字符串集是由实现方式定义的,通常包括简单明了的国家规范,如Germany。
C++头文件<locale>提供了许多扩展功能的声明,它们可以处理依赖于本地环境的数据,当程序中需要支持多个本地环境时,就应使用它们。
· 程序示例——使用标准库字符转换函数
在使用标准库函数修改上一个例子时,还可以扩展程序的功能,例如使用转换函数:
//程序的功能是演示使用标准字符转换函数 #include <cctype> // 字符测试和转化 using std::cin; using std::cout; using std::endl; int main() { char letter=0; // 存储输入字符 cout<<endl << "Enter a letter: "; // 提示输入 cin>> letter; // 输入字符 cout<<endl; if (std::isupper(letter)) { //大写字符测试 cout << "You entered a capital letter." <<endl; cout << " Converting to lowercase we get " <<static_cast<char>( std::tolower(letter))<<endl; return 0; } if (std::islower(letter)) { //小写字符测试 cout << "You entered a small letter." <<endl; cout << "Converting to uppercase we get " << static_cast<char>( std::toupper(letter)) <<endl; return 0; } cout<<" You id not enter a letter."<<endl; return 0; }
该例的输出如下所示:
Enter a letter: t You entered a small letter. Converting to uppercase we get T
该例的说明如下。
if表达式已改为使用标准库函数,不再需要嵌套的if语句,因为前面要测试的两个条件现在都包含在isupper()或islower()函数中了。
用户并不需要关心这些函数的工作原理。要使用它们,只需要知道它们完成什么任务,需要给它们传送多少参数,传送什么类型的参数,以及它们返回什么类型的值。有了这些信息,就可以使用标准库函数,使代码更简单、更一般化。程序的这个版本可以处理使用任何字符编码的char类型。
注意,在输出语句中可以直接使用从转换函数中返回的结果,如下所示:
cout << "Converting to upper case we get " << static_cast<char>(std::toupper(letter)) <<endl;
由于toupper()函数返回的值是int类型,因此这里把它强制转换为char类型,并发送给输出流cout。如果要存储返回的字符,而不是进行显式强制转换,就可以将它存储在原来的变量letter中,如下面的语句所示:
letter =std::toupper(letter);
接着就可以在输出语句中使用变量letter输出转换后的字符了:
cout << "Converting to uppercase we get "<<letter <<endl;
如果需要使用多字节字符(其类型是wchar_t),就可以包含头文件<cwctype>。该文件包含了在<cctype>中声明的所有函数的对应多字节字符。每个测试函数名都在is的后面加上了w,所以它们的名称就变成:
iswupper() iswdigit() iswspace() iswgraph() iswlower() iswxdigit() iswcntrl() iswpunct() iswalpha() iswalnum() iswprint()
它们都传递多字节字符参数,并且返回一个int值,就像处理char类型的字符函数一样。同样,多字节字符转换函数也称为towupper()和towlower()。
注意 C++中的<cctype>和<cwtype>头文件继承于C。在许多实现方式中,函数在std命名空间内部和外部都定义了,以允许旧式C程序编译和链接。此时,函数名无论是否带std限定符都能工作,但因为编写的是C++程序,所以应限定名称。
下面使用的if语句在指定的条件为true时执行一个语句;接着,程序按顺序执行下一个语句。当然,也有时候希望只有在条件为false时才执行某个语句或语句块。为此,就要扩展if语句,允许在条件为true时执行一组动作,在条件为false时执行另一组动作。之后,程序按顺序执行下一条语句。这可以描述为if-else语句。
if-else组合提供了两个选项供选择,其一般逻辑如图1-2所示。
图1-2 if-else语句执行情况
图1-2中的流程图指出了语句的执行顺序取决于if条件为true还是false。如图1-2所示,在可以使用语句的地方,总是可以用一条语句块来代替。这表示可以为if-else语句的每个选项执行任意多条语句。
下面仍然使用char类型的变量,编写一个if-else语句,报告存储在变量letter中的字符是否为字母或数字:
if(std::isalnum(letter)) std::cout << "It is a letter or a digit." << std::endl; else std::cout << "It is neither a letter nor a digit." << std::endl;
这个程序使用了<cctype>头文件中的函数isalnum()。如果变量letter包含字母或数字,函数isalnum()就返回一个正整数。因为if语句把这个看做是true,所以显示第一个消息。如果变量letter包含的不是字母或数字,函数isalnum()就返回0。对于if来说,这会自动转换为false,执行else之后的输出语句。
· 程序示例——扩展if语句
下面用一个例子来演示if-else语句,这次测试的是数值:
//程序的功能是演示使用if-else语句 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { long number=0; //存储字符 cout << "Enter an integer less than 2 billion: "; cin >> number ; cout << endl; if (number % 2L ==0) //能否被2整除 cout << " \n Your number is even." //是否为零 << endl; else cout << " \n Your number is odd." //是否为1 << endl; return 0; }
这个程序的输出如下所示:
Enter an integer less than 2 billion: 123456 Your number is even.
该例的说明如下。
在把输入的值读入number之后,就在if条件中测试该数除以2的余数(使用第2章中介绍的取余运算符%),检查它是否为0。整数除以2后的余数只能是1或0。程序中的代码进行了注释,如果余数等于0, if条件就是true,则执行if之后的语句。如果余数等于1,则if条件为false,就执行else关键字后面的语句。在输出结果后,执行return语句,结束程序。
提示 else关键字的后面没有分号,这与语句中的if一样。这里也采用了缩进格式,作为各个语句之间关系的可视化指示符。读者可以清楚地看出哪个语句在得到true时执行,哪个语句在false时执行。在程序中,应尽量缩进语句,以显示它们的逻辑结构。
在这个例子中,演示了编写if条件的另一种方式。在转换为bool类型时,任何非0值都是true,而0值将被转换为false。所以,可以把模式化操作的结果用做条件,而不需要比较它和0值。这样,if-else语句就变成:
if(number % 2L) //能否被2整除 cout << " Your number is odd."//是否为1 << endl; else cout << " Your number is even."//是否为零 << endl;
if和else子句需要调换,因为如果number的值是偶数,则(number % 2L == 0)就返回true,而(number % 2L)会转换为false。初看起来这似乎有点让人迷惑,但这个条件的第一个版本如下:
“余数为0是true吗?”
由于1会转换为true,因此,该条件的第二个版本应如下:
“余数是1吗?”
1.6.3 嵌套的if-else语句
前面介绍了如何在if语句中嵌套if语句。显然,也可以在if语句中嵌套if-else语句,在if-else语句中嵌套if语句,或者在if-else语句中嵌套其他if-else语句,这样就提供了极大的灵活性(同时也很容易出现混淆)。下面举几个例子。先看第一种情况,即在if语句中嵌套if-else语句:
if(coffee==' y' ) if(donuts==' y' ) std::cout << " We have coffee and donuts ." << std::endl; else std::cout << " We have coffee, but not donuts." << std::endl;
其中,coffee和donuts是char类型的变量,其值分别是’y’和’n' 。由于对donuts的测试仅在coffee测试的结果为true时才进行,因此在每种情况下,消息都会反映正确的情形。else属于donuts测试中的if语句,这很容易引起混淆。
如果编写这些代码时,缩进格式有错误,就会推导出错误的结论:
if(coffee==' y' ) if(donuts==' y' ) std::cout << std::endl << "We have coffee and donuts ."; else //这个else不正确 std::cout << " We have no coffee…" //错误 << std::endl;
代码的缩进让人错误地认为if语句嵌套在if-else语句中,实际上并非如此。第一个消息是正确的,但执行else后的结果就是错误的。这个语句仅在对coffee的测试为true时才执行,因为else属于donuts的测试,不属于coffee的测试。这个错误虽然很容易看出来,但需要注意的是if结构越大就越复杂,就越需要弄清楚哪个if拥有哪个else。
注意 else总是属于前面最接近的那个if(只要另一个else还不属于这个if)。这种混淆称为else悬挂问题。
在程序中,只要一组if-else语句看起来有些复杂,就可以应用这个规则,对该组语句进行排序。在编写程序时,应尽量使用花括号,使代码更清晰。在上例这样简单的情形中不需要使用花括号,但可以将其改写为:
if(coffee==' y' ) { if(donuts==' y' ) std::cout << "We have coffee and donuts ." << std::endl; else std::cout << " We have coffee, but not donuts." << std::endl; }
现在代码非常清晰了,else肯定属于测试donuts的if语句。
知道了规则后,理解if语句嵌套在if-else语句的情形就比较容易了。
if(coffee==' y' ) { if(donuts==' y' ) std::cout << " We have coffee and donuts ." << std::endl;} else if(tea==' y' ) std::cout << " We have no coffee, but we have tea" << std::endl;
提示 注意这里的代码格式。一个if语句嵌套在else的下面,此时可以把else和if写在一行上。这里花括号是必不可少的,如果省略了花括号,else就属于测试donuts的if语句了。在这种情况下,编程人员很容易忘记加上花括号,从而生成一个很难找出的错误。有这种错误的程序也会通过编译,因为代码是完全正确的。有时甚至结果也是正确的,但它没有表达出真正的意图。
如果在上例中删除花括号,则只要coffee和donuts都等于’y' ,就不会执行if(tea == ' y' )检查,从而得到正确的结果。
最后,看一个if-else语句嵌套在另一个if-else语句中的情况。即使只有一层嵌套,也可能非常混乱。最好对coffee和donuts进行彻底的分析,再开始使用这种嵌套。
if(coffee==' y' ) if(donuts==' y' ) std::cout<< " We have coffee and donuts ." << std::endl; else std::cout<< " We have coffee, but not donuts." << std::endl; else if(tea==' y' ) std::cout<< " We have no coffee, but we have tea, and maybe donuts…" << std::endl; else std::cout<< "No tea or coffee, but maybe donuts…" << std::endl;
即使采用了正确的缩进格式,这里的逻辑看起来也不是很明显。这里不需要使用花括号,因为前面的规则已校验,但如果加上花括号,则看起来会更清楚一些:
if(coffee==' y' ) { if(donuts==' y' ) std::cout<< " We have coffee and donuts ." << std::endl; else std::cout << " We have coffee, but not donuts" << std::endl; } else { if(tea==' y' ) std::cout<< " We have no coffee, but we have tea, and maybe donuts…" << std::endl; else std::cout<< " No tea or coffee, but maybe donuts…" << std::endl; }
如果把足够多的嵌套if语句放在一起,很可能会出错。在程序中处理这种逻辑还有更好的方式。
1.6.4 switch语句
用户常常会面临多项选择的情形,在这种情况下,需要根据整数变量或表达式的值,从许多选项(多于两个)中确定执行哪个语句集。例如抽奖,顾客购买了一张有号码的彩票,如果运气好,就会赢得大奖。例如,如果彩票的号码是147,就会赢得头等奖。如果彩票的号码是387,就会赢得二等奖。如果彩票的号码是29,就会赢得三等奖。其他号码则不能获奖。处理这类情形的语句称为switch语句。
switch语句允许根据给定表达式的一组固定值,从多个选项中进行选择,这些选项称为case。在彩票例子中,有4个case,每个case对应于一个获奖号码,再加上一个默认的case,用于所有未获奖的号码。下面为编写一个switch语句,为给定的彩票号码选择反馈消息:
switch(ticket_number) { case 147: std::cout<<"You win first prize! "; break; case 387: std::cout<<"You win second prize! "; break; case 29: std::cout<<"You win third prize! "; break; default: std::cout<<"Sorry, you lose."; }
switch语句描述起来比其使用难一些。在许多case中如何选择取决于关键字switch后面括号中整数表达式的值。选择表达式的结果也可以是已枚举的数据类型,因为这种类型的值可以自动转换为整数。在本例中,它就是变量ticket_number,必须是整数类型。
可以根据需要,使用多个case值定义switch语句中的所有可能选项。case值显示在case标签中,其形式如下所示:
case case_value:
称其为case标签,是因为它标注了后面的语句。如果选择表达式的值等于case值,就执行该case标签后面的语句。每个case值都必须是唯一的,但不必按特定的顺序,如本例所示。
case值必须是整数常量表达式,即编译器可以计算的表达式,所以它只能使用字面量、const变量或枚举成员。而且,其所包含的所有字面量都必须是整数类型,或者可以强制转换为整数类型。
例子中的default标签标识默认的case,它是一个否则模式。如果选择表达式不对应于任何一个case值,就执行该默认case后面的语句。但是,不一定要指定默认case,如果没有指定它,且没有选中任何case值,switch语句就什么也不做。
从逻辑上看,每一个case语句后面的break语句是绝对必须的,它在case语句执行后跳出switch语句,使程序继续执行switch右花括号后面的语句。如果省略了case后面的break语句,就将执行该case后面的所有语句。注意在最后一个case后面(通常是默认case)不需要break语句,因为此时程序将退出switch语句,但加上break是一个很好的编程习惯,因为这可以避免以后添加另一个case而导致的问题。
提示 switch、case、default和break都是关键字。
· 程序示例——switch语句
下面的例子演示了switch语句的用法:
//这个程序的功能是演示如何使用switch语句 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { int choice=0; //存储选择值 cout << endl << "Your electronic recipe book is at your service."<<endl << "You can choose from the following delicious dishes: " << endl << "1 Boiled eggs"<<endl << "2 Fired eggs"<<endl << "3 Scrambled eggs"<<endl << "4 Coddled eggs"<<endl << endl<<"Enter your selection number: "; cin >> choice; switch(choice) { case 1: cout<<endl<<"Boil some eggs."<<endl; break; case 2: cout<<endl<<"Fry some eggs."<<endl; break; case 3: cout<<endl<<" Scramble some eggs."<<endl; break; case 4: cout<<endl<<" Coddle some eggs."<<endl; break; default: cout<<endl<<"You entered a wrong number, try raw eggs."<<endl; } return 0; }
该例的说明如下。
在输出语句中定义选项,并将选中的数字读入变量choice后,就执行switch语句。该语句在关键字switch的后面,把选择表达式指定为括号中的choice。switch语句中的可能选项放在花括号中,每个选项都用一个case标签来标识。如果choice的值对应于某个case值,就执行该case标签后面的语句。在本例中,每个case只有一个语句和break语句,但一般情况下,case标签后面可以有许多语句,且不需要把它们括在花括号中。
每组case语句后面的break语句把执行权传送给switch后面的语句。break语句不是强制的,但如果不加上它,就会执行所选case之后的所有语句,这通常不是人们希望的操作。读者可以把本例中的break语句删除,看会发生什么。
如果choice的值不对应于所指定的所有case值,就执行default标签后面的语句。如果没有包括default case,且choice的值不等于所有的case值,则switch语句就什么也不做,程序继续执行switch后面的语句,即return语句。
· 程序示例——共享case
每个case值都必须是编译时常量,且必须是唯一的。任何两个case值都不能相同的原因是,如果输入了某个指定值,编译器就无法确定应执行哪些语句。但是,case值不同,并不表示必须执行不同的操作。几个case值可以共享相同的操作,如下面的例子所示。
//这个程序的功能是演示使用多值case #include <iostream> #include <cctype> using std::cin; using std::cout; using std::endl; int main() { char letter=0; cout << endl << " Enter a letter: "; cin >> letter; if ( std::isalpha(letter)) switch(std::tolower(letter)) { case ' a' : case ' e' : case ' i' : case ' o' : case ' u' : cout<<endl<<" You entered a vowel."<<endl; break; default: cout<<endl<<" You entered a consonant."<<endl; } else cout<<endl<<"You did not enter a letter."<<endl; return 0; }
这个程序的输出如下所示:
Enter a letter: E You entered a vowel.
该例的说明如下。
在这个例子中,使用了标准库的一个字符转换例程和switch语句,来确定输入的字符是元音还是辅音。if条件首先检查是否输入了一个字母,而不是其他字符:
if(std::isalpha(letter))
如果isalpha()返回的值是非0值,就执行以下switch语句:
switch(tolower(letter)) { case ' a' : case ' e' : case ' i' : case ' o' : case ' u' : cout<<endl<<" You entered a vowel."<<endl; break; default: cout<<endl<<" You entered a consonant."<<endl; }
switch由tolower()函数的返回值控制。可以只使用变量letter把所有的大写元音字母指定为case值,也可以把所有的小写元音字母指定为case值。如果tolower()函数返回的值对应于一个元音,就显示确认消息。否则就执行默认的case,显示消息“输入的字符是一个辅音”。
如果isalpha()返回0,就不执行switch语句,而执行else语句,输出消息“输入的字符不是字母”。
可以利用if语句把字母的测试和转换为小写形式这两个操作结合起来,但这需要一些技巧,且会使代码变得比较复杂。例如,可以把switch语句改写为:
switch(std::tolower(letter)*(std::isalpha(letter) ! =0)) { case ' a' : case ' e' : case ' i' : case ' o' : case ' u' : cout<<endl<<" You entered a vowel."<<endl; break; case 0: cout<<endl<<"You did not enter a letter."<<endl; break; default: cout<<endl<<" You entered a consonant."<<endl; }
如前所述,如果给isalpha()函数传送非字母字符,它就会返回整数0;如果给它传送字母,它就会返回一个正整数,但这个正整数不一定是1。选择表达式变复杂的原因,是isalpha()函数不会生成bool值。如果可以生成,就可以使用tolower(letter)*isalpha (letter),当isalpha()返回false时,这个表达式等于0,否则就等于tolower()所返回的小写字母,因为true会转换为1。
另一个方法是把isalpha()返回的值强制转换为bool类型。接着,就可以把switch语句改写为:
switch(tolower(letter)*static_cast<bool>(isalpha(letter))) { case ' a' : case ' e' : case ' i' : case ' o' : case ' u' : cout<<endl<<" You entered a vowel."<<endl; break; case 0: cout<<endl<<"You did not enter a letter."<<endl; break; default: cout<<endl<<" You entered a consonant."<<endl; }
这段代码可以正常执行,因为isalpha()返回的整数被强制转换为bool,编译器会把这个值转换为int,进行乘法运算,所以它最终是0或1。但是,switch语句会变得很混乱。使用if的原始版本肯定是代码最简洁的一个版本,因此是首选的版本,尽管其逻辑不是很好。
1.6.5 while语句
while循环使用逻辑表达式来控制循环体的执行,该循环的一般形式如图1-3所示。
图1-3 while循环的执行过程
这个流程图显示了该循环的逻辑。只要条件的值为true,就执行循环语句或循环语句块。当条件为false时,就执行循环体后面的语句。可以使用任意表达式控制循环,只要该表达式的值为bool类型或整数类型即可。
提示 如果控制循环的条件表达式结果为整数,只要该数值不是0,循环就继续。如前所述,任何非0整数都会被转换为bool类型的true,只有0才被转换为bool类型的false。
当然,while是一个关键字,不能用它来命名程序中的任何元素。
· 程序示例——使用while循环
下面使用while循环计算从1到n的整数和。
//这个程序的功能是演示使用多值while loop #include <iostream> #include <iomanip> using std::cin; using std::cout; using std::endl; int main() { int n=0; cout << "How many integers do you want to sum: "; cin >> n; int sum=0; //存储求和 int I =1; //存储求和次数 cout << "Values are: "<< endl; while(i<=n) { cout << std::setw(5)<<i; //输出i值 if(i%10) ==0) cout<<endl; //等于10换行 sum += i++; } cout<<endl<<"Sum is "<<sum<<endl; //输出sum值 return 0; }
执行这个程序,输出结果如下所示:
How many integers do you want to sum: 25 Values are: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Sum is 325
该例的说明如下。
main()中的前两个语句读取要求和的整数个数。变量n的值用于确定while循环何时结束求和。
在开始循环之前,定义并初始化一个变量i,它存储了当前加到总和中的整数;然后定义变量sum,它存储总和的值。
int sum=0; //存储总和的值 int i=1; //存储当前加入总和的整数
开始循环时,i等于1, sum是0。
循环条件是表达式i<=n,只要i不超过n,它就是true。此时,执行循环语句:
cout <<std::setw(5)<<i; //输出i值 if(i%10) ==0) cout <<endl; //等于10换行 sum += i++;
首先,输出i的当前值,其字段宽度为5个字符。为了使数字的输出比较整齐,在输出10个数值后输出一个换行符。这样一行有10个数字,只要最大的数值不超过5个数字,所有的数字都可以很好地排列在各个列中。这里最后的一个语句把总和放在sum中。由于使用了递增运算符的后缀形式,因此i的当前值加到sum上,之后递增1。这就执行完了一次循环块,接下来执行传回到while处,再次用i的新值测试循环条件。
这种模式继续重复下去,i递增到2、3、4等,直到n为止。但是,在加上n后,i就递增到n+1,此时循环条件为false,循环停止,程序继续执行循环体后面的语句,即输出总和:
cout<<"Sum is "<<sum<<endl; //输出总值
其结果是循环执行n次,即把从1到n的整数加在一起。
提示 这说明了循环的工作原理,但如果用户喜欢数学,就知道可以用公式n*(n+1)/2来计算整数1到n的总和,实际上并不需要这样一个循环。
do-while循环类似于while循环,只要指定的循环条件为true,循环就将继续执行下去。其区别是在do-while循环中,循环条件是在循环的最后检查,而不是在开始检查,所以循环语句至少要执行一次。
do-while循环的逻辑和一般形式如图1-4所示。特别要注意while语句后面的分号,这是必须的。如果遗漏了它,程序就不会被编译通过。
如果代码块总是要执行一次,也可以执行多次,使用这个逻辑就再合适不过了。下面用一个例子来说明。
图1-4 do-while循环的执行过程
· 程序示例——使用do-while循环控制输入
假定要计算任意个输入值的平均值,这些输入值可以是在某个时间段搜集来的温度。事先无法知道输入多少个值,但可以假定至少会有一个输入值,否则程序就根本不会执行。此时最好使用do-while循环。下面是程序:
//这个程序的功能是演示使用多值 do-while控制输入 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { char ch=0; //存储输入值 int count=0; //计算输入次数 double temperature=0.0; //存储一个临时值 double average=0.0; //存储平均值 cout<<endl; do { cout<<"Enter a temperature reading: "; //提示输入t cin>> temperature; //读出值 average += temperature; //计算临时值 count++; //Increment value count cout<<"Do you want to enter another? (y/n):"; cin>>ch; //得到请求 cout<<endl; }while(ch==' y' ); average /= count; //求平均值 cout<<"Average temperature is "<<average <<endl; return 0; }
该程序的运行结果如下所示:
Enter a temperature reading: 53 Do you want to enter another? (y/n): y Enter a temperature reading: 65.5 Do you want to enter another? (y/n): y Enter a temperature reading: 74 Do you want to enter another? (y/n): y Enter a temperature reading: 69.5 Do you want to enter another? (y/n): n Average temperature is 65.5
该例的说明如下。
首先,程序声明并初始化了循环和计算需要的变量:
char ch=0; //存储输入值 int count=0; //计算输入次数 double temperature=0.0; //存储一个临时值 double average=0.0; //存储平均值
变量ch用于存储对以后输入提示的响应,在循环的最后进行测试。只要输入了y,程序就继续读取输入值(理想情况下,程序还应接受Y,稍后将修正这个错误)。其他三个变量的目的在注释中已说得很清楚。
读取输入值的循环如下所示:
do { cout<<"Enter a temperature reading: "; //提示输入t cin>> temperature; //读出值 average += temperature; //计算临时值 count++; //Increment value count cout<<"Do you want to enter another? (y/n):"; cin>>ch; //得到请求 cout<<endl; }while(ch==' y' );
因为这里使用的是do-while循环,所以至少要读取一个值。在提示输入后,循环语句块就会从键盘中读取一个值,并把它存储在temperature变量中,接着把这个值加到average上。在循环结束时,average就包含了所有输入值的总和。程序还递增了count,因为需要知道输入了多少个值,才能计算平均值。在循环中,最后提示输入y或n,指出是否还要输入更多的值。在输入n(实际上可以输入除y之外的所有其他字符)后,循环条件ch==' y’就是false,循环终止。程序继续执行下面的语句:
average /= count; //计算平均值
这个语句用在average中累加的总和除以输入值的个数count,得到平均值。存储在count中的值自动转换为double,与average的类型相同,之后执行除法操作。输出结果后,程序结束。
当然,控制while(或do-while)循环时不仅可以使用简单的比较,还可以使用结果为true或false的任何表达式,或者是可以生成一个整数值的任意表达式。前面例子的一个问题是,如果从键盘上输入了Y,而不是y,程序就会终止。这不是很好的编程方式,最好允许输入y和Y以继续循环。为此,只需要把循环条件修改为:
} while(ch==' y' | | ch==' Y' );
这时输入大写的Y或小写的y都可以使循环继续。下面介绍另一种方式。首先,在代码的开始包含头文件,如下所示:
//这个程序的功能是演示使用多值 do-while loop 控制输入 #include <iostream> #include <cctype>
现在就可以把下面的循环条件放在上面的程序中,以确保用ch的小写版本与y比较:
} while(std::tolower(ch)==' y' );
如前所述,还可以把等于数值的表达式用做循环条件。在这种情况下,编译器会把表达式的结果转换为bool类型。记住,0转换为false,而任何非0值,无论正负,都会被转换为true。因此,只有在条件为0时,用数值控制的while循环才会终止。
1.6.6 for语句
for循环主要用于让语句或语句块执行预定的次数,但也可以用于其他方式。
可以使用以分号分隔开的三个表达式来控制for循环,这三个表达式放在关键字for后面的括号中,如图1-5所示。
图1-5 for循环的控制方式
控制for循环的任一表达式或所有表达式都可以省略,但分号则必须存在。这么做的原因是不明显的,但非常有效,本章后面将探讨省略表达式的一些情况。图1-6显示了for循环的流程逻辑。
图1-6 for循环的流程逻辑
初始化表达式只在循环的开始处计算一次,接着检查循环条件,如果它是true,就执行循环语句或语句块。如果条件是false,就跳过循环语句,执行循环体后面的语句。在这方面,for循环与while循环很相近,与do-while循环则不太相似。
假定条件是true,则执行完循环语句后接着计算迭代表达式,之后再次检查条件,看看是否需继续循环。
· 程序示例——使用for循环
在for循环的一般用法中,第一个表达式用于初始化一个计数器,第二个表达式用于检查计数器是否达到了给定的极限,第三个表达式用于递增计数器。下面是一个例子,它使用for循环来计算整数的总和:
//这个程序的功能是演示使用多值for循环 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { int sum=0; //求和 int count=0; //求和的次数 cout << "How many integers do you want to sum? "; cin >> count; for (int i=1; i <=count; i++) sum +=i; cout<< endl << "The sum of the integers from 1 to "<<count << "is "<<sum <<endl; return 0; }
这个程序的输出结果如下所示:
How many integers do you want to sum? 25 The sum of the integers from 1 to 25 is 325
该例的说明如下。
用下面的语句可以从键盘上读取要求和的整数的上限:
cout<<" How many integers do you want to sum? "; cin >> count;
累加总和的循环语句进行了缩进,以显示这是for循环的一部分:
for (int i=1; i <=count; i++) sum +=i;
因为循环语句只有一条,所以没有使用花括号。这个循环的结果是在变量sum中累加从1到count的整数。循环中的执行顺序如下所示:
(1)执行第一个表达式。此表达式声明整型变量i,并把它初始化为1。
(2)执行第二个表达式,检查i是否小于或等于count。如果i <=count为true,就进入第(3)步。如果它等于false,就进入第(6)步。
(3)执行循环语句,把i的当前值加到sum上。
(4)执行第三个表达式,递增i的值。
(5)返回到第(2)步。
(6)退出循环。
该程序将i的值连续加到sum中,从1开始,直到count的值为止。最后,当i递增到count+1时,for循环结束。程序接着执行循环体后面的语句,即输出在sum中累加的总和。
在for循环的初始化表达式中声明变量是合法的,这种用法很普遍。