第五章 语句

练习5.1

什么是空语句?什么时候会用到空语句?

解:

只含义一个单独的分号的语句是空语句。如:;

如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。

  1. while (cin >> s && s != sought)
  2. ;

练习5.2

什么是块?什么时候会用到块?

解:

用花括号括起来的语句和声明的序列就是块。

  1. {
  2. // ...
  3. }

如果在程序的某个地方,语法上需要一条语句,而逻辑上需要多条语句,此时应该使用块

  1. while (val <= 10) {
  2. sum += val;
  3. ++val;
  4. }

练习5.3

使用逗号运算符重写1.4.1节的while循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。

  1. while (val <= 10)
  2. sum += val, ++val;

代码的可读性反而降低了。

练习5.4

说明下列例子的含义,如果存在问题,试着修改它。

  1. (a) while (string::iterator iter != s.end()) { /* . . . */ }
  2. (b) while (bool status = find(word)) { /* . . . */ }
  3. if (!status) { /* . . . */ }

解:

  • (a) 这个循环试图用迭代器遍历string,但是变量的定义应该放在循环的外面,目前每次循环都会重新定义一个变量,明显是错误的。
  • (b) 这个循环的whileif是两个独立的语句,if语句中无法访问status变量,正确的做法是应该将if语句包含在while里面。

练习5.5

写一段自己的程序,使用if else语句实现把数字转换为字母成绩的要求。

  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. using std::vector; using std::string; using std::cout; using std::endl; using std::cin;
  5. int main()
  6. {
  7. vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
  8. for (int g; cin >> g;)
  9. {
  10. string letter;
  11. if (g < 60)
  12. {
  13. letter = scores[0];
  14. }
  15. else
  16. {
  17. letter = scores[(g - 50) / 10];
  18. if (g != 100)
  19. letter += g % 10 > 7 ? "+" : g % 10 < 3 ? "-" : "";
  20. }
  21. cout << letter << endl;
  22. }
  23. return 0;
  24. }

练习5.6

改写上一题的程序,使用条件运算符代替if else语句。

  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. using std::vector; using std::string; using std::cout; using std::endl; using std::cin;
  5. int main()
  6. {
  7. vector<string> scores = { "F", "D", "C", "B", "A", "A++" };
  8. int grade = 0;
  9. while (cin >> grade)
  10. {
  11. string lettergrade = grade < 60 ? scores[0] : scores[(grade - 50) / 10];
  12. lettergrade += (grade == 100 || grade < 60) ? "" : (grade % 10 > 7) ? "+" : (grade % 10 < 3) ? "-" : "";
  13. cout << lettergrade << endl;
  14. }
  15. return 0;
  16. }

练习5.7

改写下列代码段中的错误。

  1. (a) if (ival1 != ival2)
  2. ival1 = ival2
  3. else
  4. ival1 = ival2 = 0;
  5. (b) if (ival < minval)
  6. minval = ival;
  7. occurs = 1;
  8. (c) if (int ival = get_value())
  9. cout << "ival = " << ival << endl;
  10. if (!ival)
  11. cout << "ival = 0\n";
  12. (d) if (ival = 0)
  13. ival = get_value();

解:

  • (a) ival1 = ival2 后面少了分号。
  • (b) 应该用花括号括起来。
  • (c) if (!ival) 应该改为 else
  • (d) if (ival = 0) 应该改为 if (ival == 0)

练习5.8

什么是“悬垂else”?C++语言是如何处理else子句的?

解:

用来描述在嵌套的if else语句中,如果ifelse多时如何处理的问题。C++使用的方法是else匹配最近没有配对的if

练习5.9

编写一段程序,使用一系列if语句统计从cin读入的文本中有多少元音字母。

