首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 比較運算與邏輯運算完全指南:類型轉換、優先級、避坑大全

     編輯:PHP 綜合知識

前言

PHP 的弱類型特性導致比較運算極其複雜,是 Bug 高發區。本文系統梳理類型轉換規則、比較語義、邏輯運算優先級、短路求值特性,並提供決策表與最佳實踐,幫助開發者寫出可預測、無驚喜的代碼。

一、empty() 與 isset() 判斷真值表

值empty()isset()boolval()
未定義變量true (Notice)falsefalse
nulltruefalsefalse
falsetruetruefalse
0 (int)truetruefalse
0.0 (float)truetruefalse
'0' (string)truetruefalse
'' (空字符串)truetruefalse
[] (空數組)truetruefalse
SimpleXML 空元素truefalsefalse
無屬性對象 (PHP<5.0)truetruetrue
__get() 返回上述值truefalse依具體值

關鍵差異

  • empty():檢查「是否為空值」,不觸發 Notice,可用於未定義變量
  • isset():檢查「是否已定義且非 null」,不觸發 Notice
  • array_key_exists():檢查「鍵是否存在」,包含 null 值
<?php
$a = ['key' => null];
empty($a['key']);           // true
isset($a['key']);           // false
array_key_exists('key', $a); // true

// __get 魔術方法陷阱
class Test {
    public function __get($name) { return null; }
}
$obj = new Test();
empty($obj->prop);  // true (調用 __get 返回 null)
isset($obj->prop);  // false (調用 __get 返回 null)
?>

二、類型轉換規則總表

標量轉換矩陣

原值(bool)(int)(float)(string)(array)(object)
nullfalse00.0''[]stdClass{}
falsefalse00.0''[0=>false]stdClass{scalar:false}
truetrue11.0'1'[0=>true]stdClass{scalar:true}
0false00.0'0'[0=>0]stdClass{scalar:0}
1true11.0'1'[0=>1]stdClass{scalar:1}
'abc'true00.0'abc'[0=>'abc']stdClass{scalar:'abc'}
'123'true123123.0'123'[0=>'123']stdClass{scalar:'123'}
'0x12'true00.0'0x12'[0=>'0x12']stdClass{scalar:'0x12'}
[]false00.0'Array' (Notice)[]stdClass{}
[1,2]true11.0'Array' (Notice)[1,2]stdClass{0=>1,1=>2}

字符串轉數字細節

<?php
// 規則:從左向右提取數字字符,遇非數字停止
// 十六進制僅在數學運算/特定函數中識別

var_dump('123abc' + 0);      // int(123)
var_dump('0x12' + 0);        // int(18)  數學運算識別十六進制
var_dump((int)'0x12');       // int(0)   強制轉換不識別
var_dump((float)'0x12');     // float(0)
var_dump(intval('0x12'));    // int(0)
var_dump(is_numeric('0x12')); // true    is_numeric 識別十六進制
var_dump(is_numeric('0x12.3')); // false  不識別十六進制小數

// 科學計數法
var_dump('1.2e3' + 0);       // float(1200)
var_dump('1.2E-3' + 0);      // float(0.0012)
?>

三、比較運算符語義

== (鬆散比較) vs === (嚴格比較)

表達式結果說明
0 == '0'true字符串轉數字
0 === '0'false類型不同
'0' == falsetrue都轉 bool
null == falsetrue都轉 bool
null === falsefalse類型不同
[] == falsetrue空數組轉 bool
'abc' == 0true字符串轉數字得 0
'abc' == '0'false字符串比較
123 == '0123'true前導零不視為八進制
'0123' == '123'false字符串比較
0.1 + 0.2 == 0.3false浮點精度問題

比較決策流程(內部邏輯)

graph TD
    A[比較 $a == $b] --> B{類型相同?}
    B -->|是| C[直接比較值]
    B -->|否| D{有 bool/null?}
    D -->|是| E[雙方轉 bool 比較]
    D -->|否| F{有數字類型?}
    F -->|是| G[雙方轉數字比較]
    F -->|否| H[雙方轉字符串比較]
    
    C --> I[數字: 數值比較]
    C --> J[字符串: 字典序]
    C --> K[數組: 鍵值對比較]
    C --> L[對象: 屬性比較(同類) / 類名比較(異類)]

數組比較規則

<?php
$a = [1, 2, 3];
$b = [1, 2, 3];
$c = ['a' => 1, 'b' => 2];
$d = ['b' => 2, 'a' => 1];

var_dump($a == $b);  // true (鍵值順序相同)
var_dump($a === $b); // true
var_dump($c == $d);  // true (鍵值相同,順序無關)
var_dump($c === $d); // false (鍵順序不同)

$a = [1 => 'a', 0 => 'b'];
$b = [0 => 'b', 1 => 'a'];
var_dump($a == $b);  // true
var_dump($a === $b); // false (鍵順序不同)
?>

四、邏輯運算符優先級與短路求值

優先級表(高到低)

<?php
// 1. clone new
// 2. [] (array)
// 3. ** (冪)
// 4. ++ -- ~ (一元)
// 5. instanceof
// 6. * / %
// 7. + - .
// 8. << >>
// 9. < <= > >=
// 10. == != === !== <> <=> (比較)
// 11. & (位與)
// 12. ^ (位異或)
// 13. | (位或)
// 14. && (邏輯與)  ← 關鍵:比 and 高
// 15. || (邏輯或)  ← 關鍵:比 or 高
// 16. ?? (空合並)
// 17. ? : (三元)   ← 右結合!
// 18. = += -= *= /= .= %= &= |= ^= <<= >>= ??=
// 19. and (邏輯與) ← 極低優先級
// 20. xor (邏輯異或)
// 21. or (邏輯或)  ← 極低優先級

