Learning Objectives
- 使用算術運算子,說明 / 的結果何時是浮點數、intdiv 與 % 的用法,以及除以零會丟出 DivisionByZeroError
- 使用複合指派、遞增遞減(分辨前置與後置)與字串串接 . 和 .=
- 分辨 == 與 ===,說明 PHP 8 的字串與數字比較規則,使用 <=> 太空船運算子
- 使用 &&、||、!,說明短路求值與 and/or 優先順序比 = 低的陷阱
- 使用三元運算子、?: 、?? 與 ??=,並依優先順序判斷運算式的計算順序
運算子:讓值變成新的值
運算子(operator)把一個或幾個值變成一個新的值:3 + 4 得到 7、$age >= 18 得到 true、'Hi' . '!' 得到 'Hi!'。
被運算的值叫運算元(operand)。
PHP 的運算子大多和 C、Java、JavaScript 長得一樣,但有幾個地方要特別注意:
- 字串串接用
.,不是+(+只做數字加法) ==會先轉換型別再比較,===才是「型別和值都相同」and、or和&&、||意思相同,但優先順序差很多??專門處理「可能不存在或是 null」的值
這一課每一組運算子都有一個可以逐步執行的範例,最後整理優先順序。
算術運算子
| 運算子 | 意思 | 例子 | 結果 |
|---|---|---|---|
+、-、* |
加、減、乘 | 7 * 2 |
14 |
/ |
除 | 7 / 2、6 / 2 |
3.5(float)、3(int) |
% |
取餘數(模數) | 7 % 3 |
1 |
** |
次方 | 2 ** 10 |
1024 |
-(單元) |
取負號 | -$x |
幾個細節:
/的兩邊都是整數而且整除時結果是 int,否則是 float。PHP 沒有「整數除法」運算子,要取商用intdiv(7, 2)(得到 3)%先把兩邊轉成整數再算;結果的正負號跟著左邊:-7 % 3是 -1、7 % -3是 1。浮點數取餘數用fmod()**的優先順序比負號高,所以-2 ** 2是-(2 ** 2),等於 -4- 除以零:PHP 8 起,
/或%的右邊是 0,會丟出DivisionByZeroError,程式停止(PHP 7 的/只給警告)。除之前先確認除數不是 0
Fatal error: Uncaught DivisionByZeroError: Division by zero in /var/www/html/index.php:2
除法、取餘數與次方
注意第 3、4 行:同樣是 /,結果的型別不同。最後四行是 intdiv 與 % 最常見的用法:把總分鐘數換成「幾小時幾分鐘」。
指派、遞增遞減與字串串接
= 是指派:先算右邊,再把結果存進左邊的變數。$n = $n + 5 可以縮寫成複合指派 $n += 5:
| 寫法 | 等於 |
|---|---|
$n += 5、$n -= 5、$n *= 5、$n /= 5、$n %= 5、$n **= 2 |
$n = $n 運算子 值 |
$s .= '!' |
$s = $s . '!'(在字串後面接上) |
$n ??= 0 |
$n 不存在或是 null 時才指派(本課後面介紹) |
遞增、遞減把變數加 1 或減 1,分成前置與後置:
| 寫法 | 運算式的值 | 變數 |
|---|---|---|
++$i |
加 1 之後的值 | 加 1 |
$i++ |
加 1 之前的值 | 加 1 |
--$i、$i-- |
同上,減 1 | 減 1 |
單獨寫成一行 $i++; 時前置、後置沒有差別;只有把它放在運算式裡(例如 $a = $i++;)才有差。
字串串接用 .:'Hello' . ' ' . 'PHP'。PHP 8 起,. 的優先順序比 +、- 低,
所以 '總分:' . 80 + 5 會先算 80 + 5,得到 '總分:85'(PHP 7 會先串接,再拿字串去加 5)。
混用時加上括號最清楚:'總分:' . (80 + 5)。
複合指派與 $i++、++$i
看左邊 $n 每一行的變化。第 11 到 13 行要同時看兩個變數:$a、$b、$c 拿到的值,和 $i 本身的變化。
比較運算子:== 與 ===
| 運算子 | 意思 |
|---|---|
== |
相等(先轉換型別再比較,寬鬆比較) |
=== |
全等:型別相同而且值相等(嚴格比較) |
!=、<> |
不相等(寬鬆);兩種寫法意思相同 |
!== |
不全等(嚴格) |
<、>、<=、>= |
大小比較 |
<=> |
太空船運算子:左邊小回傳 -1、相等回傳 0、左邊大回傳 1 |
== 的轉換規則很容易出錯。PHP 8 改了字串與數字的比較規則:
- 字串是數字字串(
'1'、'01'、'1e2'、' 42')時,轉成數字比較,所以'1' == '01'、100 == '1e2'都是 true - 字串不是數字字串時,改成把數字轉成字串再比較字串:
0 == 'a'是 false、0 == ''也是 false
在 PHP 7 以前,0 == 'a' 是 true('a' 被轉成 0),這是很多安全漏洞與錯誤的來源。網路上的舊文章可能還寫著這個舊規則。
| 比較 | PHP 7 | PHP 8 |
|---|---|---|
0 == 'a' |
true | false |
0 == '' |
true | false |
42 == '42abc' |
true | false |
'1' == '01' |
true | true |
0 == '0' |
true | true |
其他常見情況:null == false 是 true(兩邊都轉成 bool),'abc' == 'ABC' 是 false(字串比較區分大小寫),
兩個字串用 <、> 比較時依字元的編碼一個一個比('apple' < 'banana')。
原則:除非你確定需要轉換,一律用 === 和 !==。
寬鬆比較與嚴格比較
每個 var_dump 印出好幾個比較的結果,一個一行。先猜猜看每一個是 true 還是 false,再對照輸出。
想一想:算術、遞增與比較
下面的程式會印出什麼?
<?php
echo 10 / 4, ' ', intdiv(10, 4), ' ', 10 % 4, ' ', -10 % 4;
下面的程式會印出什麼?
<?php
$i = 3;
$a = $i++;
$b = --$i;
echo "$a $b $i";
在 PHP 8.3 執行,下面的程式會印出什麼?
<?php
var_dump(0 == 'a', '1' == '01', 100 == '1e2', null == 0);
邏輯運算子
$a && $b或$a and $b:而且,兩邊都是 true 才是 true$a || $b或$a or $b:或者,至少一邊是 true 就是 true!$a:不是,$a是 false 時為 true$a xor $b:互斥或,剛好一邊是 true 才是 true
結果一定是 bool;運算元不是 bool 時,依上一課的「假值」規則轉換。
短路求值:&& 的左邊已經是 false,結果一定是 false,右邊不會執行;|| 的左邊已經是 true,右邊也不會執行。
常用來保護後面的運算,例如 $x !== 0 && 10 / $x > 1:$x 是 0 時不會去做除法。
and、or 的陷阱:它們的意思和 &&、|| 相同,但優先順序比 = 還低:
$r = false or true; // 等於 ($r = false) or true; $r 是 false!
$r = false || true; // 等於 $r = (false || true); $r 是 true
建議一律使用 &&、||,就不會遇到這個問題。
短路求值與 or 的陷阱
第 8 行的 10 / $x 如果真的執行,會因為除以零而停止;但 && 左邊已經是 false,右邊根本不會算。再看第 11 行執行完,$r 是 false,不是 true。
三元運算子與 null 合併運算子
三元運算子 條件 ? 值1 : 值2:條件成立取值1,否則取值2。適合「二選一」的簡短判斷:
$result = $score >= 60 ? '及格' : '不及格';
| 寫法 | 意思 | 左邊是 ''、0 時 |
左邊不存在時 |
|---|---|---|---|
$a ?: $b |
$a 是真值就用 $a,否則用 $b($a ? $a : $b 的簡寫) |
用 $b |
出現 Warning,用 $b |
$a ?? $b |
$a 存在而且不是 null 就用 $a,否則用 $b |
用 $a |
不會有警告,用 $b |
$a ??= $b |
$a 不存在或是 null 時,才把 $b 指派給 $a |
不變 | 指派 |
選擇的原則:要「沒有值才用預設值」用 ??;要「空的、零也換成預設值」用 ?:。
?? 可以串接:$a ?? $b ?? '預設',從左邊開始,取第一個存在而且不是 null 的值。
三元運算子不要巢狀(PHP 8 起,沒有加括號的巢狀三元運算子會直接報錯),條件複雜時改用 if(第 05 課)或 match。
? :、?:、?? 與 ??=
第 8、9 行對同一個空字串 $nick 分別用 ?: 和 ??,結果不同。第 12、13 行連續兩次 ??=,只有第一次會指派。
運算子優先順序
一個運算式裡有好幾個運算子時,優先順序高的先算;同一級的依結合性決定從左或從右算。常用的運算子由高到低:
**(最高;從右往左結合,2 ** 3 ** 2是2 ** 9)++、--、-(負號)、(int)等轉型!*、/、%+、-<<、>>.(PHP 8 起比+、-低)<、<=、>、>===、!=、===、!==、<>、<=>- 位元運算子
&,接著^,接著| &&||??(從右往左結合)? :=、+=、.=、??=等指派(從右往左結合,$a = $b = 0先把 0 給$b)and,接著xor,接著or(最低)
沒有特別註明的,同一級都是從左往右算:10 - 3 - 2 是 (10 - 3) - 2。
例子:2 + 3 * 2 ** 2 先算 2 ** 2 = 4,再算 3 * 4 = 12,最後 2 + 12 = 14。
不確定的時候就加括號,括號裡的永遠先算,別人讀程式也比較輕鬆。完整的表格在 PHP 官方手冊的「Operator Precedence」。
位元運算子把整數當成一串二進位的位元來運算,一般網頁程式很少用到,認得就好:
&、|、^:逐位元的 AND、OR、XOR,例如6 & 3是 2、6 | 3是 7、6 ^ 3是 5(6 是二進位 110、3 是 011)~:每個位元反轉,~0是 -1<<、>>:左移、右移,1 << 3是 8、16 >> 2是 4
最常見的用途是錯誤回報的設定,例如 E_ALL & ~E_NOTICE(全部,但不包括 Notice)。
想一想:邏輯、null 合併與除以零
下面的程式會印出什麼?
<?php
$r = false or true;
var_dump($r);
下面的程式會印出什麼?
<?php
$n = 0;
echo $n ?: 5, ' ', $n ?? 5;
在 PHP 8.3 執行下面的程式,結果是什麼?
<?php
echo 10 / 0;
動手做:換算時間
影片長度是 $seconds 秒。請用 intdiv 與 % 算出幾小時、幾分、幾秒;用三元運算子判斷是否超過一小時;使用者名稱 $user 是 null 時顯示「訪客」。執行後應該印出:
1 小時 2 分 5 秒
超過一小時:是
使用者:訪客
重點回顧
/整除時是 int,否則是 float;取商用intdiv();%的正負號跟著左邊;**是次方,比負號優先- PHP 8 起除以零(
/與%)丟出DivisionByZeroError - 複合指派
+=、.=、??=;$i++的值是加 1 前、++$i的值是加 1 後 - 字串串接用
.;PHP 8 起.比+、-低,混用時加括號 ==先轉換型別再比較,===型別與值都要相同;預設用===、!==- PHP 8:數字與非數字字串比較時,把數字轉成字串比,所以
0 == 'a'是 false <=>回傳 -1、0、1&&、||會短路;and、or優先順序比=低,$r = false or true;讓$r是 false,一律用&&、||?:看真假、??只看 null 或不存在;不確定優先順序就加括號