# Chapter 5 Statements
# Simple Statements
# 简单语句
- 表达式语句:一个表达式末尾加上分号,就变成了表达式语句。
- 空语句:只有一个单独的分号。
- 复合语句(块):用花括号
{}
包裹起来的语句和声明的序列。一个块就是一个作用域。
# Exercise 5.1
什么是空语句?什么时候会用到空语句?
解:
只含义一个单独的分号的语句是空语句。如: ;
。
如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。
while (cin >> s && s != sought) | |
; |
# Exercise 5.2
什么是块?什么时候会用到块?
解:
用花括号括起来的语句和声明的序列就是块。
{ | |
// ... | |
} |
如果在程序的某个地方,语法上需要一条语句,而逻辑上需要多条语句,此时应该使用块
while (val <= 10) { | |
sum += val; | |
++val; | |
} |
# Exercise 5.3
使用逗号运算符重写 1.4.1 节的 while
循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。
while (val <= 10) | |
sum += val, ++val; |
代码的可读性反而降低了。
# Statement Scope
# Exercise 5.4
说明下列例子的含义,如果存在问题,试着修改它。
(a) while (string::iterator iter != s.end()) { /* . . . */ } | |
(b) while (bool status = find(word)) { /* . . . */ } | |
if (!status) { /* . . . */ } |
解:
- (a) 这个循环试图用迭代器遍历
string
,但是变量的定义应该放在循环的外面,目前每次循环都会重新定义一个变量,明显是错误的。 - (b) 这个循环的
while
和if
是两个独立的语句,if
语句中无法访问status
变量,正确的做法是应该将if
语句包含在while
里面。
# Conditional Statements
# 条件语句
- 悬垂 else(dangling else):用来描述在嵌套的
if else
语句中,如果if
比else
多时如何处理的问题。C++ 使用的方法是else
匹配最近没有配对的if
。
# Exercise 5.5
写一段自己的程序,使用 if else
语句实现把数字转换为字母成绩的要求。
#include <iostream> | |
#include <vector> | |
#include <string> | |
using std::vector; using std::string; using std::cout; using std::endl; using std::cin; | |
int main() | |
{ | |
vector<string> scores = { "F", "D", "C", "B", "A", "A++" }; | |
for (int g; cin >> g;) | |
{ | |
string letter; | |
if (g < 60) | |
{ | |
letter = scores[0]; | |
} | |
else | |
{ | |
letter = scores[(g - 50) / 10]; | |
if (g != 100) | |
letter += g % 10 > 7 ? "+" : g % 10 < 3 ? "-" : ""; | |
} | |
cout << letter << endl; | |
} | |
return 0; | |
} |
# Exercise 5.6
改写上一题的程序,使用条件运算符代替 if else
语句。
#include <iostream> | |
#include <vector> | |
#include <string> | |
using std::vector; using std::string; using std::cout; using std::endl; using std::cin; | |
int main() | |
{ | |
vector<string> scores = { "F", "D", "C", "B", "A", "A++" }; | |
int grade = 0; | |
while (cin >> grade) | |
{ | |
string lettergrade = grade < 60 ? scores[0] : scores[(grade - 50) / 10]; | |
lettergrade += (grade == 100 || grade < 60) ? "" : (grade % 10 > 7) ? "+" : (grade % 10 < 3) ? "-" : ""; | |
cout << lettergrade << endl; | |
} | |
return 0; | |
} |
# Exercise 5.7
改写下列代码段中的错误。
(a) if (ival1 != ival2) | |
ival1 = ival2 | |
else | |
ival1 = ival2 = 0; | |
(b) if (ival < minval) | |
minval = ival; | |
occurs = 1; | |
(c) if (int ival = get_value()) | |
cout << "ival = " << ival << endl; | |
if (!ival) | |
cout << "ival = 0\n"; | |
(d) if (ival = 0) | |
ival = get_value(); |
解:
- (a)
ival1 = ival2
后面少了分号。 - (b) 应该用花括号括起来。
- (c)
if (!ival)
应该改为else
。 - (d)
if (ival = 0)
应该改为if (ival == 0)
。
# Exercise 5.8
什么是 “悬垂 else”?C++ 语言是如何处理 else 子句的?
解:
用来描述在嵌套的 if else
语句中,如果 if
比 else
多时如何处理的问题。C++ 使用的方法是 else
匹配最近没有配对的 if
。
# Exercise 5.9
编写一段程序,使用一系列 if
语句统计从 cin
读入的文本中有多少元音字母。
解:
#include <iostream> | |
using std::cout; using std::endl; using std::cin; | |
int main() | |
{ | |
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; | |
char ch; | |
while (cin >> ch) | |
{ | |
if (ch == 'a') ++aCnt; | |
else if (ch == 'e') ++eCnt; | |
else if (ch == 'i') ++iCnt; | |
else if (ch == 'o') ++oCnt; | |
else if (ch == 'u') ++uCnt; | |
} | |
cout << "Number of vowel a: \t" << aCnt << '\n' | |
<< "Number of vowel e: \t" << eCnt << '\n' | |
<< "Number of vowel i: \t" << iCnt << '\n' | |
<< "Number of vowel o: \t" << oCnt << '\n' | |
<< "Number of vowel u: \t" << uCnt << endl; | |
return 0; | |
} |
# Exercise 5.10
我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到 'a' 和 'A' 都应该递增 aCnt
的值,以此类推。
解:
#include <iostream> | |
using std::cin; using std::cout; using std::endl; | |
int main() | |
{ | |
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; | |
char ch; | |
while (cin >> ch) | |
switch (ch) | |
{ | |
case 'a': | |
case 'A': | |
++aCnt; | |
break; | |
case 'e': | |
case 'E': | |
++eCnt; | |
break; | |
case 'i': | |
case 'I': | |
++iCnt; | |
break; | |
case 'o': | |
case 'O': | |
++oCnt; | |
break; | |
case 'u': | |
case 'U': | |
++uCnt; | |
break; | |
} | |
cout << "Number of vowel a(A): \t" << aCnt << '\n' | |
<< "Number of vowel e(E): \t" << eCnt << '\n' | |
<< "Number of vowel i(I): \t" << iCnt << '\n' | |
<< "Number of vowel o(O): \t" << oCnt << '\n' | |
<< "Number of vowel u(U): \t" << uCnt << endl; | |
return 0; | |
} |
# Exercise 5.11
修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。
解:
#include <iostream> | |
using std::cin; using std::cout; using std::endl; | |
int main() | |
{ | |
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newLineCnt = 0; | |
char ch; | |
while (cin >> std::noskipws >> ch) //noskipws(no skip whitespce) | |
switch (ch) | |
{ | |
case 'a': | |
case 'A': | |
++aCnt; | |
break; | |
case 'e': | |
case 'E': | |
++eCnt; | |
break; | |
case 'i': | |
case 'I': | |
++iCnt; | |
break; | |
case 'o': | |
case 'O': | |
++oCnt; | |
break; | |
case 'u': | |
case 'U': | |
++uCnt; | |
break; | |
case ' ': | |
++spaceCnt; | |
break; | |
case '\t': | |
++tabCnt; | |
break; | |
case '\n': | |
++newLineCnt; | |
break; | |
} | |
cout << "Number of vowel a(A): \t" << aCnt << '\n' | |
<< "Number of vowel e(E): \t" << eCnt << '\n' | |
<< "Number of vowel i(I): \t" << iCnt << '\n' | |
<< "Number of vowel o(O): \t" << oCnt << '\n' | |
<< "Number of vowel u(U): \t" << uCnt << '\n' | |
<< "Number of space: \t" << spaceCnt << '\n' | |
<< "Number of tab char: \t" << tabCnt << '\n' | |
<< "Number of new line: \t" << newLineCnt << endl; | |
return 0; | |
} |
其中,使用 std::noskipws
可以保留默认跳过的空格。
# Exercise 5.12
修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量: ff
、 fl
和 fi
。
解:
#include <iostream> | |
using std::cin; using std::cout; using std::endl; | |
int main() | |
{ | |
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newLineCnt = 0, ffCnt = 0, flCnt = 0, fiCnt = 0; | |
char ch, prech = '\0'; | |
while (cin >> std::noskipws >> ch) | |
{ | |
switch (ch) | |
{ | |
case 'a': | |
case 'A': | |
++aCnt; | |
break; | |
case 'e': | |
case 'E': | |
++eCnt; | |
break; | |
case 'i': | |
if (prech == 'f') ++fiCnt; | |
case 'I': | |
++iCnt; | |
break; | |
case 'o': | |
case 'O': | |
++oCnt; | |
break; | |
case 'u': | |
case 'U': | |
++uCnt; | |
break; | |
case ' ': | |
++spaceCnt; | |
break; | |
case '\t': | |
++tabCnt; | |
break; | |
case '\n': | |
++newLineCnt; | |
break; | |
case 'f': | |
if (prech == 'f') ++ffCnt; | |
break; | |
case 'l': | |
if (prech == 'f') ++flCnt; | |
break; | |
} | |
prech = ch; | |
} | |
cout << "Number of vowel a(A): \t" << aCnt << '\n' | |
<< "Number of vowel e(E): \t" << eCnt << '\n' | |
<< "Number of vowel i(I): \t" << iCnt << '\n' | |
<< "Number of vowel o(O): \t" << oCnt << '\n' | |
<< "Number of vowel u(U): \t" << uCnt << '\n' | |
<< "Number of space: \t" << spaceCnt << '\n' | |
<< "Number of tab char: \t" << tabCnt << '\n' | |
<< "Number of new line: \t" << newLineCnt << '\n' | |
<< "Number of ff: \t" << ffCnt << '\n' | |
<< "Number of fl: \t" << flCnt << '\n' | |
<< "Number of fi: \t" << fiCnt << endl; | |
return 0; | |
} |
# Exercise 5.13
下面显示的每个程序都含有一个常见的编码错误,指出错误在哪里,然后修改它们。
(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0; | |
char ch = next_text(); | |
switch (ch) { | |
case 'a': aCnt++; | |
case 'e': eCnt++; | |
default: iouCnt++; | |
} | |
(b) unsigned index = some_value(); | |
switch (index) { | |
case 1: | |
int ix = get_value(); | |
ivec[ ix ] = index; | |
break; | |
default: | |
ix = ivec.size()-1; | |
ivec[ ix ] = index; | |
} | |
(c) unsigned evenCnt = 0, oddCnt = 0; | |
int digit = get_num() % 10; | |
switch (digit) { | |
case 1, 3, 5, 7, 9: | |
oddcnt++; | |
break; | |
case 2, 4, 6, 8, 10: | |
evencnt++; | |
break; | |
} | |
(d) unsigned ival=512, jval=1024, kval=4096; | |
unsigned bufsize; | |
unsigned swt = get_bufCnt(); | |
switch(swt) { | |
case ival: | |
bufsize = ival * sizeof(int); | |
break; | |
case jval: | |
bufsize = jval * sizeof(int); | |
break; | |
case kval: | |
bufsize = kval * sizeof(int); | |
break; | |
} |
解:
(a) 少了 break
语句。应该为:
unsigned aCnt = 0, eCnt = 0, iouCnt = 0; | |
char ch = next_text(); | |
switch (ch) { | |
case 'a': aCnt++; break; | |
case 'e': eCnt++; break; | |
default: iouCnt++; break; | |
} |
(b) 在 default
分支当中, ix
未定义。应该在外部定义 ix
。
unsigned index = some_value(); | |
int ix; | |
switch (index) { | |
case 1: | |
ix = get_value(); | |
ivec[ ix ] = index; | |
break; | |
default: | |
ix = static_cast<int>(ivec.size())-1; | |
ivec[ ix ] = index; | |
} |
(c) case
后面应该用冒号而不是逗号。
unsigned evenCnt = 0, oddCnt = 0; | |
int digit = get_num() % 10; | |
switch (digit) { | |
case 1: case 3: case 5: case 7: case 9: | |
oddcnt++; | |
break; | |
case 2: case 4: case 6: case 8: case 0: | |
evencnt++; | |
break; | |
} |
(d) case
标签必须是整型常量表达式。
const unsigned ival=512, jval=1024, kval=4096; | |
unsigned bufsize; | |
unsigned swt = get_bufCnt(); | |
switch(swt) { | |
case ival: | |
bufsize = ival * sizeof(int); | |
break; | |
case jval: | |
bufsize = jval * sizeof(int); | |
break; | |
case kval: | |
bufsize = kval * sizeof(int); | |
break; | |
} |
# Iterative Statements
# 迭代语句
- while:当不确定到底要迭代多少次时,使用
while
循环比较合适,比如读取输入的内容。 - for:
for
语句可以省略掉init-statement
,condition
和expression
的任何一个;甚至全部。 - 范围 for:
for (declaration: expression) statement
# Exercise 5.14
编写一段程序,从标准输入中读取若干 string
对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。
例如:如果输入是:
how now now now brown cow cow
那么输出应该表明单词 now 连续出现了 3 次。
解:
#include <iostream> | |
#include <string> | |
using std::cout; using std::cin; using std::endl; using std::string; using std::pair; | |
int main() | |
{ | |
pair<string, int> max_duplicated; | |
int count = 0; | |
for (string str, prestr; cin >> str; prestr = str) | |
{ | |
if (str == prestr) ++count; | |
else count = 0; | |
if (count > max_duplicated.second) max_duplicated = { prestr, count }; | |
} | |
if (max_duplicated.first.empty()) cout << "There's no duplicated string." << endl; | |
else cout << "the word " << max_duplicated.first << " occurred " << max_duplicated.second + 1 << " times. " << endl; | |
return 0; | |
} |
# Exercise 5.15
说明下列循环的含义并改正其中的错误。
(a) for (int ix = 0; ix != sz; ++ix) { /* ... */ } | |
if (ix != sz) | |
// . . . | |
(b) int ix; | |
for (ix != sz; ++ix) { /* ... */ } | |
(c) for (int ix = 0; ix != sz; ++ix, ++sz) { /*...*/ } |
解:
应该改为下面这样:
(a) int ix; | |
for (ix = 0; ix != sz; ++ix) { /* ... */ } | |
if (ix != sz) | |
// . . . | |
(b) int ix; | |
for (; ix != sz; ++ix) { /* ... */ } | |
(c) for (int ix = 0; ix != sz; ++ix) { /*...*/ } |
# Exercise 5.16
while
循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。for
循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。
如果只能使用一种循环,你倾向于哪种?为什么?
解:
int i; | |
while ( cin >> i ) | |
// ... | |
for (int i = 0; cin >> i;) | |
// ... | |
for (int i = 0; i != size; ++i) | |
// ... | |
int i = 0; | |
while (i != size) | |
{ | |
// ... | |
++i; | |
} |
如果只能用一种循环,我会更倾向使用 while
,因为 while
显得简洁,代码可读性强。
# Exercise 5.17
假设有两个包含整数的 vector
对象,编写一段程序,检验其中一个 vector
对象是否是另一个的前缀。
为了实现这一目标,对于两个不等长的 vector
对象,只需挑出长度较短的那个,把它的所有元素和另一个 vector
对象比较即可。
例如,如果两个 vector
对象的元素分别是 0、1、1、2 和 0、1、1、2、3、5、8,则程序的返回结果为真。
解:
#include <iostream> | |
#include <vector> | |
using std::cout; using std::vector; | |
bool is_prefix(vector<int> const& lhs, vector<int> const& rhs) | |
{ | |
if(lhs.size() > rhs.size()) | |
return is_prefix(rhs, lhs); | |
for(unsigned i = 0; i != lhs.size(); ++i) | |
if(lhs[i] != rhs[i]) return false; | |
return true; | |
} | |
int main() | |
{ | |
vector<int> l{ 0, 1, 1, 2 }; | |
vector<int> r{ 0, 1, 1, 2, 3, 5, 8 }; | |
cout << (is_prefix(r, l) ? "yes\n" : "no\n"); | |
return 0; | |
} |
# Exercise 5.18
说明下列循环的含义并改正其中的错误。
(a) do { // 应该添加花括号 | |
int v1, v2; | |
cout << "Please enter two numbers to sum:" ; | |
if (cin >> v1 >> v2) | |
cout << "Sum is: " << v1 + v2 << endl; | |
}while (cin); | |
(b) int ival; | |
do { | |
// . . . | |
} while (ival = get_response()); // 应该将 ival 定义在循环外 | |
(c) int ival = get_response(); | |
do { | |
ival = get_response(); | |
} while (ival); // 应该将 ival 定义在循环外 |
# Exercise 5.19
编写一段程序,使用 do while
循环重复地执行下述任务:
首先提示用户输入两个 string
对象,然后挑出较短的那个并输出它。
解:
#include <iostream> | |
#include <string> | |
using std::cout; using std::cin; using std::endl; using std::string; | |
int main() | |
{ | |
string rsp; | |
do { | |
cout << "Input two strings: "; | |
string str1, str2; | |
cin >> str1 >> str2; | |
cout << (str1 <= str2 ? str1 : str2) | |
<< " is less than the other. " << "\n\n" | |
<< "More? Enter yes or no: "; | |
cin >> rsp; | |
} while (!rsp.empty() && tolower(rsp[0]) == 'y'); | |
return 0; | |
} |
# Jump Statements
# 跳转语句
- break:
break
语句负责终止离它最近的while
、do while
、for
或者switch
语句,并从这些语句之后的第一条语句开始继续执行。 - continue:终止最近的循环中的当前迭代并立即开始下一次迭代。只能在
while
、do while
、for
循环的内部。
# Exercise 5.20
编写一段程序,从标准输入中读取 string
对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。
使用 while
循环一次读取一个单词,当一个单词连续出现两次时使用 break
语句终止循环。
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。
解:
#include <iostream> | |
#include <string> | |
using std::cout; using std::cin; using std::endl; using std::string; | |
int main() | |
{ | |
string read, tmp; | |
while (cin >> read) | |
if (read == tmp) break; else tmp = read; | |
if (cin.eof()) cout << "no word was repeated." << endl; //eof (end of file) 判断输入是否结束,或者文件结束符,等同于 CTRL+Z | |
else cout << read << " occurs twice in succession." << endl; | |
return 0; | |
} |
# Exercise 5.21
修改 5.5.1 节练习题的程序,使其找到的重复单词必须以大写字母开头。
解:
#include <iostream> | |
using std::cin; using std::cout; using std::endl; | |
#include <string> | |
using std::string; | |
int main() | |
{ | |
string curr, prev; | |
bool no_twice = true; | |
while (cin >> curr) | |
{ | |
if (isupper(curr[0]) && prev == curr) | |
{ | |
cout << curr << ": occurs twice in succession." << endl; | |
no_twice = false; | |
break; | |
} | |
prev = curr; | |
} | |
if (no_twice) | |
cout << "no word was repeated." << endl; | |
return 0; | |
} |
# Exercise 5.22
本节的最后一个例子跳回到 begin
,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用 goto
语句。
// 向后跳过一个带初始化的变量定义是合法的 | |
begin: | |
int sz = get_size(); | |
if (sz <= 0) { | |
goto begin; | |
} |
解:
用 for 循环修改的话就是这样
for (int sz = get_size(); sz <=0; sz = get_size()) | |
; |
# try Blocks and Exception Handling
# try 语句块和异常处理
- throw 表达式:异常检测部分使用
throw
表达式来表示它遇到了无法处理的问题。我们说throw
引发raise
了异常。 - try 语句块:以
try
关键词开始,以一个或多个catch
字句结束。try
语句块中的代码抛出的异常通常会被某个catch
捕获并处理。catch
子句也被称为异常处理代码。 - 异常类:用于在
throw
表达式和相关的catch
子句之间传递异常的具体信息。
# Exercise 5.23
编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。
解:
#include <iostream> | |
using std::cin; | |
using std::cout; | |
using std::endl; | |
int main() | |
{ | |
int i, j; | |
cin >> i >> j; | |
cout << i / j << endl; | |
return 0; | |
} |
# Exercise 5.24
修改你的程序,使得当第二个数是 0 时抛出异常。先不要设定 catch
子句,运行程序并真的为除数输入 0,看看会发生什么?
解:
#include <iostream> | |
#include <stdexcept> | |
int main(void) | |
{ | |
int i, j; | |
std::cin >> i >> j; | |
if (j == 0) | |
throw std::runtime_error("divisor is 0"); | |
std::cout << i / j << std::endl; | |
return 0; | |
} |
# Exercise 5.25
修改上一题的程序,使用 try
语句块去捕获异常。 catch
子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try
语句块的内容。
解:
#include <iostream> | |
#include <stdexcept> | |
using std::cin; using std::cout; using std::endl; using std::runtime_error; | |
int main(void) | |
{ | |
for (int i, j; cout << "Input two integers:\n", cin >> i >> j; ) | |
{ | |
try | |
{ | |
if (j == 0) | |
throw runtime_error("divisor is 0"); | |
cout << i / j << endl; | |
} | |
catch (runtime_error err) | |
{ | |
cout << err.what() << "\nTry again? Enter y or n" << endl; | |
char c; | |
cin >> c; | |
if (!cin || c == 'n') | |
break; | |
} | |
} | |
return 0; | |
} |
# Chapter Summary
🍓:)