Learning Objectives
- 使用 + - * / % 做算術運算,說明整數除法與餘數的規則
- 分辨 ++、-- 的前置與後置,使用 +=、-= 等複合指定運算子
- 用比較運算子產生 bool,用 &&、||、! 組合條件
- 說明短路求值,並用它避免除以 0 這類錯誤
- 依優先順序判讀運算式,避開 = 與 ==、連續比較、小數相等這些常見錯誤
為什麼要學運算子
程式裡的計算與判斷都靠運算子(operator)完成:算總價用 *、判斷及格用 >=、檢查兩個條件用 &&。
運算子和它的運算元組成運算式(expression),每個運算式都會算出一個值。
int total = price * qty; // 算術運算式,結果是數字
bool ok = total >= 100 && member; // 比較與邏輯運算式,結果是 bool
大部分運算子的意思和數學一樣,但 C++ 有幾個地方和數學課不同:整數相除會捨去小數、= 是指定而不是相等、
1 < x < 5 的意思也不是你想的那樣。這一課逐一看清楚。
算術運算子
| 運算子 | 意思 | 例子 | 結果 |
|---|---|---|---|
+ - * |
加、減、乘 | 17 * 5 |
85 |
/ |
除;兩邊都是整數時做整數除法 | 17 / 5 |
3 |
% |
餘數,只能用在整數 | 17 % 5 |
2 |
整數除法的結果往 0 的方向捨去:17 / 5 是 3,-17 / 5 是 -3(不是 -4)。
餘數的正負號跟著被除數:-17 % 5 是 -2。兩者滿足 (a / b) * b + a % b == a。
只要有一邊是 double,就做小數除法:17 / 5.0 是 3.4。運算前,較小的型態會先轉成較大的型態(int 轉 double)。
% 很常用:n % 2 == 0 判斷偶數、n % 10 取個位數、秒數 % 60 換算時間。整數除以 0 或對 0 取餘數是未定義行為,程式通常會當掉。
整數除法與餘數
第 8~10 行對照整數除法與餘數;第 12~16 行用 / 和 % 把 3725 秒換算成時、分、秒,這是 % 最典型的用法。
遞增、遞減與複合指定
++ 讓變數加 1、-- 減 1,各有兩種寫法:
| 寫法 | 名稱 | 運算式的值 |
|---|---|---|
++i |
前置 | 先加 1,取加完的值 |
i++ |
後置 | 先取原本的值,再加 1 |
單獨寫成一句(i++;)時兩者效果相同;差別只在它放在更大的運算式裡(例如 x = i++;)。
不要在同一個運算式裡對同一個變數做好幾次 ++,例如 i++ + i++,結果沒有保證。
複合指定運算子是「運算後存回去」的簡寫:
| 寫法 | 等於 |
|---|---|
x += 5 |
x = x + 5 |
x -= 5 |
x = x - 5 |
x *= 5 |
x = x * 5 |
x /= 5 |
x = x / 5 |
x %= 5 |
x = x % 5 |
前置、後置與複合指定
看第 7 行和第 10 行執行後的變數區:x 拿到的是加 1 之前的 5,y 拿到的是加 1 之後的 6,而 i、j 最後都是 6。第 17~22 行連續用複合指定改 score,每一步的值都看得到。
想一想:算術與遞增
下面的程式會印出什麼?
cout << 19 / 4 << " " << 19 % 4;
下面的程式會印出什麼?
int a = 3;
int b = a++ + 10;
cout << a << " " << b;
下面的程式會印出什麼?
int n = 10;
n -= 3;
n *= 2;
n %= 4;
cout << n;
比較與邏輯運算子
比較運算子的結果是 bool:
| 運算子 | 意思 | 運算子 | 意思 |
|---|---|---|---|
== |
等於 | != |
不等於 |
< |
小於 | <= |
小於或等於 |
> |
大於 | >= |
大於或等於 |
邏輯運算子把條件組合起來:
| 運算子 | 意思 | 何時為 true |
|---|---|---|
&& |
而且(AND) | 兩邊都是 true |
|| |
或(OR) | 至少一邊是 true |
! |
不是(NOT) | 原本是 false |
cout 印 bool 預設是 1、0;先送出 boolalpha,之後就會印 true、false。
印比較或邏輯運算的結果時要加括號,例如 cout << (a < b);,原因在後面的優先順序。
比較與邏輯運算
第 9 行印出的是 1 0 1,第 10 行送出 boolalpha 之後,同樣的值改印成 true false。最後的閏年判斷把三個條件組合起來:能被 4 整除但不能被 100 整除,或者能被 400 整除。
短路求值
&& 和 || 由左而右計算,而且一知道答案就停:
A && B:A 是 false,整體一定是 false,B 不會執行A || B:A 是 true,整體一定是 true,B 不會執行
這叫做短路求值(short-circuit evaluation)。它常用來保護後面的運算,例如先確認除數不是 0 再做除法:
if (count != 0 && total / count > 50) // count 是 0 時,不會執行除法
如果把兩個條件的順序對調,count 是 0 時就會先做除法,造成錯誤。
右邊什麼時候會被跳過
isPositive 被呼叫時會印出「檢查 n」,並在右邊出現它的框架。注意第 12、14 行都沒有進入 isPositive,只有第 16 行進去了。最後的 if 因為 count 是 0,total / count 根本沒有執行。
優先順序與常見錯誤
一個運算式裡有好幾個運算子時,依優先順序決定先算哪個(由高到低,常用的部分):
| 順序 | 運算子 |
|---|---|
| 1 | !、前置 ++ --、正負號 -x |
| 2 | * / % |
| 3 | + - |
| 4 | << >>(也是 cout、cin 用的符號) |
| 5 | < <= > >= |
| 6 | == != |
| 7 | && |
| 8 | || |
| 9 | = += -= 等指定運算子 |
同一層的運算子大多由左而右計算(指定運算子例外,由右而左),所以 20 - 6 - 4 是 10。
不確定時就加括號,括號也讓別人更容易讀懂。
因為 << 的優先順序比比較運算子高,cout << a < b; 會變成 (cout << a) < b,無法編譯;要寫 cout << (a < b);。
三個常見錯誤:
- 把
==寫成=:if (x = 0)是把 0 指定給 x,運算式的值是 0(false),而且 x 被改掉了。 g++ 加上-Wall時會對這種寫法提出警告,建議你加括號;不要忽略它 - 連續比較:
1 < t < 5會先算1 < t得到 true(1)或 false(0),再拿它和 5 比,結果幾乎永遠是 true。 要寫成1 < t && t < 5 - 用
==比較小數:0.1 + 0.2 == 0.3是 false,因為小數在電腦裡有微小誤差。改成比較兩者的差距:fabs(a - b) < 1e-9(fabs在<cmath>)
優先順序與三個常見錯誤
注意第 10 行:if 的條件把 x 改成 0,條件是 false,所以第 11 行沒有執行,但 x 已經被改掉了。第 18 行的 sum 在變數區看起來是 0.3(只顯示 6 位有效數字),和 0.3 比較卻不相等。
想一想:條件與優先順序
下面的程式會印出什麼?
int x = 5;
cout << (x > 3 && x < 5) << (x > 3 || x < 0) << !(x == 5);
下面的程式會印出什麼?
int k = 0;
bool r = (k > 0) && (++k > 0);
cout << k << r;
下面的程式會印出什麼?
int x = 5;
if (x = 0) cout << "A";
else cout << "B";
cout << x;
下面的程式會印出什麼?
cout << 2 + 6 / 2 * 3;
動手寫:找零
商品 630 元,顧客付 1000 元。用 / 算出每種面額要找幾張(個),用 % 算出剩下還要找的錢,面額由大到小:100 元、50 元、10 元。預期輸出:
找零 370 元
100 元:3
50 元:1
10 元:2
重點回顧
- 兩個整數相除是整數除法,往 0 捨去;
%取餘數,正負號跟著被除數;有 double 參與就做小數除法 i++先取值再加 1,++i先加 1 再取值;單獨一句時相同;x += 5是x = x + 5的簡寫- 比較運算子產生 bool;
&&兩邊都要成立、||一邊成立即可、!取反;boolalpha讓 bool 印成 true/false - 短路求值:
&&左邊 false、||左邊 true 時,右邊不會執行,可以用來避免除以 0 - 先乘除、後加減、再比較、再邏輯,最後指定;不確定就加括號
- 比較用
==不是=;範圍判斷寫成a < x && x < b;小數不要用==比較,改看差距