Home
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

4

Local AI

--

Uedu Code

--

Taipei AQI 57 · Taichung AQI 74 · Tainan AQI 63 · Kaohsiung AQI 64
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 09

字串操作

單雙引號、串接、中文字串的長度、搜尋取代、切割合併、格式化與正規表達式
Interaction實作 30 minute(s)

Learning Objectives

  • 分辨單引號、雙引號、heredoc、nowdoc,在雙引號字串裡用 {$arr['k']} 插入陣列元素
  • 說明中文字在 UTF-8 佔 3 個位元組,處理中文時改用 mb_strlen、mb_substr
  • 用 strpos 搜尋時以 !== false 判斷,並使用 str_contains、str_starts_with、str_ends_with
  • 使用 str_replace、trim、explode、implode、str_pad、sprintf、number_format 整理與格式化字串
  • 用 preg_match、preg_match_all、preg_replace、preg_split 處理有規則的文字,中文要加 u 修飾字

網頁裡幾乎都是字串

PHP 最常做的事就是產生文字:HTML、表單送來的姓名與留言、要寫進檔案的紀錄、要寄出的通知。 使用者的輸入一律是字串,前後可能有多餘的空白、格式可能不對;要顯示的數字需要加上千分位、補零、固定小數位數。 這一課整理處理字串的常用工具:

需求 函式
長度、取一段 strlen、mb_strlen、substr、mb_substr
搜尋 strpos、str_contains、str_starts_with、str_ends_with
取代、去空白、大小寫 str_replace、trim、strtoupper、ucfirst
切割與合併 explode、implode、str_split
格式化 sprintf、printf、number_format、str_pad
有規則的文字 preg_match、preg_replace、preg_split

PHP 的字串其實是一串位元組(byte),處理中文時要特別注意,本課會一再看到這一點。

四種寫字串的方式

寫法 跳脫字元(\n、\t…) 變數插值 用途
'單引號' 只認得 \' 和 \\ 不處理 固定的文字
"雙引號" 處理 處理 要插入變數、要換行
heredoc <<<END … END 處理 處理 很多行的文字、HTML
nowdoc <<<'END' … END 不處理 不處理 很多行、要原樣保留

雙引號裡插入變數的寫法:

"Hi $name"                  // 簡單的變數
"{$name}你好"               // 變數後面緊接中文或字母時,用大括號標出範圍
"{$item['title']}"          // 陣列元素:用大括號,鍵照常加引號
"{$user->city}"             // 物件的屬性(物件在第 14 課介紹)

"${name}" 這種寫法在 PHP 8.2 起已棄用(deprecated),請用 {$name}。 字串的串接用 .(不是 +),$s .= 'x' 把 'x' 接到 $s 後面。

單雙引號、插值與 heredoc

第 5 行用單引號,$name 和 \n 都原樣印出;第 6 行換成雙引號,就變成 Amy 和換行。第 16 行的 heredoc 結尾標記 HTML; 前面有 4 個空白,PHP 會把每一行開頭同樣的 4 個空白去掉(PHP 7.3 起)。

中文字串的長度與擷取

網頁一律使用 UTF-8 編碼:英文字母、數字佔 1 個位元組,中文字佔 3 個位元組(少數罕用字 4 個)。 PHP 傳統的字串函式都以位元組計算,所以處理中文會出錯:

以位元組計算 以字計算(多位元組,mb_)
strlen('程式設計') 是 12 mb_strlen('程式設計') 是 4
substr($s, 0, 4) 切到第二個字的一半,產生亂碼 mb_substr($s, 0, 2) 取前兩個字
strtoupper 只轉換英文字母 mb_strtoupper 也處理其他語言的字母

驗證「姓名最多 20 個字」要用 mb_strlen;擷取標題前 30 個字當摘要要用 mb_substr。 substr($s, -5) 的負數表示從後面數過來,mb_substr 也一樣。

strlen 與 mb_strlen、substr 與 mb_substr

第 12 行用 substr 取「程式設計」的前 4 個位元組:第一個字 3 個位元組,再加上第二個字的第 1 個位元組,於是 $broken 的結尾是半個字,左邊的變數區顯示成 �。bin2hex 把每個位元組印成兩位十六進位數字,可以看到確實只有 4 個位元組。

想一想:引號與長度

1.

下面的程式會印出什麼?

<?php
$n = 3;
echo 'n=$n', " n=$n", " {$n}0";
2.

下面的程式會印出什麼?

<?php
echo strlen('臺北101'), ' ', mb_strlen('臺北101');
第 1 次作答

搜尋:0 和 false 不一樣

strpos($s, $find) 回傳 $find 第一次出現的位置(從 0 起算),找不到回傳 false。 問題是:如果在開頭找到,位置是 0,而 0 當成條件是 false(第 05 課):

if (strpos('php is fun', 'php')) { … }            // 錯:在位置 0 找到,卻被當成沒找到
if (strpos('php is fun', 'php') !== false) { … }  // 對:用嚴格比較區分 0 和 false

只想知道「有沒有」的時候,PHP 8.0 起可以直接用 str_contains($s, $find), 另外有 str_starts_with、str_ends_with,都回傳 true 或 false,不會有這個問題。

其他常用的函式:

函式 作用
str_replace($find, $replace, $s, $count) 取代全部;第四個參數(可省略)記下取代了幾次。不分大小寫用 str_ireplace
explode(',', $s) 依分隔字串切成陣列
implode('、', $arr) 用分隔字串把陣列接成一個字串
str_split($s, 4) 每 4 個位元組切一段(中文用 mb_str_split)
str_pad($s, 長度, 補的字, 方向) 補到指定長度,STR_PAD_LEFT 補在左邊
str_repeat('=', 5) 重複 5 次

