# 第五章 述句 練習 ## 練習 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; } ```