【C++ Primer】第五章 语句 (练习)
C++ Primer 5th 随堂练习
第五章 表达式
练习 5.1
什么是 空语句 ?什么时候会用到空语句?
解答
空语句是最简单的语句,类似于 Python 的 pass 或 ... 。形式上,空语句仅由一个单独的分号 ; 构成。功能上,空语句什么也不执行。
如果在程序某处,语法上需要一条语句但逻辑上不需要,此时应使用空语句。一种常见的情况是,当循环的全部工作在条件部分就可以完成时,通常会用到空语句。
此外,使用空语句时最好加上注释,从而令读者明白该语句是有意为之的。
练习 5.2
什么是 块 ?什么时候会用到块?
解答
块即复合语句。形式上,块是用花括号括起来的语句和声明的序列,但不需要以分号 ; 结束。功能上,一个块构成一个作用域,在块中引入的名字只能在块内部及嵌套子块中访问。
{
// ...
}
若在程序某处,语法上需要一条语句,但逻辑上需要多条语句,此时应使用块。例如,循环体必须是一条语句,但通常需要在循环体内执行不止一条语句,此时就应该把这些语句用花括号括起来,从而将语句序列转变成块。
while (val <= 10) {
sum += val;
++val;
}
练习 5.3
使用逗号运算符重写 1.4.1 节的 while 循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。
解答
使用连续的逗号运算符的确可以把多条语句合并为一条,这点与块的作用类似。但一般而言,直接使用块在程序可读性上更具优势。如下所示,重写后代码的可读性降低了。
while (val <= 10)
sum += val, ++val;
练习 5.4
说明下列例子的含义,如果存在问题,试着修改它。
(a) while (string::iterator iter != s.end()) { /* . . . */ } (b) while (bool status = find(word)) { /* . . . */ } if (!status) { /* . . . */ }
解答
- (a):该循环试图用迭代器遍历 string,但 while 语句的循环变量应定义于循环外,而上述语句每次循环都会重新定义一个变量 iter,以至于循环始终到达不了终止条件而无限循环。
- (b):该循环的 while 和 if 是两个独立的语句,if 语句中无法访问 status 变量,理应将 bool 遍历 status 定义在循环体外。
练习 5.5
写一段自己的程序,使用 if else 语句实现把数字转换为字母成绩的要求。
解答
源程序:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
for (int grade; cin >> grade; )
{
string letter;
if (grade < 60) {
letter = scores[0];
}
else {
letter = scores[(grade - 50) / 10];
if (grade != 100) {
if (grade % 10 > 7) {
letter += "+";
}
else if (grade % 10 < 3) {
letter += "-";
}
// letter += grade % 10 > 7 ? "+" : grade % 10 < 3 ? "-" : "";
}
}
cout << letter << endl;
}
system("pause");
return 0;
}
控制台交互:
100
A++
95
A
98
A+
91
A-
60
D-
59
F
练习 5.6
改写上一题的程序,使用条件运算符代替 if else 语句。
解答
源程序:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
int grade = 0;
while (cin >> grade) {
string letter = (grade < 60) ? scores[0] : scores[(grade - 50) / 10];
letter += (grade == 100 || grade < 60) ? ""
: (grade % 10 > 7) ? "+"
: (grade % 10 < 3) ? "-" : "";
cout << letter << endl;
}
system("pause");
return 0;
}
控制台交互:
100
A++
99
A+
95
A
91
A-
60
D-
59
F
可见,伴随着简洁性的提升,代码的可读性在下降。
练习 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) if (ival1 != ival2)
ival1 = ival2; // 补上分号
else
ival1 = ival2 = 0;
(b) if (ival < minval) { // 补上花括号, 使两个语句都在同一作用域内
minval = ival;
occurs = 1;
}
(c) int ival; // 两个 if 语句都用到了变量 ival, 故应将其定义在其他处
if (ival = get_value())
cout << "ival = " << ival << endl;
if (!ival)
cout << "ival = 0\n";
(d) if (ival == 0) // 将 = 改为 == 实现判断意图
ival = get_value();
练习 5.8
什么是 “ 悬垂 else ” ?C++语言是如何处理 else 子句的?
解答
悬垂 else 是指当程序中的 if 分支多于 else 分支时,如何为 else 寻找与之匹配的 if 分支的问题。
C++ 规定,else 与离他最近的而尚未匹配的 if 匹配,从而消除二义性。
练习 5.9
编写一段程序,使用一系列 if 语句统计从 cin 读入的文本中有多少元音字母。
解答
源程序:
#include <iostream>
using namespace std;
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;
system("pause");
return 0;
}
练习 5.10
我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到 'a' 和 'A' 都应该递增 aCnt 的值,以此类推。
解答
源程序 - 方式一:
#include <iostream>
using namespace std;
int main()
{
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
if ((ch == 'a') || (ch == 'A')) ++aCnt;
else if ((ch == 'e') || (ch == 'E')) ++eCnt;
else if ((ch == 'i') || (ch == 'I')) ++iCnt;
else if ((ch == 'o') || (ch == 'O')) ++oCnt;
else if ((ch == 'u') || (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;
system("pause");
return 0;
}
源程序 - 方式二:
#include <iostream>
using namespace std;
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;
default: // 总是带上 default 语句是好习惯
break;
}
}
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;
system("pause");
return 0;
}
练习 5.11
修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。
解答
源程序:
#include <iostream>
using namespace std;
int main()
{
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
unsigned spaceCnt = 0, tableCnt = 0, newlineCnt = 0;
char ch;
// 注意, 不能再用 cin >> 了, 得换用 cin.get()
// 也可以使用 std::noskipws 保留默认跳过的空格
while (cin.get(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;
case ' ':
++spaceCnt;
break;
case '\t':
++tableCnt;
break;
case '\n':
++newlineCnt;
break;
default:
break;
}
}
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 << '\n'
<< "Number of space: \t" << spaceCnt << '\n'
<< "Number of space: \t" << tableCnt << '\n'
<< "Number of space: \t" << newlineCnt << endl;
system("pause");
return 0;
}
练习 5.12
修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:ff、fl 和 fi。
解答
为了统计字符序列的情况,必须记录前一个字符的内容,以下使用变量 prech 实现:
#include <iostream>
using namespace std;
int main()
{
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
unsigned spaceCnt = 0, tableCnt = 0, newlineCnt = 0;
unsigned ffCnt = 0, flCnt = 0, fiCnt = 0;
char ch, prech = '\0'; // prech 保存前一个字符
// 注意, 不能再用 cin >> 了, 得换用 cin.get()
// 也可以使用 std::noskipws 保留默认跳过的空格
while (cin.get(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':
++tableCnt;
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: \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 << '\n'
<< "Number of space: \t" << spaceCnt << '\n'
<< "Number of table: \t" << tableCnt << '\n'
<< "Number of newline: \t" << newlineCnt << '\n'
<< "Number of space: \t" << ffCnt << '\n'
<< "Number of space: \t" << flCnt << '\n'
<< "Number of space: \t" << fiCnt << endl;
system("pause");
return 0;
}
练习 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; }
解答
// 补上 break;
(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
char ch = next_text();
switch (ch) {
case 'a':
aCnt++;
break;
case 'e':
eCnt++;
break;
default:
iouCnt++;
break;
}
// 在 case 1 分支中定义并初始化了 ix, 而在 default 分支中却未定义而使用了 ix, 应在 switch 语句外定义 ix
(b) unsigned index = some_value();
int ix;
switch (index) {
case 1:
ix = get_value();
ivec[ ix ] = index;
break;
default:
ix = ivec.size()-1;
ivec[ ix ] = index;
}
// 同一 case 标签中只能对应一个值, 应使用多个 case 共享同一个块
(c) 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 10:
evencnt++;
break;
}
// 不应使用变量作为 case 标签, C++ 规定 case 标签只能是整型常量表达式, 故补上 const
(d) 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;
}
练习 5.14
编写一段程序,从标准输入中读取若干 string 对象并查找连续重复出现的单词。所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。 例如:如果输入是:
how now now now brown cow cow
那么输出应该表明单词 now 连续出现了 3 次。
解答
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 初始化参数
string curr_string, prev_string = "", max_string = "";
int curr_num = 0, max_num = 0;
// 输入和统计
while (cin >> curr_string) {
if (curr_string == prev_string) {
++curr_num; // 连续出现次数 +1
if (curr_num > max_num) {
if (max_string != curr_string)
max_string = curr_string; // 最大连续重复单词不同才更新, 不必每次都更新
max_num = curr_num; // 最大重复次数更新
}
}
else {
prev_string = curr_string; // 遇到新单词才更新前一单词, 不必每次都更新
curr_num = 1; // 出现次数置 1
}
}
// 输出
if (max_num > 1)
cout << max_string << " appears " << max_num << " times continuously" << endl;
else
cout << "each word successfully appears only once" << endl;
system("pause");
return 0;
}
控制台交互:
how now now now brown cow cow
now appears 3 times continuously
练习 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) { /*...*/ }
解答
// 循环变量 ix 仅定义于 for 循环内, 局限于 for 循环的作用域中,
// 而后续的 if 语句却存在未定义的 ix, 编译将无法通过, 修改如下:
(a) int ix;
for (ix = 0; ix != sz; ++ix) { /* ... */ }
if (ix != sz)
// . . .
// 变量 ix 未经初始化便使用, 且 for 循环中缺少变量初始化的语句 ———— 至少需要空语句,
// 修改如下:
(b) int ix;
for (ix = 0; ix != sz; ++ix) { /* ... */ }
// for 循环的控制语句中, ix 和 sz 同步增长, 一旦进入 for 循环时 ix != sz, 就会陷入死循环,
// 修改如下:
(c) for (int ix = 0; ix != sz; ++ix) { /*...*/ }
练习 5.16
while 循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。for 循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。 如果只能使用一种循环,你倾向于哪种?为什么?
解答
练习 5.17
假设有两个包含整数的 vector 对象,编写一段程序,检验其中一个 vector 对象是否是另一个的前缀。 为了实现这一目标,对于两个不等长的 vector 对象,只需挑出长度较短的那个,把它的所有元素和另一个 vector 对象比较即可。 例如,如果两个 vector 对象的元素分别是 0、1、1、2 和 0、1、1、2、3、5、8 则程序的返回结果为真。
解答
源程序 - 方式一 (索引/下标):
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> seq1 = {0, 1, 1, 2};
vector<int> seq2 = {0, 1, 1, 2, 3, 5, 8};
auto len = seq1.size() > seq2.size() ? seq2.size() : seq1.size(); // 最短长度
for (auto i = 0; i < len; ++i) {
if (seq1[i] != seq2[i]) {
cout << "no" << endl;
system("pause");
return -1;
}
}
cout << "yes" << endl;
system("pause");
return 0;
}
源程序 - 方式二 (迭代器):
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> seq1 = {0, 1, 1, 2};
vector<int> seq2 = {0, 1, 1, 2, 3, 5, 8};
auto beg1 = seq1.cbegin();
auto end1 = seq1.cend();
auto beg2 = seq2.cbegin();
auto end2 = seq2.cend();
while (beg1 != end1 && beg2 != end2) {
if (*beg1 != *beg2) {
cout << "no" << endl;
system("pause");
return -1;
}
else {
++beg1;
++beg2;
}
}
cout << "yes" << endl;
system("pause");
return 0;
}
练习 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) do { // . . . } while (ival = get_response()); (c) do { ival = get_response(); } while (ival);
解答
(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; // 应将 ival 定义在循环外
do {
// . . .
} while (ival = get_response());
(c) int ival = get_response(); // 应将ival 定义在循环外
do {
ival = get_response();
} while (ival);
练习 5.19
编写一段程序,使用 do while 循环重复地执行下述任务:首先提示用户输入两个 string 对象,然后挑出较短的那个并输出它。
解答
#include <iostream>
#include <string>
using namespace std;
int main()
{
do {
string s1, s2;
cout << "input two strings: " << endl;
cin >> s1 >> s2;
if (s1.size() == s2.size())
cout << "the lengths of two strings are equal" << endl;
else
cout << "the shorter string is: " << (s1.size() > s2.size() ? s2 : s1) << endl;
} while (cin);
system("pause");
return 0;
}
控制台交互:
input two strings:
man woman
the shorter string is: man
input two strings:
girl boy
the shorter string is: boy
input two strings:
father mother
the lengths of two strings are equal
input two strings:
练习 5.20
编写一段程序,从标准输入中读取 string 对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。使用 while 循环一次读取一个单词,当一个单词连续出现两次时使用 break 语句终止循环。输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。
解答
源程序 - 方式一 (flag 标记):
#include <iostream>
#include <string>
using namespace std;
int main()
{
string curr_string, prev_string = ""; // 当前输入单词, 前一个输入单词
bool flag = false; // 找到重复出现单词标记
while (cin >> curr_string) {
if (curr_string == prev_string) {
flag = true;
break;
}
prev_string = curr_string; // 前一个单词更新
}
if (flag)
cout << "sequential words are: " << curr_string << endl;
else
cout << "no words appear successfully" << endl;
system("pause");
return 0;
}
源程序 - 方式二 (交互更友好):
#include <iostream>
#include <string>
using namespace std;
int main()
{
string curr, prev;
while (cin >> curr) {
if (curr == prev)
break;
prev = curr;
}
// eof(end of file) 判断输入是否结束, 或者文件结束符, 等同于 CTRL+Z
if (cin.eof())
cout << "no word was repeated." << endl;
else
cout << curr << " occurs twice in succession." << endl;
system("pause");
return 0;
}
练习 5.21
修改 5.5.1 节练习题的程序,使其找到的重复单词必须以大写字母开头。
解答
#include <iostream>
#include <string>
using namespace std;
int main()
{
string curr_string, prev_string = ""; // 当前输入单词, 前一个输入单词
bool flag = false; // 找到重复出现单词标记
cout << "input some strings: ";
while (cin >> curr_string) {
if (!isupper(curr_string[0])) // 非大写字母开头单词不予考虑, 写在此处更高效
continue;
if (curr_string == prev_string) {
flag = true;
break;
}
prev_string = curr_string; // 前一个单词更新
}
if (flag)
cout << "sequential words are: " << curr_string << endl;
else
cout << "no words appear successfully" << endl;
system("pause");
return 0;
}
练习 5.22
本节的最后一个例子跳回到 begin,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用 goto 语句。
// 向后跳过一个带初始化的变量定义是合法的 begin: int sz = get_size(); if (sz <= 0) { goto begin; }
解答
// 方式一
int sz = get_size();
while (sz <= 0) {
sz = get_size();
}
// 方式二
int sz;
do {
sz = get_size();
} while (sz <= 0);
// 方式三
for (int sz = get_size(); sz <=0; sz = get_size())
;
练习 5.23
编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。
解答
练习 5.24
修改你的程序,使得当第二个数是 0 时抛出异常。先不要设定 catch 子句,运行程序并真的为除数输入 0,看看会发生什么?
解答
练习 5.25
修修改上一题的程序,使用 try 语句块去捕获异常。catch 子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try 语句块的内容。
解答