专栏C++学习笔记
《C++ Primer》学习笔记/习题答案 总目录
——————————————————————————————————————————————————————
📚💻 Cpp-Prime5 + Cpp-Primer-Plus6 源代码和课后题
第5章 - 循环、分支、跳转和异常处理语句
练习5.1
什么是空语句?什么时候会用到空语句?
解:
空语句是最简单的语句,空语句由一个单独的分号构成。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句,空语句什么也不做。
一种常见的情况是,当循环的全部工作在条件部分就可以完成时,我们通常会用到空语句。使用空语句时最好加上注释,从而令代码的阅读者知道这条语句是有意省略内容的。
while (cin >> s && s != sought)
;
练习5.2
什么是块?什么时候会用到块?
解:
块是指用花括号括起来的语句和声明的序列,也称为复合语句。一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,此时应该使用块。块不需要以分号结束。
例如,循环体必须是一条语句,但是我们通常需要在循环体内做很多事情,此时就应该把多条语句用花括号括起来,从而把语句序列转变成块。
while (val <= 10) {
sum += val;
++val;
}
练习5.3
使用逗号运算符重写1.4.1节的 while
循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。
解:
原文的while循环使用了块,其形式是:
while (val <= 10)
{
sum += val;
++val;
}
利用逗号运算符改写之后的形式如下所示:
while (val <= 10)
sum += val, ++val;
很明显,改写之后的代码不够清晰,可读性降低了。
练习5.4
说明下列例子的含义,如果存在问题,试着修改它。
(a) while (string::iterator iter != s.end()) {/*...*/}
(b) while (bool status = find(word)) {/*...*/}
if (!status) {/*...*/}
解:
(a)是非法的,它的原意是希望在 while
语句的控制结构当中定义一个 string::iterator
类型的变量 iter
,然后判断 iter
是否到达了 s
的末尾,只要还没有到达末尾就执行循环体的内容。但是该式把变量的定义和关系判断混合在了一起,如果要使用 iter
与其他值比较,必须首先为 iter
赋初值。
修改后的程序应该是:
string::iterator iter=s.begin();
while (iter!=s.end())
{
++iter;
/*...*/
}
PS:while
循环里能定义变量,只不过每次循环之后就失效了,也就是每循环一次就会重新定义一个相同名称的变量。
(b)是非法的,变量 status
定义在 while
循环控制结构的内部,其作用域仅限于 while
循环。if
语句已经位于 while
循环的作用域之外,status
在 if
语句内是一个未命名的无效变量。要想在 if
语句中继续使用 status
,需要把它定义在 while
循环之前。
修改后的程序应该是:
bool status;
while (status=find(word)) {/*...*/}
if (!status) {/*...*/}
练习5.5
写一段自己的程序,使用 if else
语句实现把数字转换为字母成绩的要求。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
cout << "请输入您的成绩:" << endl;
for (int g; cin >> g;)
{
string letter;
if (g < 0 || g > 100)
{
cout << "该成绩不合法!!!" << endl;
continue;
}
else if (g < 60)
{
letter = scores[0];
}
else
{
letter = scores[(g - 50) / 10];
if (g != 100){
if (g % 10 > 7){
letter += "+";
}
else if (g % 10 < 3)
{
letter += "-";
}
else
{
letter += "";
}
}
}
cout << "等级成绩是:" << letter << endl;
}
system("pause");
return 0;
}
练习5.6
改写上一题的程序,使用条件运算符代替 if else
语句。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
cout << "请输入您的成绩:" << endl;
for (int g; cin >> g;)
{
string letter;
if (g < 0 || g > 100)
{
cout << "该成绩不合法!!!" << endl;
continue;
}
letter = (g < 60) ? scores[0] : scores[(g - 50) / 10];
letter += (g == 100 || g < 60) ? "" : g % 10 > 7 ? "+" : g % 10 < 3 ? "-" : "";
cout << "等级成绩是:" << letter << endl;
}
system("pause");
return 0;
}
练习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
语句的循环体应该是一条语句,需要以分号结束。程序修改为:
if (ival1 != ival2)
ival1 = ival2;
else
ival1 = ival2 = 0;
(b)if
语句的循环体只能是一条语句,或者应该用花括号括起来。程序修改为:
if (ival < minval)
{
minval = ival;
occurs = 1;
}
©ival
是定义在 if
语句中的变量,其作用域仅限于第一个 if
语句,要想在第二个 if
语句中也使用它,就必须把它定义在两个 if
语句的外部。程序修改为:
int ival;
if (ival = get_value())
cout << "ival = " << ival << endl;
if (!ival)
cout << "ival = 0\n";
(d)程序的原意是判断 ival
的值是否是0,原题使用赋值运算符的结果是把0赋给了 ival
,然后检验 ival
的值,这样使得条件永远不会满足。程序修改为:
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 vowelCnt = 0;
char ch;
cout << "请输入一段文本:" << endl;
while (cin >> ch)
{
if (ch == 'a')
++vowelCnt;
else if (ch == 'e')
++vowelCnt;
else if (ch == 'i')
++vowelCnt;
else if (ch == 'o')
++vowelCnt;
else if (ch == 'u')
++vowelCnt;
}
cout << "您输入的文本中共有 " << vowelCnt << " 个元音字母" << endl;
system("pause");
return 0;
}
练习5.10
我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到’a’和’A’都应该递增aCnt
的值,以此类推。
解:
关键是在适当位置添加 break;
语句。其中表示同一个字母的大小写的 case
标签之间不写 break;
。
#include <iostream>
using namespace std;
int main()
{
unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
cout << "请输入一段文本:" << endl;
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;
system("pause");
return 0;
}
练习5.11
修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。
解:
#include <iostream>
using namespace std;
int main()
{
unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
unsigned int spaceCnt = 0, tabCnt = 0, newLineCnt = 0;
char ch;
cout << "请输入一段文本:" << endl;
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':
++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;
system("pause");
return 0;
}
练习5.12
修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:ff
、fl
和 fi
。
解:
设定是一个字符只会被统计一次,即两个 f
的第二个 f
只能被占用一次。
#include <iostream>
using namespace std;
int main()
{
unsigned int ffCnt = 0, flCnt = 0, fiCnt = 0;
char ch, prech = '\0';
cout << "请输入一段文本:" << endl;
while (cin >> ch)
{
bool bl = true;
if (prech == 'f')
{
switch (ch)
{
case 'f':
++ffCnt;
bl = false;
break;
case 'l':
++flCnt;
break;
case 'i':
++fiCnt;
break;
}
}
if (!bl)
prech = '\0';
else
prech = ch;
}
cout << "Number of ff: \t" << ffCnt << '\n'
<< "Number of fl: \t" << flCnt << '\n'
<< "Number of fi: \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;
}
解:
(a)的错误是在每个 case
分支中都缺少了 break;
语句,造成的后果是一旦执行了前面的 case
分支,必定还会继续执行接下来的其他 case
分支,这显然与程序的预期是不相符的。
修改后的程序如下所示:
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)的错误是在 case
分支中定义并初始化了变量 ix
,同时在 default
分支中使用了该变量,此时如果控制流跳过 case
分支而直接到达 default
分支,则会试图使用未经初始化的变量,因而该程序无法通过编译。
修改后的程序如下所示:
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;
}
©的错误是在同一个 case
标签中放置了多个值,而 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
标签的内容,C++规定,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;
}
练习5.14
编写一段程序,从标准输入中读取若干 string
对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。
例如:如果输入是:
how now now now brown cow cow
那么输出应该表明单词 now
连续出现了3次。
解:
只输入第一个出现次数最多的字符串。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string currString, preString = "", maxString;
int currCnt = 1, maxCnt = 0;
cout << "请输入字符串:" << endl;
while (cin >> currString)
{
if (currString == preString)
{
++currCnt;
if (currCnt > maxCnt)
{
maxCnt = currCnt;
maxString = currString;
}
}
else
{
currCnt = 1;
}
preString = currString;
}
if (maxCnt > 1)
cout << "出现最多的字符串是:" << maxString << ",次数是:" << maxCnt << endl;
else
cout << "每个字符串都只出现了一次" << endl;
system("pause");
return 0;
}
练习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)的错误是在 for
语句中定义了变量 ix
,然后试图在 for
语句之外继续使用 ix
。因为 ix
定义在 for
语句的内部,所以其作用域仅限于 for
语句。在 if
语句中 ix
已经失效,因此程序无法编译通过。
修改后的程序如下:
(a) int ix;
for (ix = 0; ix != sz; ++ix) { /* ... */ }
if (ix != sz)
// . . .
(b)的错误有两个,一是变量 ix
未经初始化就直接使用,二是 for
语句的控制结构缺少一句话,在语法上是错误的。
修改后的程序如下:
(b) int ix;
for (; ix != sz; ++ix) { /* ... */ }
©的错误是一旦进入循环,程序就会无休止地执行下去。也就是说,当初始情况下 ix != sz
时,由题意可知 ix
和 sz
一直同步增长,循环的终止条件永远不会满足,所以该循环是一个死循环。
修改后的程序如下:
(c) for (int ix = 0; ix != sz; ++ix) { /* ... */ }
练习5.16
while
循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。
for
循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。
如果只能使用一种循环,你倾向于哪种?为什么?
解:
从标准输入流读取数据的程序一般使用 while
循环,其形式是:
char ch;
while (cin >> ch)
{
/* ... */
}
因为这项功能事实上不太要求我们严格跟踪循环变量的变化过程,所以改写成 for
循环后稍显冗余。
for (;cin >> ch;)
{
/* ... */
}
请注意:在上面的 for
循环控制结构中,第一条语句和第三条语句都是空语句,额外添加一个循环控制变量(比如 int i
)是没有意义的。
整数累加求和的程序一般使用 for
循环,其形式是:
int iCount = 0;
for (int i = 0;i < 10; ++i)
{
iCount += i;
}
使用 while
循环改写后的形式是:
int iCount = 0, i = 0;
while (i < 10)
{
iCount += i;
++i;
}
验证程序:
#include <iostream>
using namespace std;
int main()
{
int iCount = 0;
for (int i = 0; i < 10; ++i)
{
iCount += i;
}
//int iCount = 0, i = 0;
//while (i < 10)
//{
// iCount += i;
// ++i;
//}
cout << iCount << endl;
system("pause");
return 0;
}
如果只能用一种循环,我会更倾向使用 while
,因为 while
显得简洁,代码可读性强。
当然也可以说 for
,因为 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> v1 = { 0, 1, 1, 2 };
vector<int> v2 = { 0, 1, 1, 2, 3, 5, 8 };
auto it1 = v1.cbegin();
auto it2 = v2.cbegin();
while (it1 != v1.cend() && it2 != v2.cend()){
if (*it1 != *it2){
cout << "v1和v2之间不存在前缀关系" << endl;
break;
}
++it1;
++it2;
}
if (it1 == v1.cend())
cout << "v1是v2的前缀" << endl;
if (it2 == v2.cend())
cout << "v2是v1的前缀" << 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 (int ival = get_response());
(c) do {
int ival = get_response();
} while (ival);
解:
(a)的含义是每次循环读入两个整数并输出他们的和。因为 do-while
语句的循环体必须是一条语句或者一个语句块,所以在本题中应该把循环体的内容用花括号括起来。
修改后的程序是:
do {
int v1, v2;
cout << "Please enter two numbers to sum:" ;
if (cin >> v1 >> v2)
cout << "Sum is: " << v1 + v2 << endl;
} while (cin);
(b)的含义是当 get_response
的返回值不为0时执行循环体。因为 do-while
语句不允许在循环条件内定义变量,所以该程序是错误的。
修改后的程序是:
int ival;
do {
// . . .
} while (ival = get_response());
©的含义是当 get_response
的返回值不为0时执行循环体。因为出现在 do-while
语句条件部分的变量必须定义在循环体之外,所以该程序是错误的。
修改后的程序是:
int ival = get_response();
do {
ival = get_response();
} while (ival);
练习5.19
编写一段程序,使用 do while
循环重复地执行下述任务:
首先提示用户输入两个 string
对象,然后挑出较短的那个并输出它。
解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
do
{
cout << "请输入两个字符串:" << endl;
string str1, str2;
cin >> str1 >> str2;
if (str1.size() < str2.size())
cout << "长度较小的字符串是:" << str1 << endl;
else if (str1.size() > str2.size())
cout << "长度较小的字符串是:" << str2 << endl;
else
cout << "两个字符串等长!" << endl;
} while (cin);
system("pause");
return 0;
}
练习5.20
编写一段程序,从标准输入中读取 string
对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。
使用 while
循环一次读取一个单词,当一个单词连续出现两次时使用 break
语句终止循环。
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。
解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string currString, preString;
bool bl = true;
cout << "请输入一组字符串:" << endl;
while (cin >> currString)
{
if (currString == preString)
{
bl = false;
cout << "连续出现的字符串是:" << currString << endl;
break;
}
preString = currString;
}
if (bl)
cout << "没有连续出现的字符串!" << endl;
system("pause");
return 0;
}
练习5.21
修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。
解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string currString, preString;
bool bl = true;
cout << "请输入一组字符串:" << endl;
while (cin >> currString)
{
if (!isupper(currString[0]))
continue;
if (currString == preString)
{
bl = false;
cout << "连续出现的字符串是:" << currString << endl;
break;
}
preString = currString;
}
if (bl)
cout << "没有连续出现的字符串" << endl;
system("pause");
return 0;
}
练习5.22
本节的最后一个例子跳回到 begin
,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用 goto
语句。
// 向后跳过一个带初始化的变量定义是合法的
begin:
int sz = get_size();
if (sz <= 0) {
goto begin;
}
解:
用 for
循环语句改写后的程序如下所示:
int sz;
for (sz = get_size(); sz <= 0; sz = get_size())
;
用 do-while
语句改写后的程序如下所示:
int sz;
do {
sz = get_size();
} while (sz <= 0);
用 while
语句改写后的程序如下所示:
int sz;
while (sz <= 0)
{
sz = get_size();
}
练习5.23
编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。
解:
#include <iostream>
using namespace std;
int main()
{
cout << "请依次输入被除数和除数:" << endl;
int ival1, ival2;
cin >> ival1 >> ival2;
if (ival2 == 0)
{
cout << "除数不能为0" << endl;
return -1;
}
cout << "两数相除的结果是:" << ival1 / ival2 << endl;
system("pause");
return 0;
}
练习5.24
修改你的程序,使得当第二个数是0时抛出异常。先不要设定 catch
子句,运行程序并真的为除数输入0,看看会发生什么?
解:
#include <iostream>
using namespace std;
int main()
{
cout << "请依次输入被除数和除数:" << endl;
int ival1, ival2;
cin >> ival1 >> ival2;
if (ival2 == 0)
{
throw runtime_error("除数不能为0");
}
cout << "两数相除的结果是:" << ival1 / ival2 << endl;
system("pause");
return 0;
}
练习5.25
修改上一题的程序,使用 try
语句块去捕获异常。catch
子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try
语句块的内容。
解:
#include <iostream>
#include <stdexcept>
using namespace std;
int main()
{
cout << "请依次输入被除数和除数:" << endl;
int ival1, ival2;
while (cin >> ival1 >> ival2)
{
try{
if (ival2 == 0)
{
throw runtime_error("除数不能为0");
}
cout << "两数相除的结果是:" << ival1 / ival2 << endl;
} catch (runtime_error err)
{
cout << err.what() << endl;
cout << "需要继续嘛(y or n)?";
char ch;
cin >> ch;
if (ch != 'y' && ch != 'Y')
break;
}
}
system("pause");
return 0;
}
如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~
回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。
想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~