目录
通常情况下,语句是顺序执行的。但除非是最简单的程序,否则仅有顺序执行远远不够。因此,C++ 语言提供了一组控制流(flow-of-control)语句以支持更复杂的执行路径。
5.1 简单语句
C++ 语言中的大多数语句都是以分号结束,一个表达式,比如 ival + 5,末尾加上分号就变成了表达式语句(expression statement)。表达式语句的作用是执行表达式并丢弃掉求值结果。
ival + 5; //一条没什么用的表达式
cout << ival; //一条有用的表达式
空语句
最简单的语句是空语句(null statement),空语句中只含有一个单独的分号。
; //空语句
如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。一种常见情况是,当循环的全部工作在条件部分就可以完成时,我们通常会用到空语句。例如,我们想读取输入流的内容直到遇到一个特定的值为止,除此之外什么事情也不做。
//重复读入数据直至到达文件末尾或某次输入的值等于sought
while(cin >> s && s != sought)
;//空语句
使用空语句时应该加上注释。
别漏写分号,也别多写分号
//出现了糟糕的情况:额外的分号,循环体是那条空语句
while(iter != svec.end()); //while循环体是那条空语句
++iter; //递增运算不属于循环的一部分
复合语句(块)
复合语句(compound statement)是指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称做块。
如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,则使用复合语句。
块不以分号作为结束。
所谓空块,是指内部没有任何语句的一对花括号。
while (cin >> s && s != sought)
{ } //空块
5.2 语句作用域
可以在if、switch、while和 for 语句的控制结构内定义变量。定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量也就超出其作用范围了。
5.2 节练习
(a)非法,它的原意是希望在 while 语句的控制结构当中定义一个 string::iterator 类型的变量 iter,然后判断 iter 是否到达了 s 的末尾,只要还没有到达末尾就执行循环体的内容,但是该式把变量的定义和关系判断混合在了一起,如果要使用 iter 与其他值比较,必须首先为 iter 赋初值。修改后为:
string::iterator iter = s.begin(); while(iter != s.end()) { ++iter; ... }
(b)是非法的,变量 status 定义在 while 循环控制结构的内部,其作用域仅限于 while 循环。修改后:
bool status; while(status = find(word)) {...} if(!status) {...}
5.3 条件语句
C++语言提供了两种按条件执行的语句。一种是 if 语句,它根据条件决定控制流;另外一种是 switch 语句,它计算一个整型表达式的值,然后根据这几个值从几条路径中选择一条。
5.3.1 if 语句
if 语句(if statement)的作用是:判断一个指定的条件是否为真,根据判断结果决定是否执行另外一条语句。if 语句有以下两种形式:
//第一种
if (condition)
statement
//第二种
//if else 语句的形式
if (condition)
statement1
else
statement2
注意花括号
有一种常见的错误:本来程序中有几条语句应该作为一个块来执行,但是我们忘了用花括号把这些语句包围。在下面的例子中,添加加号减号的代码将被无条件地执行,这显然违背了我们的初衷:
if(grade < 60)
lettergrade = score[0];
else //错误:缺少分号
lettergrade = score[(grade - 50)/10];
//虽然下面的语句从形式上看有缩进,但是因为没有花括号,
//所以无论什么情况都会执行接下来的代码
//不及格的成绩也会添加上加号或减号,这显然是错误的
if(grade != 100)
if(grade % 10 > 7)
lettergrade += '+';//末尾是8或者9的成绩添加一个加号
else if(grade % 10 < 3)
lettergrade += '-';//末尾是0、1、2的成绩添加一个加号
垂悬 else
当有多个 if 语句嵌套时,如何知道某个给定的 else 是和哪个 if 匹配呢?
这个问题通常被称作 悬垂问题(dangling else )。就C++ 而言,它规定 else 与离它最近的尚未匹配的 if 匹配,从而消除程序的二义性。
使用花括号控制执行路径
5.3.1 节练习
//练习5.5:写一段自己的程序,使用if else 语句实现把数字成绩转换成字母成绩的要求
int grade;
cout << "请输入您的成绩:";
cin >> grade;
if (grade < 0 || grade >100)
{
cout << "该成绩不合法" << endl;;
}
if (grade == 100)
{
cout << "等级成绩是:" << "A++" << endl;
}
if (grade < 60)
{
cout << "成绩等级是:" <<"F"<< endl;
}
int iU = grade / 10;//成绩的10位数
int iT = grade % 10;//成绩的个位数
string score, level, lettergrade;
//根据成绩的10位数确定score
if (iU == 9)
{
score = "A";
}
else if (iU == 8)
{
score = "B";
}
else if (iU == 7)
{
score = "C";
}
else
{
score = "D";
}
//根据成绩的个位数确定level
if (iT < 3)
{
level = "-";
}
else if (iT > 7)
{
level = "+";
}
else
{
level = "";
}
//累加求得成绩
lettergrade = score + level;
cout << "等级成绩是:" << lettergrade << endl;
//练习5.6:改写上一题的程序,使用条件运算符代替 if else语句
//根据成绩的10位数确定score
score = (iU == 9) ? "A"
: (iU == 8) ? "B"
: (iU == 7) ? "C" : "D";
//根据成绩的个位数确定level
level = (iT < 3) ? "-"
: (iT > 7) ? "+" : "";
5.3.2 switch 语句
switch 语句(switch statement)提供了一条便利的途径使得我们能够在若干固定选项中做出选择。
case关键字和它对应的值一起被称为 case 标签(case label)。case 标签必须是整型常量表达式。
漏写break容易引发缺陷
switch (ch)
{
case 'a':
++aCnt;
case'b':
++bCnt;
case 'c':
++cCnt;
case'o':
++oCnt;
case 'u':
++uCnt;
}
假设 ch 的值是'c',程序会直接执行case 'c' 标签后面的代码,将 cCnt的值加 1。接下来,程序将跨越 case 标签的边界,接着递增 oCnt和 uCnt。
default 标签
如果没有任何一个 case 标签能匹配上 switch 表达式的值,程序将执行紧跟在 default 标签(default label)后面的语句。
5.3.2 节练习
//练习5.9:编写一段程序,使用一系列 if 语句统计从 cin 读入的文本中多少元音字母
char ch;
cout << "请输入一段文本:";
unsigned int cnt = 0;
while (cin >> ch)
{
if (ch == 'a')
{
++cnt;
}
if (ch == 'e')
{
++cnt;
}
if (ch == 'i')
{
++cnt;
}
if (ch == 'o')
{
++cnt;
}
if (ch == 'u')
{
++cnt;
}
}
cout << "共有" << cnt << "个元音字母" << endl;
//练习5.10:改进上一段程序,使得大小写形式都能被统计
char ch;
cout << "请输入一段文本:";
unsigned int cnt = 0;
while (cin >> ch)
{
ch = tolower(ch);//全部转换成小写
if (ch == 'a')
{
++cnt;
}
if (ch == 'e')
{
++cnt;
}
if (ch == 'i')
{
++cnt;
}
if (ch == 'o')
{
++cnt;
}
if (ch == 'u')
{
++cnt;
}
}
cout << "共有" << cnt << "个元音字母" << endl;
//书上的方法是用 switch 语句
//练习5.11:修改统计元音字母的程序,使其也能统计空格、制表符和换行符的数量
char ch;
cout << "请输入一段文本:" << endl;
unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
unsigned int spaceCnt = 0, tabCnt = 0, newlineCnt = 0;
while (cin >> ch)
{
switch (ch)
{
case 'a':
case 'A':
++aCnt;
break;
case 'e':
case 'E':
++eCnt;
break;
case 'i':
case 'I':
++iCnt;
case 'o':
case 'O':
++oCnt;
break;
case 'u':
case 'U':
++uCnt;
break;
case ' ':
++spaceCnt;
break;
case '\t':
++tabCnt;
break;
case '\n':
++newlineCnt;
break;
}
}
cout << "元音字母 a 的数量是:" << aCnt << endl;
cout << "元音字母 e 的数量是:" << eCnt << endl;
cout << "元音字母 i 的数量是:" << iCnt << endl;
cout << "元音字母 o 的数量是:" << oCnt << endl;
cout << "元音字母 u 的数量是:" << uCnt << endl;
cout << "空格 的数量是:" << spaceCnt << endl;
cout << "制表符 的数量是:" << tabCnt << endl;
cout << "换行符 的数量是:" << newlineCnt << endl;
//练习5.12:修改上述元音字母的程序,使其能统计含有以下两个字符的字符序列的数量:ff、fi、fl
char ch,prech='\0';
cout << "请输入一段文本:" << endl;
unsigned int ffCnt = 0, flCnt = 0, fiCnt = 0;
while (cin >> ch)
{
bool bl = true;
if (prech == 'f')
{
switch (ch)
{
case 'f':
++ffCnt;
break;
case 'l':
++flCnt;
break;
case 'i':
++fiCnt;
default:
break;
}
}
if (!bl)
prech = '\0';
else
{
prech = ch;
}
}
cout << "ff 的数量是:" << ffCnt << endl;
cout << "fl 的数量是:" << flCnt << endl;
cout << "fi 的数量是:" << fiCnt << endl;
5.4 迭代语句
迭代语句通常称为循环,它重复执行操作直到满足某个条件才停下来。while 和 for 语句在执行循环体之前检查条件, do while 语句先执行循环体,然后再检查条件。
5.4.1 while 语句
只要条件为真, while语句(while statement)就重复地执行循环体,它的语法形式如下:
while (condition)
statement
只要condition的求值结果为真就一直执行 statement,condition 不能为空,如果 condition第一次求值就得 false ,statement 一次都不执行。
使用 while 循环
当不确定到底要迭代多少次时,使用 while 循环比较合适。
5.4.1 节练习
string currString,preString =" ",maxString;
unsigned int currCnt=1,maxCnt=0;
cout << "请输入一段字符串:";
while (cin >> currString)
{
if (preString == currString)
{
++currCnt;
if (currCnt > maxCnt)
{
maxCnt = currCnt;
maxString = currString;
}
}
else
{
currCnt = 1;
}
preString = currString;
}
if (maxCnt > 1)
{
cout << "出现最多的字符串是:" << maxString
<< ",次数是:" << maxCnt << endl;
}
else
{
cout << "每个字符都只出现了一次" << endl;
}
5.4.2 传统的 for 语句
for 语句的语法形式是:
for (init-statement;condition;expression)
statement
关键字 for 及括号里的部分称作 for 语句头。
init-statement 必须是一下三种形式中的一种:声明语句、表达式语句或者空语句,因为这些语句都以分号作为结束,所以 for 语句的语法形式也可看作:
for(initializer;condition;expression)
statement
一般情况下,init-statement 负责初始化一个值,这个值将随着循环的进行而改变。condition作为循环控制的条件,只要 condition 为真,就执行一次 statement。如果 condiiton第一次的求值结果就是 false ,则 statement 一次也不会执行。
传统 for 循环的执行流程
牢记 for 语句头中定义的对象只在 for 循环体内可见。
for 语句头中的多重定义
init-statement 可以定义多个对象,但是只能有一条声明语句,因此,所有变量的基础类型必须相同。
省略 for 语句头的某些部分
for 语句头可以省略掉 init-statement、condition和 expression中的任何一个(或者全部)。
5.4.2 节练习
//练习5.17:假设有两个包含整数的不等长的vector 对象,编写一段程序
//检验其中一个 vector 对象是否是另一个的前缀。为了实现这个目标
//对于两个不等长的 vector 对象,只需挑出长度较短的那个
//把它的所有元素和另一个 vector 对象比较即可
vector<int> v1 = { 0,1,1,2 };
vector<int> v2 = { 0,1,1,2 ,3,5,8 };
vector<int> v3 = { 3,5,8 };
vector<int> v4 = { 3,5,0,9,2,7 };
auto it1 = v1.begin();
auto it2 = v2.begin();
auto it3 = v3.begin();
auto it4 = v4.begin();
//设定循环条件:v1和v2都尚未检查完
while (it1 != v1.end() && it2 != v2.end())
{
if (*it1 != *it2)
{
cout << "v1 和 v2 之间不存在前缀关系" << endl;
break;
}
++it1;
++it2;
}
if (it1 == v1.end());
{
cout << "v1 是 v2 的前缀" << endl;
}
if (it2 == v2.end())
{
cout << "v2 是 v1 的前缀" << endl;
}
5.4.3 范围 for 语句
C++11 新标准引入。范围 for 语句(range for statement)的语法形式是:
for(declaration : expression)
statement
expresion 表示的必须是一个序列,比如用花括号括起来的初始值列表、数组、 或者 vector或 string 等类型的对象,这些类型的共同特点是拥有能返回迭代器的 begin 和 end 成员。
5.4.4 do while 语句
do while 语句(do while statement)和 while 语句非常相似,唯一的区别是 do while 语句先执行循环体后检查条件。不管条件的值如何,都至少执行一次循环。do while语句的语法形式如下:
do
statement
while (condition);
5.4.4 节练习
//练习5.19:
string str1, str2;
do
{
cout << "请输入两个字符串:" << endl;
cin >> str1 >> str2;
if (str1.size() == str2.size())
{
cout << "两个字符串一样长" << endl;
}
else if (str1.size() < str2.size())
{
cout << "较短的字符串为:" << str1 << endl;
}
else
{
cout << "较短的字符串为:" << str2 << endl;
}
} while (cin);
5.5 跳转语句
C++ 提供了4种跳转语句:break、continue、goto 和 return 。
5.5.1 break语句
break 语句(break statement)负责终止离它最近的 while、 do while 、for 或 switch 语句,并从这些语句之后的第一条语句开始继续执行。
break 语句只能出现在迭代语句或者 switch 语句内部(包括嵌套在此类循环里的语句或块的内部)。break 语句的作用范围仅限于最近的循环或者 switch。
5.5.1 节练习
//练习5.20:编写一段程序,从标准输入中读取 string 对象的序列直到连续出现两个相同的单词
//或者所有单词都读完为止。使用while 循环一次读取一个单词,当一个单词练习出现两次时使用
//break 语句终止循环。输出连续重复出现的单词,或说明没有单词连续重复。
string currString, preString=" ";
bool flag = true;
cout << "请输入一个单词:" << endl;
while (cin>>currString)
{
if (currString == preString)
{
flag = false;
cout << "连续重复的单词是:" << currString << endl;
break;
}
else
{
preString = currString;
}
}
if(flag)
cout << "没有连续重复的单词" << endl;
5.5.2 continue 语句
continue 语句(continue statement)终止最近的循环中的当前迭代并立即开始下一次迭代。continue 语句只能出现在 for、while、和 do while 循环的内部,或者嵌套在此类循环里的语句或块的内部。continue 语句仅作用于离它最近的循环。和 break 语句不同的是,只有当 switch 语句嵌套在迭代语句内部时,才能在switch 里使用 continue。
continue 语句中断当前的迭代,但是仍然继续执行循环。
5.5.2 节练习
//练习:5.21:修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头
string currString, preString = " ";
char arr[] = { 'A','B','C' };
bool flag = true;
cout << "请输入一个单词:" << endl;
while (cin >> currString)
{
if (!isupper(currString[0]))
continue;
if (currString == preString)
{
flag = false;
cout << "连续重复的单词是:" << currString << endl;
break;
}
else
{
preString = currString;
}
}
if (flag)
cout << "没有连续重复的单词" << endl;
5.5.3 goto 语句
goto 语句(goto statement)的作用在从 goto 语句无条件跳转到同一函数内的另一条语句。
建议:不要在程序中使用 goto 语句,因为它使得程序既难理解又难修改。
goto 语句的语法形式是:
goto label;
其中, label 是用于标识一条语句的标识符。带标签语句(labeled statement)是一种特殊的语句,在它之前有一个标识符以及一个冒号:
end : return;//带标签语,可以作为 goto 的目标
标签标识符独立于变量或其他标识符的名字,因此,标签标识符可以和程序中其他实体的标识符使用同一个名字而不会互相干扰。goto 语句和控制权专项的那条带标签的语句必须位于同一个函数中。
5.6 try 语句块和异常处理
异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++ 语言中,异常处理包括:
- throw表达式(throw expression),异常检测部分使用 throw 表达式来表示它遇到了无法处理的问题。我们说 throw 引发(raise)了异常。
- try 语句块(try block),异常处理部分使用 try 语句块处理异常。 try 语句块以关键字 try 开始,并以一个或多个 catch 子句(catch clause)结束。 try 语句块中代码抛出的异常通常会被某个 catch 子句处理。因为catch 子句“处理”异常,所以它们也被称为异常处理代码(exception handler)。
- 一套异常类(exception class),用于在 throw 表达式和相关的 catch 子句之间传递异常的具体信息。
5.6.1 throw 表达式
程序的异常检测部分使用 throw 引发一个异常。throw 表达式包含关键字 throw 和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw 表达式后面通常紧跟一个分号,从而构成一条表达式语句。
5.6.2 try 语句块
try 语句块的通用语法形式是:
try{
program-statement
}catch (exception-declaration){
handler-statements
}catch (exception-declaration){
handler-statements
} //...
catch 子句包括三部分:关键字 catch 、括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)以及一个块。当选中了某个 catch 子句处理异常之后,执行与之对应的块。catch 一旦完成,程序跳转到 try 语句块最后一个 catch 子句之后的那条语句继续执行。
函数在寻找处理代码的过程退出
如果最终没有找到匹配的 catch 子句,程序转到名为 terminate 的标准库函数。一般情况下,执行该函数将导致程序非正常退出。
5.6.3 标准异常
C++ 标准定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在4个头文件中: