限定符決定「匹配多少次」,定位符決定「在哪裡匹配」。二者結合,能精準描述字符串模式。本文系統講解貪婪/惰性/佔有優先三種匹配模式、各類限定符語法、邊界定位符,並以 HTML 標籤匹配為例演示貪婪與惰性的關鍵差異。
限定符完整對照表
| 語法 | 名稱 | 匹配次數 | 等價寫法 | 貪婪性 |
* | 零或多次 | 0~∞ | {0,} | 貪婪 |
+ | 一次或多次 | 1~∞ | {1,} | 貪婪 |
? | 零或一次 | 0~1 | {0,1} | 貪婪 |
{n} | 固定 n 次 | n | - | 貪婪 |
{n,} | 至少 n 次 | n~∞ | - | 貪婪 |
{n,m} | n 到 m 次 | n~m | - | 貪婪 |
*? | 零或多次 (惰性) | 0~∞ | {0,}? | 惰性 |
+? | 一次或多次 (惰性) | 1~∞ | {1,}? | 惰性 |
?? | 零或一次 (惰性) | 0~1 | {0,1}? | 惰性 |
{n,m}? | n 到 m 次 (惰性) | n~m | - | 惰性 |
章節編號匹配實例
<?php
// 匹配 "Chapter 1" 到 "Chapter 999"
$patterns = [
'/Chapter [1-9][0-9]*/' => '任意位數 (貪婪)',
'/Chapter [0-9]{1,3}/' => '1-3 位數字 (含 0 開頭)',
'/Chapter [1-9][0-9]{0,2}/' => '1-3 位,不以 0 開頭 (推薦)',
'/Chapter [1-9][0-9]?/' => '1-2 位,不以 0 開頭',
];
$test = "Chapter 1\nChapter 12\nChapter 123\nChapter 05\nChapter 0";
foreach ($patterns as $pat => $desc) {
echo "$desc: $pat\n";
preg_match_all($pat, $test, $m);
print_r($m[0]);
echo "\n";
}
貪婪 vs 惰性:HTML 標籤匹配經典案例
<?php
$html = '<H1>Chapter 1 – Introduction</H1><P>Content</P>';
// 貪婪模式:匹配盡可能多
$greedy = '/<.*>/';
preg_match($greedy, $html, $m);
echo "貪婪: {$m[0]}\n";
// 輸出: <H1>Chapter 1 – Introduction</H1><P>Content</P>
// 惰性模式:匹配盡可能少
$lazy = '/<.*?>/';
preg_match($lazy, $html, $m);
echo "惰性: {$m[0]}\n";
// 輸出: <H1>
// 佔有優先模式 (PHP/PCRE 不直接支援,需用原子組)
$possessive = '/<.*+>/'; // 錯誤語法
// 正確寫法:原子組 (?>.*)
$atomic = '/<(?>.)*>/'; // 實際不常用
匹配機制對比
| 模式 | 行為 | 回溯 | 適用場景 |
| 貪婪 | 先匹配最長,失敗時回溯 | 是 | 大多數情況,性能較好 |
| 惰性 | 先匹配最短,成功時擴展 | 是 | 分隔符匹配、標籤內容提取 |
| 佔有優先 | 匹配最長,不回溯 | 否 | 避免災難性回溯,需原子組支援 |
定位符(錨點)
| 符號 | 含義 | 多行模式 (m) 影響 |
^ | 字符串開始 / 行首 | 啟用後匹配每行開頭 |
$ | 字符串結束 / 行尾 | 啟用後匹配每行結尾 |
\A | 絕對字符串開始 | 不受 m 影響 |
\Z | 絕對字符串結束 (或末尾換行前) | 不受 m 影響 |
\z | 絕對字符串結束 | 不受 m 影響 |
\b | 單詞邊界 | - |
\B | 非單詞邊界 | - |
\G | 上次匹配結束位置 | 配合 \G 連續掃描 |
實戰:提取 HTML 標籤內容
<?php
$html = '<div class="box"><p>Hello</p><span>World</span></div>';
// 1. 提取第一個 div 完整內容 (惰性)
preg_match('/<div[^>]*>(.*?)<\/div>/is', $html, $m);
echo "內容: {$m[1]}\n";
// 輸出: <p>Hello</p><span>World</span>
// 2. 提取所有 p 標籤文本
preg_match_all('/<p>(.*?)<\/p>/is', $html, $m);
print_r($m[1]);
// Array ( [0] => Hello )
// 3. 驗證完整 HTML 結構 (簡易)
$valid = '/^<(\w+)[^>]*>.*?<\/\1>$/s'; // \1 後向引用標籤名
常見錯誤與避坑
.* 匹配換行需加 s 修飾符 (PCRE_DOTALL)
{n,m} 中間不能有空格:{1, 3} 錯誤
- 惰性量詞 `*?` 在某些引擎中可能比貪婪慢(需更多擴展嘗試)
- 邊界 `\b` 依賴 `\w` 定義(字母數字下劃線),中文邊界需用 Unicode 屬性 `\p{Han}`
總結
限定符控制數量,定位符控制位置。貪婪是默認、惰性按需、佔有優先防回溯。實戰中:能用具體字符類別不用點號,能用錨點定位不用模糊匹配,能用惰性分隔符不用貪婪跨界。