解:

  1. #include <iostream>
  2. using std::cout; using std::endl; using std::cin;
  3. int main()
  4. {
  5. unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
  6. char ch;
  7. while (cin >> ch)
  8. {
  9. if (ch == 'a') ++aCnt;
  10. else if (ch == 'e') ++eCnt;
  11. else if (ch == 'i') ++iCnt;
  12. else if (ch == 'o') ++oCnt;
  13. else if (ch == 'u') ++uCnt;
  14. }
  15. cout << "Number of vowel a: \t" << aCnt << '\n'
  16. << "Number of vowel e: \t" << eCnt << '\n'
  17. << "Number of vowel i: \t" << iCnt << '\n'
  18. << "Number of vowel o: \t" << oCnt << '\n'
  19. << "Number of vowel u: \t" << uCnt << endl;
  20. return 0;
  21. }

练习5.10

我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到’a’和’A’都应该递增aCnt的值,以此类推。

解:

  1. #include <iostream>
  2. using std::cin; using std::cout; using std::endl;
  3. int main()
  4. {
  5. unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
  6. char ch;
  7. while (cin >> ch)
  8. switch (ch)
  9. {
  10. case 'a':
  11. case 'A':
  12. ++aCnt;
  13. break;
  14. case 'e':
  15. case 'E':
  16. ++eCnt;
  17. break;
  18. case 'i':
  19. case 'I':
  20. ++iCnt;
  21. break;
  22. case 'o':
  23. case 'O':
  24. ++oCnt;
  25. break;
  26. case 'u':
  27. case 'U':
  28. ++uCnt;
  29. break;
  30. }
  31. cout << "Number of vowel a(A): \t" << aCnt << '\n'
  32. << "Number of vowel e(E): \t" << eCnt << '\n'
  33. << "Number of vowel i(I): \t" << iCnt << '\n'
  34. << "Number of vowel o(O): \t" << oCnt << '\n'
  35. << "Number of vowel u(U): \t" << uCnt << endl;
  36. return 0;
  37. }

练习5.11

修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。

解:

  1. #include <iostream>
  2. using std::cin; using std::cout; using std::endl;
  3. int main()
  4. {
  5. unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newLineCnt = 0;
  6. char ch;
  7. while (cin >> std::noskipws >> ch) //noskipws(no skip whitespce)
  8. switch (ch)
  9. {
  10. case 'a':
  11. case 'A':
  12. ++aCnt;
  13. break;
  14. case 'e':
  15. case 'E':
  16. ++eCnt;
  17. break;
  18. case 'i':
  19. case 'I':
  20. ++iCnt;
  21. break;
  22. case 'o':
  23. case 'O':
  24. ++oCnt;
  25. break;
  26. case 'u':
  27. case 'U':
  28. ++uCnt;
  29. break;
  30. case ' ':
  31. ++spaceCnt;
  32. break;
  33. case '\t':
  34. ++tabCnt;
  35. break;
  36. case '\n':
  37. ++newLineCnt;
  38. break;
  39. }
  40. cout << "Number of vowel a(A): \t" << aCnt << '\n'
  41. << "Number of vowel e(E): \t" << eCnt << '\n'
  42. << "Number of vowel i(I): \t" << iCnt << '\n'
  43. << "Number of vowel o(O): \t" << oCnt << '\n'
  44. << "Number of vowel u(U): \t" << uCnt << '\n'
  45. << "Number of space: \t" << spaceCnt << '\n'
  46. << "Number of tab char: \t" << tabCnt << '\n'
  47. << "Number of new line: \t" << newLineCnt << endl;
  48. return 0;
  49. }

其中,使用 std::noskipws可以保留默认跳过的空格。

练习5.12

修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:ffflfi

