# 第四章 運算式 練習 ## 練習 4.1 5 + 10 * 20 / 2 回傳的值是什麼? 解: 等同於 (5 + ((10 * 20) / 2)),回傳 105 ## 練習 4.2 參閱表 4.12,在下列運算式中加入括弧,指出運算元歸組的順序 (a) *vec.begin() (b) *vec.begin() + 1 解: ```cpp (a)*(vec.begin()) (b)(*(vec.begin())) + 1 ``` ## 練習 4.3 大多數二元運算子的估算順序都未定義,以讓編譯器有進行最佳化的機會。這種策略要在有效率的程式碼產生和程式設計師誤用此語言的潛在可能性之間取捨。你認為這是一種可接受的取捨嗎?為何呢?或為何不是呢? 解: 可以接受。C++ 的設計思想就是盡可能地"相信"程式設計師,將效率最大化。然而這種思想卻有著潛在的危害,就是無法控制程式設計師自身引發的錯誤。因此 Java 的誕生也是必然,Java 的思想就是盡可能地"不相信"程式設計師 ## 練習 4.4 為下列的運算式加上括弧,顯示它是如何被估算的。編譯這個運算式(不帶括弧的)並印出結果來驗證你的答案 12 / 3 * 4 + 5 * 15 + 24 % 4 / 2 解: ((((12 / 3) * 4) + (5 * 15)) + ((24 % 4) / 2)) = ((16 + 75) + 0) = 91 ## 練習 4.5 判斷下列運算式的結果 ```cpp (a)-30 * 3 + 21 / 5 (b)-30 + 3 * 21 / 5 (c)30 / 3 * 21 % 5 (d)-30 / 3 * 21 % 4 ``` - (a):-86 - (b):-18 - (c):0 - (d):-2 ## 練習 4.6 寫一個運算式來判斷一個 int 值是偶數(even)或奇數(odd) 解: ```cpp if (i % 2); /* 判斷為偶數 */ if (i & 0x1); /* 判斷為奇數 */ ``` ## 練習 4.7 溢位(overflow)代表什麼呢?展示三個會溢位的運算式 解: 當計算的結果超出該型別所能表示的範圍就會產生溢位 ```cpp short svalue = 32767; ++svalue; // -32768 unsigned uivalue = 0; --uivalue; // 4294967295 unsigned short usvalue = 65535; ++usvalue; // 0 ``` 註:在許多系統上,發生 overflow 時,不會有任何的編譯期或執行期警告,就跟其他位定義行為一樣,到底發生什麼事,是不可預知的。在大多數的系統上,以 svalue 舉例,會產出 -32768 這個值繞回來(wrapped around)了,正負號位元(sign bit)之前是 0,現在設為 1 了,結果就是個負值,但在其他系統上,結果可能會不同,或程式行為會不同,包括完全當掉 ## 練習 4.8 說明邏輯 AND、邏輯 OR 與相等性運算子中的運算元何時被估算 解: 邏輯 And 運算子與邏輯 OR 運算子都是先求左側運算式的值再求右側運算式的值,且僅當左側運算式無法確定該邏輯運算的結果時,才會再計算右側運算式的值,這種作法稱作短路估算(short-circuit evaluated) 至於相等性運算子並沒有定義求值順序 ## 練習 4.9 解釋下列 if 中條件的行為: ```cpp const char *cp = "Hello World"; if (cp && *cp) ``` 解: 先判斷 cp,但 cp 並不是一個空指標,因此 cp 為 true,然後再判斷 \*cp,\*cp 的值是字元 H,非 0,因此最後的結果為 true ## 練習 4.10 為一個 while 迴圈寫出會從標準輸入讀取 int 並在讀到的值等於 42 時停止的條件 解: ```cpp #include <iostream> using std::cin; int main() { int i; while (cin >> i && i != 42) ; } ``` ## 練習 4.11 寫一個運算式測試四個值 a、b、c 與 d,確保 a 大於 b,而 b 大於 c,c 大於 d 解: ```cpp a > b && b > c && c > d ``` ## 練習 4.12 假設 i、j 與 k 全都是 int,請解釋 i != j < k 的意義 解: 這個運算式等於 (i != (j < k))。首先得到 j < k 的結果為 true 或 false,轉換為整數值是 1 或 0,然後判斷 i 是否不等於該值,而最終的結果為 bool ## 練習 4.13 每個指定後的 i 和 d 值各是什麼? ```cpp int i; double d; (a)d = i = 3.5; (b)i = d = 3.5; ``` - (a):i = 3, d = 3.0 - (b):d = 3.5, i = 3 ## 練習 4.14 解釋這些 if 測試中發生了什麼事: ```cpp if (42 = i) if (i = 42) ``` 解: 第一個 if 導致編譯錯誤。在指定運算子左側的運算元必須要是個可修改的左值,而字面值(literal)是右值 第二個 if 的運算結果保證為 true ## 練習 4.15 下列的指定是非法的。為什麼呢?你要如何更正它們呢? ```cpp double dval; int ival; int *pi; dval = ival = pi = 0; ``` 解: p 是指標,不能指定給 int,應改為: ```cpp dval = ival = 0; pi = 0; ``` ## 練習 4.16 雖然下列的程式碼是合法的,它們的行為或許不如程式設計師所預期的。為什麼呢? ```cpp if (p = getPtr() != 0) if (i = 1024) ``` 解: ```cpp if ((p=getPtr()) != 0) if (i == 1024) ``` ## 練習 4.17 解釋前綴(prefix)與後綴(postfix)遞增之間的差異 解: 前綴遞增運算子將物件本身作為左值回傳,而後綴遞增運算子將物件原始值的拷貝作為右值回傳 ## 練習 4.18 如果前面印出一個 vector 的元素的 while 迴圈使用前綴遞增運算子,會發生什麼事? 解: 將會從第二個元素開始解參考(dereference),並且最後對 v.end() 進行解參考,結果是未定義的 ## 練習 4.19 如果 ptr 指向一個 int,而 vec 是一個 vector\<int>,而 ival 是一個 int,請解釋下列各個運算式的行為。如果有的話,哪一個很可能不是正確的呢?為什麼?要如何更正呢? ```cpp (a)ptr != 0 && *ptr++ (b)ival++ && ival (c)vec[ival++] <= vec[ival] ``` 解: - (a)判斷 ptr 不是一個空指標,並且 ptr 當前指向的元素的值也為 true,然後將 ptr 指向下一個元素 - (b)判斷 ival 的值為 true,並且 ival + 1 的值也為 true - (c)表達式有錯誤,C++ 並沒有規定關係運算子(relational operator <=)運算子兩邊的求值順序,應該改為 vec[ival] <= vec[ival+1],這樣即保證 ival 本體的值不會被修改 ## 練習 4.20 假設 iter 是一個 vector\<string>::iterator,指出下列哪些運算式是合法的(如果有的話)。解釋合法運算式的行為,以及那些不合法的錯在哪裡 ```cpp (a)*iter++; (b)(*iter)++; (c)*iter.empty(); (d)iter->empty(); (e)++*iter; (f)iter++->empty(); ``` 解: - (a)合法,回傳迭代器所指向的元素,然後迭代器遞增 - (b)不合法,因為 vector 元素的型別是 string,沒有 ++ 的運算子重載(operator overloading) - (c)不合法,這裡加括號:(*iter).empty() - (d)合法,判斷迭代器當前的元素是否為空 - (e)不合法,string 型別沒有 ++ 的運算子重載(operator overloading) - (f)合法,判斷迭代器所指向的元素當前是否為空,然後迭代器遞增 ## 練習 4.21 寫一個程式來使用條件運算子,找出 vector\<int> 中有奇數值的元素,並將每個這種值都變成兩倍 解: ```cpp #include <iostream> #include <vector> using std::cout; using std::endl; using std::vector; int main() { vector<int> ivec{1, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto &i : ivec) // 使用參考才能更改本體的值 cout << ((i & 0x1) ? i * 2 : i) << " "; cout << endl; return 0; } ``` ## 練習 4.22 擴充會指定高分通過、通過或沒通過的程式,讓它也會為介於 60 和 75 之間(包括兩端)的成績指定低分通過(low pass)。撰寫兩個版本:一個只使用條件運算子,另一個應該使用一或更多個 if 述句。你認為哪個版本比較容易理解?為麼呢? 解: ```cpp // 使用條件運算子 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { for (unsigned g; cin >> g;) { auto result = g > 90 ? "High Pass" : g < 60 ? "Fail" : g < 75 ? "Low Pass" : "Pass"; cout << result << endl; } return 0; } ``` ```cpp // 使用條件述句 #include <iostream> using std::cin; using std::cout; using std::endl; int main() { for (unsigned g; cin >> g;) { if (g > 90) cout << "High Pass"; else if (g < 60) cout << "Fail"; else if (g <= 75) cout << "Low Pass"; else cout << "Pass"; cout << endl; } return 0; } ``` 第二個版本比較容易理解。當條件運算子嵌套好幾層以後,可讀性會變得很低,而 if else 的邏輯較為清晰 ## 練習 4.23 下列運算式出於運算子優先序(precedence)的關係無法編譯。使用表 4.12,解釋為何出錯,你會如何進行修正? ```cpp string s = "Word"; string pl = s + s[s.size() - 1] == 's' ? "" : "s"; ``` 解: 加法運算子的優先序(precedence)高於條件運算子,因此應改為: ```cpp string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ; ``` ## 練習 4.24 我們區分高分通過、通過和沒通過的程式仰賴條件運算子是右結合的這個事實。描述如果該運算子是左結合的,會如何進行估算(evaluated) 解: 如果條件運算子滿足的是左結合律,那麼: finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass"; 等同於 finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass"; 假如此時 grade > 90,第一個條件運算式的結果是 "high pass",而字元字串字面值的型別是 const char*,因為非 nullptr 所以為 true,因此第二個條件運算式的結果是 "fail",這樣就出現了與題意相矛盾的邏輯 ## 練習 4.25 在 int 為 32 位元,char 為 8 位元的機器上使用 Latin-1 字元集,其中 'q' 的位元模式是 01110001,請問 ~'q' << 6 的值是什麼? 解: 首先將 char 型別提升(promote)為 int 型別,即:00000000 00000000 00000000 01110001,然後取反,再往左 shift 6 位,結果是 -7296 ## 練習 4.26 在本節中我們的成績範例中,如果我們使用 unsigned int 作為 quiz1 的型別,會發生什麼事? 解: 在某些機器上,unsigned int 型別有可能只有 16 位元,因此結果可能是未定義的 ## 練習 4.27 下列各個運算式的結果為何? ```cpp unsigned long ul1 = 3, ul2 = 7; (a)ul1 & ul2 (b)ul1 | ul2 (c)ul1 && ul2 (d)ul1 || ul2 ``` 解: - (a) 3 - (b) 7 - (c) true - (d) ture ## 練習 4.28 寫一個程式來印出每個內建型別(built-in type)之大小 解: ```cpp #include <iostream> using namespace std; int main() { cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl; cout << "char:\t\t" << sizeof(char) << " bytes" << endl; cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl; cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl; cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl; cout << "short:\t\t" << sizeof(short) << " bytes" << endl; cout << "int:\t\t" << sizeof(int) << " bytes" << endl; cout << "long:\t\t" << sizeof(long) << " bytes" << endl; cout << "long long:\t" << sizeof(long long) << " bytes" << endl; cout << "float:\t\t" << sizeof(float) << " bytes" << endl; cout << "double:\t\t" << sizeof(double) << " bytes" << endl; cout << "long double:\t" << sizeof(long double) << " bytes" << endl; return 0; } ``` 輸出: ``` bool: 1 bytes char: 1 bytes wchar_t: 2 bytes char16_t: 2 bytes char32_t: 4 bytes short: 2 bytes int: 4 bytes long: 4 bytes long long: 8 bytes float: 4 bytes double: 8 bytes long double: 16 bytes ``` ## 練習 4.29 預測下列程式馬並解釋你的推理。現在執行程式。輸出如你預期嗎?如果不是,請找出原因 ```cpp int x[10]; int *p = x; cout << sizeof(x) / sizeof(*x) << endl; cout << sizeof(p) / sizeof(*p) << endl; ``` 解: 第一個輸出結果為 10,第二個結果為 1 ## 練習 4.30 使用表 4.12,為下列運算式加上括弧(parentheses)來產生和預設估算相同的結果: ```cpp (a)sizeof x + y (b)sizeof p->mem[i] (c)sizeof a < b (d)sizeof f() ``` 解: - (a)(sizeof(x)) + y - (b)sizeof(p->mem[i]) - (c)sizeof(a) < b - (d)sizeof(f()) ## 練習 4.31 本節中的程式使用了前綴遞增和遞減運算子。請解釋為何使用前綴而非後綴呢?要做什麼變更才能使用後綴版本呢?使用後綴運算子改寫程式 解: 除非必需,否則不用遞增遞減運算子的後綴版本,但若要使用,在此立下並不需要任何改動,直接改為後綴即可 ## 練習 4.32 解釋下列迴圈 ```cpp constexpr int size = 5; int ia[size] = {1, 2, 3, 4, 5}; for (int *ptr = ia, ix = 0; ix != size && ptr != ia + size; ++ix, ++ptr) { /* ... */ } ``` 解: 這個迴圈在遍歷陣列 ia,而指標 ptr 和整數型 ix 都是用於循環計數的功能 ## 練習 4.33 使用表 4.12 解釋下列運算式會做些什麼事情: ```cpp someValue ? ++x, ++y : --x, --y ``` 解: 因為逗號運算子的優先序(precedence)是最低的,因此這個運算式等同於: ```cpp (someValue ? ++x, ++y : --x), --y ``` 如果 someValue 的值為 true,x 和 y 的值都會遞增並且回傳 y 值,然後丟棄 y 值,y 遞減並回傳 y 值,如果 someValue 的值為 false,x 遞減並回傳 x 值,然後丟棄 x 值,y 遞減並回傳 y 值 ## 練習 4.34 給定本節中的變數定義,解釋下列運算式會發生何種轉換: ```cpp (a)if (fval) (b)dval = fval + ival; (c)dval + ival * cval; ``` 解: - (a)fval 轉型為 bool - (b)ival 轉型為 float,相加的結果轉型為 double - (c)cval 轉型為 int,相加的結果轉型為 double ## 練習 4.35 給定下列定義, ```cpp char cval; int ival; unsigned int ui; float fval; double dval; ``` 辨識出隱含的型別轉換,如果有的話: ```cpp (a)cval = 'a' + 3; (b)fval = ui - ival * 1.0; (c)dval = ui * fval; (d)cval = ival + fval + dval; ``` 解: - (a)'a' 轉型為 int,與 3 相加後轉型為 char - (b)ival 轉型為 double,ui 轉型為 double,相減後轉型為 float - (c)ui 轉型為 float,與 fval 相乘後轉型為 double - (d)ival 轉型為 float,與 fval 相加後轉型為 double,再與 dval 相加後轉型為 char ## 練習 4.36 假設 i 是一個 int 而 d 是一個 double,請寫出運算式 i *= d,讓它進行整數的,而非浮點數的,乘法運算 解: ```cpp i *= static_cast<int>(d); ``` ## 練習 4.37 使用具名的強制轉型改寫下列的舊式強制轉型 ```cpp int i; double d; const string *ps; char *pc; void *pv; (a)pv = (void *)ps; (b)i = int(*pc); (c)pv = &d; (d)pc = (char *)pv; ``` 解: - (a)pv = static_cast<void*>(const_cast<string*>(ps)); - (b)i = static_cast<int>(*pc); - (c)pv = static_cast<void*>(&d); - (d)pc = static_cast<char*>(pv); ## 練習 4.38 解釋下列運算式 ```cpp double slope = static_cast<double>(j / i); ``` 解: 將 j / i 的結果值轉型為 double,然後再指定給 slope