格式化:sprintf、printf、number_format

sprintf(格式, 值1, 值2, …) 依照格式字串產生一個字串;printf 用法相同,但直接印出。 格式字串裡的 % 開頭是佔位符,依序換成後面的值:

佔位符 意思 範例 結果
%s 字串 sprintf('%s 分', 'A') A 分
%d 整數(小數直接捨去) sprintf('%d', 95.8) 95
%.2f 浮點數,小數 2 位(四捨五入) sprintf('%.2f', 87.456) 87.46
%05d 寬度 5,不足補 0 sprintf('%05d', 42) 00042
%8s、%-8s 寬度 8,靠右/靠左(-) sprintf('[%-6s]', 'ab') [ab ]
%% 印出 % 本身 sprintf('%d%%', 95) 95%

寬度以位元組計算,中文對不齊,表格對齊請用英數。 number_format(1234567.891) 加上千分位並四捨五入到整數(1,234,568),第二個參數指定小數位數。

比較字串:== 和 === 都區分大小寫;不分大小寫用 strcasecmp。strcmp($a, $b) 依字典順序比較, $a 在前回傳負數、相同回傳 0、在後回傳正數(PHP 8.2 起固定是 -1、0、1),很適合拿來排序。 注意 == 比較兩個看起來像數字的字串時,會當成數字比較:'10' == '1e1' 是 true,要精確比對字串請用 ===。

sprintf、printf、number_format 與字串比較

對照每一行的格式字串與輸出區的結果:%.2f 四捨五入到兩位、%05d 補零、%-8s 靠左。最後三行比較字串,注意 '10' == '1e1' 是 true:兩個字串都像數字,== 把它們當成 10 來比較。

想一想:搜尋、切割與格式化

1.

下面的程式會印出什麼?

<?php
$s = 'apple';
if (strpos($s, 'a')) {
    echo 'yes';
} else {
    echo 'no';
}
2.

下面的程式會印出什麼?

<?php
echo count(explode(',', 'a,,b,')), ' ', implode('+', explode(' ', 'x y z'));
3.

下面的程式會印出什麼?

<?php
printf('%05.1f|%-4s|%3d', 3.14159, 'ab', 7);
第 1 次作答

正規表達式入門

要找的不是固定的文字,而是「有規則的文字」(日期、Email、訂單編號)時,用正規表達式(regular expression)描述規則。 PHP 的寫法是把規則放在兩個斜線之間:'/規則/修飾字'。

規則 意思
\d 一個數字;\s 空白;\w 英文字母、數字或底線
[A-Z]、[a-z0-9_] 方括號裡任一個字元
. 任一個字元
{4}、{2,4}、+、*、? 重複 4 次、2 到 4 次、1 次以上、0 次以上、0 或 1 次
^、$ 字串的開頭、結尾(整個字串都要符合時使用)
( … ) 群組:把符合的部分另外擷取出來
函式 作用
preg_match($re, $s, $m) 找第一個符合的;找到回傳 1、沒找到 0;$m[0] 是整段,$m[1] 起是各個群組
preg_match_all($re, $s, $all) 找全部,回傳個數;$all[0] 是所有符合的字串
preg_replace($re, $replace, $s) 取代;$1、$2 代表群組的內容
preg_split($re, $s) 依符合的位置切成陣列

處理中文一定要加 u 修飾字(/…/u):沒有 u 時,. 代表一個位元組,一個中文字會被當成 3 個字元。 規則寫在單引號字串裡,\d 這類反斜線就不必再跳脫。

擷取日期與訂單編號、取代、切割

第 4 行的規則有三個群組,右邊的 $m 會依序放整段日期、年、月、日。第 12 行比較有沒有 u 修飾字:「密碼」兩個字,加 u 換成 2 個星號,沒加 u 換成 6 個(每個位元組一個)。

想一想:正規表達式

1.

下面的程式會印出什麼?

<?php
preg_match('/(\w+)@(\w+)\.com/', '聯絡:[email protected]', $m);
echo $m[2], ' ', count($m);
第 1 次作答

動手做:遮蔽個人資料

$raw 是從表單收到的名單,每行是「姓名,手機」,前後可能有多餘的空白。請逐行印出遮蔽後的資料:

  • 姓名只保留第一個字,其餘每個字換成 *(「王小明」變成「王**」)
  • 手機保留前 4 碼與後 3 碼,中間換成 ***
  • 每行前面加上編號,格式是 1. 王** 0912***678
你的輸出
預期輸出

重點回顧

  • 單引號原樣輸出;雙引號處理 \n 等跳脫字元與變數插值;heredoc 像雙引號、nowdoc 像單引號
  • 插入陣列元素或變數後面緊接文字時,寫成 {$item['title']}、{$name};串接用 . 與 .=
  • UTF-8 的中文字佔 3 個位元組:計算字數用 mb_strlen、擷取用 mb_substr,substr 可能切出亂碼
  • strpos 在開頭找到時回傳 0,判斷要用 !== false;只問有沒有就用 str_contains(PHP 8.0)
  • str_replace、trim、explode、implode、str_pad、str_repeat 回傳新字串,原字串不變
  • sprintf/printf:%s、%d、%.2f、%05d、%-10s、%%;number_format 加千分位
  • == 比較兩個數字形式的字串時會當成數字;精確比對用 ===,不分大小寫用 strcasecmp
  • 正規表達式:preg_match 擷取、preg_match_all 找全部、preg_replace 取代、preg_split 切割;有中文就加 u
這一課完成了!
Next lesson:日期和時間
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。