第五章:语句
一般来说,语句是顺序执行的,但仅有顺序执行远远不够,因此,C++语言提供了一组控制流语句以支持更复杂的执行路径。
一.简单语句
一个表达式加上分号就成了表达式语句,表达式语句的作用是执行表达式并丢弃掉结果。
只含有一个分号的语句称为空语句,用在语法上需要语句但是逻辑上又不需要语句的地方。
语句块也被称作复合语句,复合语句可被看作是有多条语句的一条语句。一个块就是一个作用域。
二.语句作用域
定义在控制结构(for,if,while,switch)内的变量只在相应语句的内部可见。
三.条件语句
switch语句计算一个整型表达式的值,然后根据这个值从几条执行路径中选择一条。
注意,if和else比较多的情况下,容易分不清else到底是哪个if的,C++语言规定else与离它最近的尚未匹配的if匹配。
switch语句首先对括号里的表达式求值,表达式的值转换成整数类型,然后与每个case标签的值进行比较,case标签的值必须是整型常量表达式,程序从该标签之后的第一条语句开始执行,直至到达switch的末尾或者遇到break。
case语句的末尾一般要加break语句,如果每加,那么最好写注释说明程序的逻辑。
如果没有任何case标签匹配值,那么可以定义一个default标签,程序执行该标签的值,一般就算没用default,也可以加上这个默认的标签。
case标签后定义的变量作用域是在整个switch语句中,所以其他case标签与default标签也可以使用,但是如果case标签定义变量时初始化了,则是错误的,因为不一定会执行这个初始化。即在case标签后定义变量相当于在switch中定义了一个变量,但是不能初始化。如果要初始化的话,要把它定义为case标签专属的且其他标签不能再用了。可以在case标签后加上一个语句块达到该效果。(注意前一种情况我的IDE没有提示报错,但是编译的时候还是出错了)
标签不应该孤零零的出现,即标签后面必须跟上一个语句或者另一个case标签,标签后面什么都没有是错误的语法格式,例如:
switch(a) {
case 1: // 换成default也是错的
}
所以如果要单独写的话必须加个空语句或者空块。
四.迭代语句
迭代语句通常称为循环。
for语句头中对初始化语句可以定义多个对象,但是只能有一个声明,即定义的变量的基础类型必须相同。
for语句头能省略掉三个部分的任意一个部分。
在范围for语句中,预存了end()的值,所以不能增删序列,否则end()的值可能失效。
一般来说,不能在do while语句中的条件定义变量(因为while在do的后面,所以在条件中定义了变量也不能再do中使用),while也不能使用在do中定义的变量,即do和while不能互相使用各自定义的变量。总而言之,不能再while的条件中定义变量,while的条件中也不能使用do中定义的变量。
五.跳转语句
跳转语句中断当前的执行过程,C++提供了四种跳转语句:break,continue,goto,return。
goto语句的作用是从goto语句无条件跳转到同一函数内的另一条语句。
goto语句的语法形式为 goto label;其中label是用于标识一条语句的标示符。带标签语句为label : expr。
标签独立于变量的名字,所以可以与其他对象重名。
不要在程序中使用goto语句,它使得程序既难理解又难修改。
六.try语句块和异常处理
异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++中,异常处理包括:
-
throw表达式:异常检测部分使用throw表达式来表示它遇到了无法处理的问题,所以我们说throw引发了异常。
-
try语句块:异常处理部分使用try语句块来处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。
-
一套异常类:用于在throw表达式和相关的catch子句之间传递异常的具体信息。
throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型,其后跟一个分号构成一条语句。
try语句块的通用语法格式为:
try {
program-statements
} catch (expre-decl) {
handler-state
} ...
catch子句包含3个部分:关键字catch,括号里的异常声明以及一个块。
try语句块中的program是正常的C++语句,其中声明的变量只能在该块中使用,catch中也不能使用。
每个标准库异常类都定义了名为what的成员函数,这些函数没有参数,返回值是const char*。
如果try语句块存在多层调用,例如try语句块中调用了某个函数,而该函数中也有try语句块,当该函数中发生异常时,先在该函数找对应的catch语句,如果没找到才在外层去找。如果都没找到则程序转到名为terminate的标准库函数,一般情况下,执行该函数将导致程序非正常退出。对于没有try语句块定义的异常,其产生后也是转到terminate函数。
异常发生时程序中断了,所以一部分计算已经完成,而一部分没有,某些对象处于无效或未完成的状态,或者资源没有正常释放,那些在异常发生期间正确执行了“清理”工作的程序被称作异常安全的,但是编写异常安全的代码很困难。
C++标准库定义一组类用于报告异常。分别定义在四个头文件中。
- exception定义最通用的异常类exception。
- stdexcept定义几种常见的异常类
- new定义了bad_alloc异常类
- type_info定义了bad_cast异常类型
stdexcept定义的异常类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDG5Xt9v-1665475961233)(C:\Users\q1369\AppData\Roaming\Typora\typora-user-images\image-20221011155934993.png)]
我们只能以默认初始化的方式初始化exception、bad_alloc和bad_cast对象,不能提供初始值。
我们必须为其他异常类型(也就是stdexcept里定义的类型)提供string或C风格字符串的初始化。
所有异常类型只有一个what成员函数。
练习
5.1
空语句是最简单的语句,空语句由一个单独的分号构成。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句,空语句什么也不做。
一种常见的情况是,当循环的全部工作在条件部分就可以完成时,我们通常会用到空语句。使用空语句时最好加上注释,从而令代码的阅读者知道这条语句是有意省略内容的。
5.2
块是指用花括号括起来的语句和声明的序列,也称为复合语句。一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,此时应该使用块。块不需要以分号结束。
例如,循环体必须是一条语句,但是我们通常需要在循环体内做很多事情,此时就应该把多条语句用花括号括起来,从而把语句序列转换成块。
5.3
while (val <= 10)
sum += val, ++val;
可读性降低了。
5.4
(a) 非法的,它的原意是希望在 while 语句的控制结构当中定义一个 string::iterator 类型的变量 iter,然后判断 iter 是否到达了 s 的末尾,只要还没有到达末尾就执行循环体的内容。但是该式把变量的定义和关系判断混合在了一起,如果要使用 iter 与其他值比较,必须首先为 iter 赋初值。可改为:
string::iterator iter = s.begin();
while (iter != s.end()) {
++iter;
/* ... */
}
(b) 非法的,status只能在if的语句中使用,其他语句不能使用。可改为:
bool status;
while (status = find(word)) { /* ... */}
if (!status) { /* ... */ }
5.5
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
vector<string> scores = {"F", "D", "C", "B", "A", "S"};
int grade;
cin >> grade;
string level;
if (grade < 60) {
level = scores[0];
}
else {
level = scores[(grade / 10) - 5];
if (grade != 100) {
if ((grade % 10) < 3)
level += "-";
else if ((grade % 10) > 7)
level += "+";
}
}
cout << level << endl;
return 0;
}
5.6
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
vector<string> scores = {"F", "D", "C", "B", "A", "S"};
int grade;
cin >> grade;
string level;
level = grade < 60 ? scores[0] : scores[(grade / 10) - 5];
level += grade != 100 ? grade % 10 > 7 ? "+" : grade % 10 < 3 ? "-" : "" : "";
cout << level << endl;
return 0;
}
注意条件运算符是右结合律,是从右往左算。
5.7
(a) ival1 = ival2没分号
(b) occurs = l不在if语句中了
(c) 第二个if不能使用ival
(d) 应该用==而不是=
5.8
悬垂 else 是指程序中的 if 分支多于 else 分支时,如何为 else 寻找与之匹配的 if 分支的问题。
C++ 规定,else 与离它最近的尚未匹配的 if 匹配,从而消除了二义性。
5.9
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
char ch;
int vowel_cnt = 0;
while (cin >> ch) {
if (ch == 'a' || ch == 'i' || ch == 'o'
|| ch == 'e' || ch == 'u')
{
++vowel_cnt;
}
}
cout << vowel_cnt << endl;
return 0;
}
5.10
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
char ch;
int a_cnt = 0, e_cnt = 0, i_cnt = 0, o_cnt = 0, u_cnt = 0;
while (cin >> ch) {
switch (ch) {
case 'a':
case 'A':
++a_cnt;
break;
case 'e':
case 'E':
++e_cnt;
break;
case 'i':
case 'I':
++i_cnt;
break;
case 'o':
case 'O':
++o_cnt;
break;
case 'u':
case 'U':
++u_cnt;
break;
default:
; // 空语句
}
}
cout << a_cnt << ' ' << e_cnt << ' ' << i_cnt << ' ' << o_cnt << ' ' << u_cnt << endl;
return 0;
}
5.11
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
char ch;
int a_cnt = 0, e_cnt = 0, i_cnt = 0, o_cnt = 0, u_cnt = 0, blank_cnt = 0, tab_cnt = 0, end_cnt = 0;
while (cin.get(ch)) { // 这里要使用cin.get(),否则会忽略那些符号
switch (ch) {
case 'a':
case 'A':
++a_cnt;
break;
case 'e':
case 'E':
++e_cnt;
break;
case 'i':
case 'I':
++i_cnt;
break;
case 'o':
case 'O':
++o_cnt;
break;
case 'u':
case 'U':
++u_cnt;
break;
case ' ':
++blank_cnt;
break;
case '\t':
++tab_cnt;
break;
case '\n':
++end_cnt;
break;
default:
; // 空语句
}
}
cout << a_cnt << ' ' << e_cnt << ' ' << i_cnt << ' ' << o_cnt << ' ' << u_cnt << ' ' << blank_cnt << tab_cnt << end_cnt <<endl;
return 0;
}
5.12
见这篇文章5.12
5.13
(a) 没有break
(b) ix在case 1中被定义并初始化,不符合语法
(c) case后面必须是整型常量表达式,而不能是一系列逗号表达式
(d) case标签的内容只能是整型常量表达式
5.14
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
string temp, result, pre;
int max_cnt = 1, cur_cnt = 1;
while (cin >> temp) {
if (temp == pre) {
cur_cnt++;
if (cur_cnt > max_cnt) {
max_cnt = cur_cnt;
result = temp;
}
}
else {
cur_cnt = 1;
}
pre = temp;
}
if (max_cnt == 0) {
cout << "没有连续出现的单词" << endl;
}
else {
cout << result << "连续出现了" << max_cnt << "次" << endl;
}
return 0;
}
5.15
(a) ix在for语句头中定义,不能在for循环外使用
(b) 只有两个部分,就算要省略第一个部分也要写分号
(c) 如果起始x不等于sz的话,之后也不会等于了,相当于死循环
5.16
从标准输入流中读取数据
while (cin >> ch) {
//
}
for (; cin >> ch; ) {
//
}
累加求和
int sum = 0;
for (int i = 1; i < 10; ++i) {
sum += i;
}
sum = 0;
int i = 1;
while (i < 10) {
sum +=i;
++i;
}
for循环结构严谨,便于控制逻辑,但是我个人认为while循环更加自由,如果非要选择,while!
5.17
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
vector<int> ivec1 = {1, 2, 3, 4};
vector<int> ivec2 = {1, 2, 3, 4, 5};
decltype(ivec1.size()) i = 0, j = 0;
while (i < ivec1.size() && j < ivec2.size()) {
if (ivec1[i] != ivec2[j]) {
cout << "不是前缀" << endl;
return 0;
}
++i;
++j;
}
cout << "是前缀" << endl;
return 0;
}
5.18
(a) do后面应该使用花括号把语句括起来才能和while组成do while语句
(b) while条件中不能定义变量
(c) while条件中不能使用do中定义的变量,要定义在外面
5.19
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
do {
cout << "请输入两个string对象" << endl;
string temp1, temp2;
if (cin >> temp1 >> temp2) {
if (temp1.size() > temp2.size())
cout << temp2 << endl;
else
cout << temp1 << endl;
}
} while (cin);
return 0;
}
5.20
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
string temp, result, pre;
while (cin >> temp) {
if (temp == pre) {
result = temp;
break;
}
pre = temp;
}
if (result.empty()) {
cout << "输入字符串中没有重复的" << endl;
}
else {
cout << result << endl;
}
return 0;
}
5.21
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
string temp;
while (cin >> temp) {
if (!isupper(temp[0]))
continue;
else
cout << temp << ' ';
}
return 0;
}
5.22
int sz;
do {
sz = get_size();
} while (sz <= 0);
5.23
#include <iostream>
#include <vector>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
int num1, num2;
cin >> num1 >> num2;
cout << num1 / num2 << endl;
return 0;
}
5.24
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
int num1, num2;
cin >> num1 >> num2;
if (num2 == 0)
throw std::runtime_error("除数不能为0");
cout << num1 / num2 << endl;
return 0;
}
输出信息:
terminate called after throwing an instance of 'std::runtime_error'
what(): 除数不能为0
Aborted
5.25
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main() {
int num1, num2;
while (cin >> num1 >> num2) {
try {
if (num2 == 0)
throw std::runtime_error("除数不能为0");
cout << num1 / num2 << endl;
} catch (std::runtime_error err) {
cout << err.what() << endl;
cout << "Try again ? Enter y or n" << endl;
char c;
cin >> c;
if (!cin || c == 'n')
break;
}
}
return 0;
}