# 第五章 述句 練習
## 練習 5.1
null statement 是什麼?什麼時候你會用到一個 null statement?
解:
只含一個單獨的分號的述句就是 null statement,如果在程式的某個地方,語法上需要一條述句但是邏輯上不需要,此時就應該使用 null statement
```cpp
while (cin >> s && s != sought);
```
## 練習 5.2
區塊是什麼?什麼時候你會用到一個區塊?
解:
用大括號(curly brackets)包起來的述句和宣告的序列就是區塊,如果在程式的某個地方,語法上需要一條述句,而邏輯上需要多條述句,此時應該使用區塊
```cpp
while (val <= 10) {
sum += val;
++val;
}
```
## 練習 5.3
使用逗號運算子(comma operator)改寫來自 1.4.1 的 while 迴圈,讓它不再需要一個區塊。解釋這種改寫方式增進或減少程式碼的可讀性
解:
```cpp
while (val <= 10)
sum += val, ++val;
```
程式碼的可讀性反而降低了
## 練習 5.4
解釋下列每個範例,更正你所發現的任何問題
```cpp
(a)while(string::iterator iter != s.end()) { /* . . . */
}
(b)while(bool status = find(word)) { /* . . . */
}
if (!status) { /* . . . */
}
```
解:
- (a)這個迴圈試圖用迭代器遍歷 string,但是變數的定義應該放在迴圈的外面,而目前的迴圈每次都會重新定義一個變數,因此是錯誤
- (b)這個迴圈的 while 和 if 是兩個獨立的述句,if 述句中無法存取 status 變數,正確的作法是應該將 if 包含在 while 裡面
## 練習 5.5
使用一個 if else 述句寫出你自己版本的程式,來從數值成績產生字母成績
解:
```cpp
#include <iostream>
#include <string>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;
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;
}
```
## 練習 5.6
改寫你的成績程式,使用條件運算子(conditional operator),來取代 if else 述句
解:
```cpp
#include <iostream>
#include <string>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;
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;
}
```
## 練習 5.7
更正下列程式碼片段中的錯誤:
```cpp
(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 後面少了分號(semi-colon)
- (b)應使用花括號包起來(curly braces)
- (c)if (!ival) 應改為 else
- (d)if (ival = 0) 應改為 if (ival == 0)
## 練習 5.8
什麼是懸置的 else(dangling else)?C++ 中的 else 子句如何解析?
解:
用來描述在嵌套的 if else 述句中,如果 if 比 else 多時如何處理的問題。C++ 使用的方法是 else 配對最接近的 if,若沒有配對到即為懸置 else
## 練習 5.9
使用一系列的 if 述句寫一個程式計數從 cin 讀入的文字中母音的總數
解:
```cpp
#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) {
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;
```
## 練習 5.10
我們實作的母音計數程式有一個問題:它不會把大寫字母算為母音。寫個能夠將大小寫字母都適切地算為母音的程式,也就是說,你的程式應該將 'a' 及 'A' 都累計到 aCnt,依此類推
解:
```cpp
#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) {
ch = tolower(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;
}
```
## 練習 5.11
修改我們的母音計數程式,讓它也計數讀到的空格、tab 以及 newlines
解:
```cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
using std::noskipws;
int main() {
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0,
tabCnt = 0, newLineCnt = 0;
char ch;
while (cin >> noskipws >> ch) { // no skip whitespce
ch = tolower(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;
else if (ch == ' ')
++spaceCnt;
else if (ch == '\t')
++tabCnt;
else if (ch == '\n')
++newLineCnt;
}
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 tab char: \t" << tabCnt << '\n'
<< "Number of new line: \t" << newLineCnt << endl;
return 0;
}
```
使用 noskipws 可以保留默認跳過的空格
## 練習 5.12
修改我們的母音計數程式,讓它計數下列雙字母序列出現的頻率:ff、fl 與 fi
解:
```cpp
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
using std::noskipws
int main() {
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0,
tabCnt = 0, newLineCnt = 0;
unsigned ffCnt = 0, flCnt = 0, fiCnt = 0;
char cur, prev = '\0';
while (cin >> noskipws >> cur) { // no skip whitespce
cur = tolower(cur);
if (cur == 'a')
++aCnt;
else if (cur == 'e')
++eCnt;
else if (cur == 'i') {
++iCnt;
if (prev == 'f')
fiCnt++;
} else if (cur == 'o')
++oCnt;
else if (cur == 'u')
++uCnt;
else if (cur == ' ')
++spaceCnt;
else if (cur == '\t')
++tabCnt;
else if (cur == '\n')
++newLineCnt;
else if (cur == 'l' && prev == 'f')
flCnt++;
else if (cur == 'f' && prev == 'f')
ffCnt++;
prev = cur;
}
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 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;
}
```
## 練習 5.13
列於後面用於練習 5.13 的程式碼中的每個程式都含有一個常見的程式設計錯誤。請找出並更正每個錯誤
```cpp
(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
```cpp
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 並未定義,應該在 switch 的外部定義 ix
```cpp
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 後面使用冒號而不是逗號
```cpp
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 lable 必須要是整數型別的常數運算式(constant expression)
```cpp
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 這個字出現了三次
解:
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::pair;
using std::string;
int main() {
pair<string, int> maxDuplicated;
int Cnt = 0;
for (string cur, prev; cin >> cur; prev = cur) {
if (cur == prev)
++Cnt;
else {
Cnt = 0;
continue;
}
if (Cnt > maxDuplicated.second)
maxDuplicated = {prev, Cnt};
}
if (maxDuplicated.first.empty())
cout << "There's no duplicated string." << endl;
else
cout << "the word " << maxDuplicated.first << " occurred "
<< maxDuplicated.second + 1 << " times. " << endl;
return 0;
}
```
## 練習 5.15
解釋下列每個迴圈。更正你所偵測到的任何問題
```cpp
(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)ix 所在的範疇太小,需在外部宣告
```cpp
int ix;
for (ix = 0; ix != sz; ++ix) { /* ... */
}
if (ix != sz)
// . . .
```
- (b)for 標頭必須要有三個欄位,缺一不可
```cpp
int ix;
for (; ix != sz; ++ix) { /* ... */
}
```
- (c)此迴圈可能為無限迴圈
```cpp
for (int ix = 0; ix != sz; ++ix) { /*...*/
}
```
## 練習 5.16
while 迴圈特別適合用於某個條件成立時就要重複執行的工作,例如當我們需要不斷讀取值,值到檔案結尾為止的時候。for 迴圈一般被想成是一種逐步迴圈(step loop):藉由一個索引逐步處理過某個群集(collection)中一整個範圍(range)的值。以這兩個迴圈各自的習慣寫出兩個程式使用它們,然後改以另一個迴圈構造改寫這兩個程式。如果你只能使用一種迴圈,你會選哪個?為什麼呢?
解:
```cpp
int i;
while ( cin >> i )
// ...
int ary[10];
for (int i = 0; i <= 10; i++){
ary[i]; // ...
}
```
```cpp
for (int i = 0; cin >> i;)
// ...
int ary[10];
int i = 0;
while (i != 10)
{
// ...
++i;
}
```
如果只能用一種迴圈,我傾向使用 while,因為 while 顯得更簡潔,程式碼可讀性更強
## 練習 5.17
給定由 int 組成的兩個 vector,寫一個程式來判斷其中一個 vector 是否為另一個的前綴(prefix)。對於長度不同的 vector,比較較小的 vector 的元素數。舉例來說,若給定分別含有 0、1、1 與 2 以及 0、1、1、2、3、5、8 的兩個 vector,你的程式應該回傳 true
解:
```cpp
#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> v1{0, 1, 1, 2};
vector<int> v2{0, 1, 1, 2, 3, 5, 8};
cout << (is_prefix(v2, v1) ? "yes\n" : "no\n");
return 0;
}
```
## 練習 5.18
解釋下列的迴圈。更正你所偵測到的任何問題
```cpp
(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 之間應加上大括號(curly braces)
- (b)應將 ival 定義在 do while 之外
- (c)應將 ival 定義在 do while 之外
## 練習 5.19
寫一個程式,使用 do while 向使用者重複請求兩個 string,並回報哪個 string 比較短
解:
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
int main() {
string nextCheck;
do {
cout << "Input two strings: ";
string str1, str2;
cin >> str1 >> str2;
cout << (str1 == str2 ? "Equal"
: str1 < str2 ? "First small"
: "Second small")
<< endl;
cout << " Try again? Enter y or n";
cin >> nextCheck;
} while (!nextCheck.empty() && tolower(nextCheck[0]) == 'y');
return 0;
}
```
## 練習 5.20
寫一個程式從標準輸入讀取一序列的 string,直到相同的字詞(word)連續出現兩次,或所有的字都已讀完為止。使用一個 while 迴圈從文字輸入一次讀取一個字。使用 break 述句在某個字連續出現兩次時,終結迴圈。如果是出現兩次,就印出那個字,不然就印出一個訊息指出沒有重複的字
解:
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
int main() {
string cur, prev;
bool twice = false;
while (cin >> cur) {
if (cur == prev) {
twice = true;
break;
}
prev = cur;
}
if (twice)
cout << cur << " occurs twice in succession." << endl;
else
cout << "no word was repeated." << endl;
return 0;
}
```
## 練習 5.21
修改 5.5.1 中的練習,讓它僅尋找以一個大寫字母開頭的重複字詞
解:
```cpp
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;
int main() {
string cur, prev;
bool twice = false;
while (cin >> cur) {
if (isupper(cur.at(0)) && cur == prev) {
twice = true;
break;
}
prev = cur;
}
if (twice)
cout << cur << " occurs twice in succession." << endl;
else
cout << "No word was repeated." << endl;
return 0;
}
```
## 練習 5.22
本節最後一個會跳回 begin 的那個範例用一個迴圈來寫會比較好。請改寫那段程式碼,消除 goto 的使用
```cpp
// 往回跳過一個已經初始化過的變數定義是沒問題的
begin : int sz = get_size();
if (sz <= 0) {
goto begin;
}
```
解:
可使用 for、while 迴圈修改
```cpp
// for loop
for (int sz = get_size(); sz <= 0; sz = get_size())
;
```
``` cpp
// while loop
int sz = get_size();
while (sz <= 0)
sz = get_size();
```
## 練習 5.23
寫一個程式從標準輸入讀取兩個整數,並印出第一個數字除以第二個數字的結果
解:
```cpp
#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;
}
```
## 練習 5.24
改寫你的程式,讓它在第二個數字是零的時候擲出一個例外。以一個零輸入測試你的程式,看看如果你不 catch 例外,在你的系統上會發生甚麼事
解:
```cpp
#include <iostream>
#include <stdexcept>
using std::cin;
using std::cout;
using std::endl;
using std::runtime_error;
int main(void) {
int i, j;
cin >> i >> j;
if (j == 0)
throw runtime_error("Divisor is 0.");
cout << i / j << endl;
return 0;
}
```
如果沒有使用 catch 抓住 exception,會導致程式呼叫 terminate 的程式庫中的某個函式,依據系統而定,在 windows 中會呼叫 abort() 而使該程式結束執行
## 練習 5.25
改寫前一個練習你寫的程式,使用一個 try 區塊來 catch 那個例外。這個 catch 子句應該印出一個訊息給使用者,並要求他們提供一個新的數字,並重複 try 裡面的程式碼
解:
```cpp
#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() << "\tTry again!" << endl;
}
}
return 0;
}
```