0%

C++ Primer 第五章

语句

简单语句

空语句

当某个地方语法上需要,逻辑上不需要,则会使用,且应该加注释标明。

1
2
3
//重复读入数据直至到达文件末尾或某次输入的值等于sought
while (cin >> s &&s != sought)
; //空语句

复合语句

指用花括号括起来的(可能为空的)语句和声明的序列,复合语句也被称作块(block)。一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。通常,名字在有限的区域内可见,该区域从名字定义处开始,到名字所在的(最内层)块的结尾为止,通常在语法上需要一条语句,但逻辑上需要多条语句的使用。

所谓空块,是指内部没有任何语句的一对花括号。空块的作用等价于空语句:


语句作用域

可以在if、switch、while和 for语句的控制结构内定义变量。定义在控制结构当中的变量只在相应语句的内部可见,一旦语句结束,变量也就超出其作用范围了。


条件语句

if语句

1
2
3
4
5
6
7
//if else语句的形式是
if (condition){
statement
}
else{
statement2
}

悬垂else

1
2
3
4
5
6
//错误:实际的执行过程并非像缩进格式显示的那样; else 分支匹配的是内层if语句
if (grade % 10 >= 3)
if(grade % 10 >7)
lettergrade += '+'; //末尾是8或者9的成绩添加一个加号
else
lettergrade += '-';1/末尾是3456或者7的成绩添加一个减号!

这里程序将else与内层if匹配,导致错误,加上花括号可避免此问题。

switch语句

1
2
3
4
5
6
7
8
switch (ch){
case 'a':
++aCnt;
break;
case 'e':
++eCnt;
break;
}

break 跳转出switch,case必须是整形常量表达式,一个switch内case不能相同。

内部控制流

有时候故意省略break语句,使得程序连续执行多个标签。如:也许我们想统计的是所有元音字母出现的总次数:

1
2
3
4
5
6
7
8
9
10
11
12
unsigned voweICnt =o;
// ...
switch (ch){
//出现了a、e、 i、o或u中的任意一个都会将vowelCnt的值加1
case 'a' :
case 'e' :
case 'i' :
case 'o' :
case 'u' ://也可以不换行,写成case 'a' : case 'e' : case 'i' : case 'o' : case 'u' :
++Vowelcnt;
break;
}

default标签

如果没有任何一个case 标签能匹配上 switch 表达式的值,程序将执行紧跟在default 标签(default label)后面的语句。例如,可以增加一个计数值来统计非元音字母的数量,只要在 default分支内不断递增名为otherCnt的变量就可以了。

内部变量定义

如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外。

1
2
3
4
5
6
7
8
case true:{
//正确:声明语句位于语句块内部
string file_name = get_file_name () ;
// ...
}
break;
case false:
if (file_name . empty())//错误:file_name不在作用域之内

迭代语句

while循环

当不确定循环多少次时或循环结束后访问循环控制变量

1
2
3
4
5
6
7
8
9
10
11
vector<int> v;
int i;
//重复读入数据,直至到达文件末尾或者遇到其他输入问题
while (cin >> i)
v.push_back (i);
//寻找第一个负值元素
auto beg = v.begin ( );
while (beg != v.end () && *beg >= 0)
++beg;
if (beg == v.end( ))
//此时我们知道v中的所有元素都大于等于0

传统for语句

1
2
for (init-statemen; condition; expression)
statement

for语句头中的多重定义

init-statemen可以定义多个变量,但只能定义一种类型。

for语句头省略

for语句头能省略掉init-statement、condition和 expression中的任何一个(或者全部),但分号必须保留。

范围for语句

1
2
for (declaration : expression)
statement

do while语句

do while语句( do while statement)和 while语句非常相似,唯一的区别是,do while语句先执行循环体后检查··条件。不管条件的值如何,我们都至少执行一次循环。do while语句的语法形式如下所示:

1
2
3
do
statement
while (condition) ;

do while语句应该在括号包围起来的条件后面用一个分号表示语句结束。


跳转语句

break语句

break语句(break statement)负责终止离它最近的while、do while、for或switch语句,并从这些语句之后的第一条语句开始继续执行。

continue语句

continue语句(continue statement)终止最近的循环中的当前迭代并立即开始下一次迭代。continue语句只能出现在 for、while和 do while循环的内部,或者嵌套在此类循环里的语句或块的内部。和 break语句不同的是,只有当switch语句嵌套在迭代语句内部时,才能在 switch里使用continue,它会使程序直接跳出switch,并重新执行switch外的循环。

goto语句

不要在程序中使用goto语句,因为它使得程序既难理解又难修改。


try语句块和异常处理

throw表达式

1
2
3
4
5
6
7
8
9
10
sales_item item1, item2;
cin >> item1 >> item2;
//首先检查item1和item2是否表示同一种书籍
if (item1.isbn () == item2 .isbn () ) {
cout << item1 + item2 << endl;
return 0; //表示成功
}else {
cerr << "Data must refer to same ISBN" << endl;
return -l; //表示失败
}

在真实的程序中,应该把对象相加的代码和用户交互的代码分离开来。此例中,我们改写程序使得检查完成后不再直接输出一条信息,而是抛出一个异常:

1
2
3
4
5
//首先检查两条数据是否是关于同一种书籍的
if (item1.isbn () != item2 .isbn ( ))
throw runtime_error ( "Data must refer to same ISBN");
//如果程序执行到了这里,表示两个ISBN是相同的
cout << item1 + item2 << endl ;

在这段代码中,如果ISBN不一样就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。

try语句块

1
2
3
4
5
6
7
try {
program-statements
} catch (exception-declaration){
handler-statements
} catch (exception-declaration) {
handler-statements
} //...
  • try语句块的一开始是关键字try,随后紧跟着一个块,跟在try 块之后的是一个或多个catch子句。catch子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)以及一个块。当选中了某个catch子句处理异常之后,执行与之对应的块。catch 一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。

  • try语句块中的program-statements组成程序的正常逻辑,像其他任何块一样,program-statements可以有包括声明在内的任意C++语句。一如往常,try语句块内声明的变量在块外部无法访问,特别是在catch子句内也无法访问。

编写处理的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
while (cin >> item1 >> item2) {
try {
//执行添加两个Sales_item对象的代码
//如果添加失败,代码抛出一个runtime_error异常
} catch (runtime_error err) {
//提醒用户两个ISBN必须一致,询问是否重新输入
cout << err.what ()
<< "\nTry Again? Enter y or n" << endl;
char c ;
cin >> c;
if(!cin || c== 'n') break;
}
}

那么如果输入书籍不同,则会输出两句话:

Data must refer to same ISBN

Try Again? Enter y or n

函数在寻找处理代码的过程中退出(*****)
在复杂系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如,一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块可能调用了包含又一个try语句块的新函数,以此类推。

寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的 catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的 catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。

如果最终还是没能找到任何匹配的 catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。

处理复杂异常超出了本书的范围。


标准异常(*)

表5.1:定义的异常类

问题
exception 最常见的问题
runtime_error 只有在运行时才能检测出的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 程序逻辑错误
domain_error 逻辑错误:参数对应的结果值不存在
invalid_argument 逻辑错误:无效参数
length_error 逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值

我们只能以默认初始化的方式初始化 exception,bad_alloc和 bad _cast对象,不允许为这些对象提供初始值。

其他异常类型的行为则恰好相反:应该使用string对象或者C风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。