解:

  1. #include <iostream>
  2. using std::cin; using std::cout; using std::endl;
  3. int main()
  4. {
  5. unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newLineCnt = 0, ffCnt = 0, flCnt = 0, fiCnt = 0;
  6. char ch, prech = '\0';
  7. while (cin >> std::noskipws >> ch)
  8. {
  9. switch (ch)
  10. {
  11. case 'a':
  12. case 'A':
  13. ++aCnt;
  14. break;
  15. case 'e':
  16. case 'E':
  17. ++eCnt;
  18. break;
  19. case 'i':
  20. if (prech == 'f') ++fiCnt;
  21. case 'I':
  22. ++iCnt;
  23. break;
  24. case 'o':
  25. case 'O':
  26. ++oCnt;
  27. break;
  28. case 'u':
  29. case 'U':
  30. ++uCnt;
  31. break;
  32. case ' ':
  33. ++spaceCnt;
  34. break;
  35. case '\t':
  36. ++tabCnt;
  37. break;
  38. case '\n':
  39. ++newLineCnt;
  40. break;
  41. case 'f':
  42. if (prech == 'f') ++ffCnt;
  43. break;
  44. case 'l':
  45. if (prech == 'f') ++flCnt;
  46. break;
  47. }
  48. prech = ch;
  49. }
  50. cout << "Number of vowel a(A): \t" << aCnt << '\n'
  51. << "Number of vowel e(E): \t" << eCnt << '\n'
  52. << "Number of vowel i(I): \t" << iCnt << '\n'
  53. << "Number of vowel o(O): \t" << oCnt << '\n'
  54. << "Number of vowel u(U): \t" << uCnt << '\n'
  55. << "Number of space: \t" << spaceCnt << '\n'
  56. << "Number of tab char: \t" << tabCnt << '\n'
  57. << "Number of new line: \t" << newLineCnt << '\n'
  58. << "Number of ff: \t" << ffCnt << '\n'
  59. << "Number of fl: \t" << flCnt << '\n'
  60. << "Number of fi: \t" << fiCnt << endl;
  61. return 0;
  62. }

练习5.13

下面显示的每个程序都含有一个常见的编码错误,指出错误在哪里,然后修改它们。

  1. (a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
  2. char ch = next_text();
  3. switch (ch) {
  4. case 'a': aCnt++;
  5. case 'e': eCnt++;
  6. default: iouCnt++;
  7. }
  8. (b) unsigned index = some_value();
  9. switch (index) {
  10. case 1:
  11. int ix = get_value();
  12. ivec[ ix ] = index;
  13. break;
  14. default:
  15. ix = ivec.size()-1;
  16. ivec[ ix ] = index;
  17. }
  18. (c) unsigned evenCnt = 0, oddCnt = 0;
  19. int digit = get_num() % 10;
  20. switch (digit) {
  21. case 1, 3, 5, 7, 9:
  22. oddcnt++;
  23. break;
  24. case 2, 4, 6, 8, 10:
  25. evencnt++;
  26. break;
  27. }
  28. (d) unsigned ival=512, jval=1024, kval=4096;
  29. unsigned bufsize;
  30. unsigned swt = get_bufCnt();
  31. switch(swt) {
  32. case ival:
  33. bufsize = ival * sizeof(int);
  34. break;
  35. case jval:
  36. bufsize = jval * sizeof(int);
  37. break;
  38. case kval:
  39. bufsize = kval * sizeof(int);
  40. break;
  41. }

解:

(a) 少了break语句。应该为:

  1. unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
  2. char ch = next_text();
  3. switch (ch) {
  4. case 'a': aCnt++; break;
  5. case 'e': eCnt++; break;
  6. default: iouCnt++; break;
  7. }

(b) 在default分支当中,ix未定义。应该在外部定义ix

  1. unsigned index = some_value();
  2. int ix;
  3. switch (index) {
  4. case 1:
  5. ix = get_value();
  6. ivec[ ix ] = index;
  7. break;
  8. default:
  9. ix = static_cast<int>(ivec.size())-1;
  10. ivec[ ix ] = index;
  11. }

(c) case后面应该用冒号而不是逗号。

  1. unsigned evenCnt = 0, oddCnt = 0;
  2. int digit = get_num() % 10;
  3. switch (digit) {
  4. case 1: case 3: case 5: case 7: case 9:
  5. oddcnt++;
  6. break;
  7. case 2: case 4: case 6: case 8: case 0:
  8. evencnt++;
  9. break;
  10. }

(d) case标签必须是整型常量表达式。

  1. const unsigned ival=512, jval=1024, kval=4096;
  2. unsigned bufsize;
  3. unsigned swt = get_bufCnt();
  4. switch(swt) {
  5. case ival:
  6. bufsize = ival * sizeof(int);
  7. break;
  8. case jval:
  9. bufsize = jval * sizeof(int);
  10. break;
  11. case kval:
  12. bufsize = kval * sizeof(int);
  13. break;
  14. }

练习5.14

编写一段程序,从标准输入中读取若干string对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。
例如:如果输入是:

  1. how now now now brown cow cow

那么输出应该表明单词now连续出现了3次。

解:

  1. #include <iostream>
  2. #include <string>
  3. using std::cout; using std::cin; using std::endl; using std::string; using std::pair;
  4. int main()
  5. {
  6. pair<string, int> max_duplicated;
  7. int count = 0;
  8. for (string str, prestr; cin >> str; prestr = str)
  9. {
  10. if (str == prestr) ++count;
  11. else count = 0;
  12. if (count > max_duplicated.second) max_duplicated = { prestr, count };
  13. }
  14. if (max_duplicated.first.empty()) cout << "There's no duplicated string." << endl;
  15. else cout << "the word " << max_duplicated.first << " occurred " << max_duplicated.second + 1 << " times. " << endl;
  16. return 0;
  17. }

练习5.15

说明下列循环的含义并改正其中的错误。

  1. (a) for (int ix = 0; ix != sz; ++ix) { /* ... */ }
  2. if (ix != sz)
  3. // . . .
  4. (b) int ix;
  5. for (ix != sz; ++ix) { /* ... */ }
  6. (c) for (int ix = 0; ix != sz; ++ix, ++sz) { /*...*/ }

解:

应该改为下面这样:

  1. (a) int ix;
  2. for (ix = 0; ix != sz; ++ix) { /* ... */ }
  3. if (ix != sz)
  4. // . . .
  5. (b) int ix;
  6. for (; ix != sz; ++ix) { /* ... */ }
  7. (c) for (int ix = 0; ix != sz; ++ix) { /*...*/ }

练习5.16

while循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。
for循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。
如果只能使用一种循环,你倾向于哪种?为什么?

解:

  1. int i;
  2. while ( cin >> i )
  3. // ...
  4. for (int i = 0; cin >> i;)
  5. // ...
  6. for (int i = 0; i != size; ++i)
  7. // ...
  8. int i = 0;
  9. while (i != size)
  10. {
  11. // ...
  12. ++i;
  13. }

如果只能用一种循环,我会更倾向使用while,因为while显得简洁,代码可读性强。

练习5.17

假设有两个包含整数的vector对象,编写一段程序,检验其中一个vector对象是否是另一个的前缀。
为了实现这一目标,对于两个不等长的vector对象,只需挑出长度较短的那个,把它的所有元素和另一个vector对象比较即可。
例如,如果两个vector对象的元素分别是0、1、1、2 和 0、1、1、2、3、5、8,则程序的返回结果为真。

解:

  1. #include <iostream>
  2. #include <vector>
  3. using std::cout; using std::vector;
  4. bool is_prefix(vector<int> const& lhs, vector<int> const& rhs)
  5. {
  6. if(lhs.size() > rhs.size())
  7. return is_prefix(rhs, lhs);
  8. for(unsigned i = 0; i != lhs.size(); ++i)
  9. if(lhs[i] != rhs[i]) return false;
  10. return true;
  11. }
  12. int main()
  13. {
  14. vector<int> l{ 0, 1, 1, 2 };
  15. vector<int> r{ 0, 1, 1, 2, 3, 5, 8 };
  16. cout << (is_prefix(r, l) ? "yes\n" : "no\n");
  17. return 0;
  18. }

练习5.18

说明下列循环的含义并改正其中的错误。

  1. (a) do { // 应该添加花括号
  2. int v1, v2;
  3. cout << "Please enter two numbers to sum:" ;
  4. if (cin >> v1 >> v2)
  5. cout << "Sum is: " << v1 + v2 << endl;
  6. }while (cin);
  7. (b) int ival;
  8. do {
  9. // . . .
  10. } while (ival = get_response()); // 应该将ival 定义在循环外
  11. (c) int ival = get_response();
  12. do {
  13. ival = get_response();
  14. } while (ival); // 应该将ival 定义在循环外

练习5.19

编写一段程序,使用do while循环重复地执行下述任务:
首先提示用户输入两个string对象,然后挑出较短的那个并输出它。

解:

  1. #include <iostream>
  2. #include <string>
  3. using std::cout; using std::cin; using std::endl; using std::string;
  4. int main()
  5. {
  6. string rsp;
  7. do {
  8. cout << "Input two strings: ";
  9. string str1, str2;
  10. cin >> str1 >> str2;
  11. cout << (str1 <= str2 ? str1 : str2)
  12. << " is less than the other. " << "\n\n"
  13. << "More? Enter yes or no: ";
  14. cin >> rsp;
  15. } while (!rsp.empty() && tolower(rsp[0]) == 'y');
  16. return 0;
  17. }

练习5.20

编写一段程序,从标准输入中读取string对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。
使用while循环一次读取一个单词,当一个单词连续出现两次时使用break语句终止循环。
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。

解:

  1. #include <iostream>
  2. #include <string>
  3. using std::cout; using std::cin; using std::endl; using std::string;
  4. int main()
  5. {
  6. string read, tmp;
  7. while (cin >> read)
  8. if (read == tmp) break; else tmp = read;
  9. if (cin.eof()) cout << "no word was repeated." << endl; //eof(end of file)判断输入是否结束,或者文件结束符,等同于 CTRL+Z
  10. else cout << read << " occurs twice in succession." << endl;
  11. return 0;
  12. }

练习5.21

修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。

解:

  1. #include <iostream>
  2. using std::cin; using std::cout; using std::endl;
  3. #include <string>
  4. using std::string;
  5. int main()
  6. {
  7. string curr, prev;
  8. bool no_twice = true;
  9. while (cin >> curr)
  10. {
  11. if (isupper(curr[0]) && prev == curr)
  12. {
  13. cout << curr << ": occurs twice in succession." << endl;
  14. no_twice = false;
  15. break;
  16. }
  17. prev = curr;
  18. }
  19. if (no_twice)
  20. cout << "no word was repeated." << endl;
  21. return 0;
  22. }

练习5.22

本节的最后一个例子跳回到begin,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用goto语句。

  1. // 向后跳过一个带初始化的变量定义是合法的
  2. begin:
  3. int sz = get_size();
  4. if (sz <= 0) {
  5. goto begin;
  6. }

解:

用 for 循环修改的话就是这样

  1. for (int sz = get_size(); sz <=0; sz = get_size())
  2. ;

练习5.23

编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。

解:

  1. #include <iostream>
  2. using std::cin;
  3. using std::cout;
  4. using std::endl;
  5. int main()
  6. {
  7. int i, j;
  8. cin >> i >> j;
  9. cout << i / j << endl;
  10. return 0;
  11. }

练习5.24

修改你的程序,使得当第二个数是0时抛出异常。先不要设定catch子句,运行程序并真的为除数输入0,看看会发生什么?

解:

  1. #include <iostream>
  2. #include <stdexcept>
  3. int main(void)
  4. {
  5. int i, j;
  6. std::cin >> i >> j;
  7. if (j == 0)
  8. throw std::runtime_error("divisor is 0");
  9. std::cout << i / j << std::endl;
  10. return 0;
  11. }

练习5.25

修改上一题的程序,使用try语句块去捕获异常。catch子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行try语句块的内容。

解:

  1. #include <iostream>
  2. #include <stdexcept>
  3. using std::cin; using std::cout; using std::endl; using std::runtime_error;
  4. int main(void)
  5. {
  6. for (int i, j; cout << "Input two integers:\n", cin >> i >> j; )
  7. {
  8. try
  9. {
  10. if (j == 0)
  11. throw runtime_error("divisor is 0");
  12. cout << i / j << endl;
  13. }
  14. catch (runtime_error err)
  15. {
  16. cout << err.what() << "\nTry again? Enter y or n" << endl;
  17. char c;
  18. cin >> c;
  19. if (!cin || c == 'n')
  20. break;
  21. }
  22. }
  23. return 0;
  24. }