經典陷阱:&& vs and

<?php
// 情況 1:賦值表達式
$a = 1;
$b = 0;

$b && $a = 100;  // 等價 $b && ($a = 100)
echo $a;         // 1 (短路,$a 未賦值)

$b || $a = 200;  // 等價 $b || ($a = 200)
echo $a;         // 200 (執行賦值)

// 情況 2:函數調用鏈
function f1() { echo "f1\n"; return false; }
function f2() { echo "f2\n"; return true; }

f1() && f2();    // 輸出 f1 (短路)
f1() and f2();   // 輸出 f1\nf2 (and 優先級極低,等價 f1() and (f2()))

// 建議:統一使用 && || ,避免 and or xor
?>

三元運算符右結合陷阱

<?php
// PHP 三元運算符是左結合(大多數語言右結合)
$arg = 'T';
$vehicle = ($arg == 'B') ? 'bus' : 
           ($arg == 'A') ? 'airplane' : 
           ($arg == 'T') ? 'train' : 
           ($arg == 'C') ? 'car' : 'feet';

echo $vehicle; // 輸出 'horse' 而非 'train'!

// 解析過程(左結合):
// (((($arg == 'B') ? 'bus' : ($arg == 'A')) ? 'airplane' : ($arg == 'T')) ? 'train' : ...)
// 實際計算:
// 1. ($arg == 'B') ? 'bus' : ($arg == 'A')  → false ? 'bus' : false → false
// 2. false ? 'airplane' : ($arg == 'T')     → false ? ... : true → true
// 3. true ? 'train' : ($arg == 'C')         → 'train' ? ... : false → 'train'
// 4. 'train' ? 'car' : 'feet'               → 'train' (非空字符串為 true) → 'train'
// 等等... 實際輸出 'horse' 是舊版本行為,PHP 8.0+ 棄用無括號鏈式三元

// 正確寫法:必須加括號
$vehicle = ($arg == 'B') ? 'bus' : 
           (($arg == 'A') ? 'airplane' : 
           (($arg == 'T') ? 'train' : 
           (($arg == 'C') ? 'car' : 'feet')));
// PHP 8.0+ 建議:使用 match 表達式
$vehicle = match($arg) {
    'B' => 'bus',
    'A' => 'airplane',
    'T' => 'train',
    'C' => 'car',
    default => 'feet',
};
?>

五、switch 比較語義

<?php
// switch 使用 == (鬆散比較),而非 ===
$value = '0abc';

switch ($value) {
    case 0:
        echo "匹配 0\n";      // 會執行!'0abc' == 0 為 true
        break;
    case '0abc':
        echo "匹配 '0abc'\n";
        break;
}

// 解決方案:強制類型或使用 match (PHP 8.0+)
switch (true) {
    case $value === 0:
    case $value === '0abc':
        // ...
}

// match 使用 === (嚴格比較)
$result = match($value) {
    0 => 'int zero',
    '0abc' => 'string 0abc',
    default => 'other',
};
?>

六、對象比較規則

<?php
class A { public $x = 1; }
class B { public $x = 1; }

$a1 = new A();
$a2 = new A();
$a3 = $a1;
$b = new B();

var_dump($a1 == $a2);  // true (同類、屬性相同)
var_dump($a1 === $a2); // false (不同實例)
var_dump($a1 === $a3); // true (同一實例)
var_dump($a1 == $b);   // false (不同類)
var_dump($a1 === $b);  // false

// PHP 8.0+ 可實現 __equals() 自定義比較
class CustomCompare {
    public function __construct(public int $id) {}
    public function __equals(object $other): bool {
        return $other instanceof self && $other->id === $this->id;
    }
}
?>

七、對象轉數組完整規則

<?php
class Demo {
    public $pub = 'public';
    protected $pro = 'protected';
    private $pri = 'private';
    static $sta = 'static';
    const CONST = 'const';
}

$obj = new Demo();
$arr = (array) $obj;

print_r($arr);
/*
Array
(
    [pub] => public
    [*pro] => protected     // 前綴 * + \0
    [Demopri] => private    // 類名 + \0
)
*/

// 關鍵點:
// 1. 靜態屬性、常量、方法被丟棄
// 2. protected 前綴: \0*\0
// 3. private 前綴: \0類名\0
// 4. 這些特殊字符不可見,需用 var_dump/strlen 觀察
?>

八、最佳實踐清單

場景推薦做法避免
判斷變量是否有值isset($var) || $var !== nullempty($var) (會吞掉 '0')
判斷數組非空$arr !== [] 或 count($arr) > 0empty($arr) (同樣問題)
比較數值$a === $b (嚴格)== (類型轉換陷阱)
浮點數比較abs($a - $b) < 0.0001== / ===
字符串比較strcmp($a, $b) === 0== (數字字符串陷阱)
邏輯組合$a && $b / $a || $band / or (優先級陷阱)
多條件分支match (PHP 8.0+) / switch(true)switch($var) 鬆散比較
默認值賦值$val = $input ?? 'default'$val = $input ?: 'default' (0 會被覆蓋)

總結

PHP 比較運算的核心矛盾在於弱類型自動轉換與開發者心理模型的偏差。黃金法則:默認使用 === 和 !==、邏輯用 && ||、分支用 match、浮點用 epsilon 比較、字符串用 strcmp、默認值用 ??。只有在明確需要類型轉換語義時(如 HTTP 參數解析),才有意識地使用鬆散比較,並添加註釋說明意圖。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved