Learning Objectives
- 建立索引陣列與關聯陣列,用鍵讀取、新增、修改與刪除元素
- 說明沒有指定鍵時 PHP 怎麼自動編號,以及字串鍵 "10" 會變成整數鍵 10
- 說明陣列是「值」:指派與傳給函式時會複製一份,要共用同一份才用參照 &
- 用多維陣列表示「一群有相同結構的資料」,用 foreach 走訪
- 使用 array_map、array_filter、array_reduce、排序函式與 in_array、array_search,並注意 array_filter 會保留原本的鍵
一個變數裝很多資料
一個班級有 30 位學生,總不能寫 $score1、$score2…$score30。陣列(array)讓一個變數裝一整組資料,
再用鍵(key)取出其中一個值(value):
$scores = [80, 92, 75]; // 索引陣列:鍵自動是 0、1、2
echo $scores[1]; // 92
$price = ['蘋果' => 30, '香蕉' => 15]; // 關聯陣列:自己指定字串當鍵
echo $price['香蕉']; // 15
很多語言把這兩種分成「串列」和「字典」兩個型別;PHP 只有一種陣列,它是一個有順序的鍵值對照表:
- 鍵只能是整數或字串,值可以是任何型別(包括另一個陣列)
- 元素保持加入的順序,
foreach依這個順序走訪 [1, 2, 3]其實就是[0 => 1, 1 => 2, 2 => 3]
本課的逐步執行動畫,左邊是變數,右邊是陣列的內容:索引陣列畫成一排格子(格子上方的小字是鍵), 關聯陣列畫成「鍵 => 值」的表格。
建立、讀取、新增、修改、刪除
注意第 5 行的 $fruits[] = …:中括號裡沒有鍵,表示加到最後面。第 12 行的 unset 刪掉一個元素之後,其他元素的鍵不會重新編號。foreach ($price as $name => $p) 每一圈把一個鍵放進 $name、值放進 $p。
中文字串裡的變數要加大括號
上面第 14 行寫成 "{$name}:{$p} 元",如果省略大括號寫成 "$name:$p 元",PHP 會印出警告:
Warning: Undefined variable $name: in /var/www/html/index.php on line 14
PHP 的變數名稱可以包含英文字母、數字、底線,也可以包含中文等非英文字元。所以 $name: 連同全形冒號被當成一個變數名稱,
這個變數不存在,於是印出警告、當成空字串。變數後面緊接著中文時,一律用 {$變數} 標出名稱的範圍。
沒有寫鍵時怎麼編號
$a[] = … 的鍵是「目前最大的整數鍵 + 1」(陣列是空的就從 0 開始)。第 8 行用字串 '10' 當鍵,PHP 會把長得像十進位整數的字串轉成整數鍵 10,所以第 9 行接著編到 11。
想一想:鍵與自動編號
下面的程式會印出什麼?
<?php
$a = [3 => 'x'];
$a[] = 'y';
$a[1] = 'z';
$a[] = 'w';
echo implode(',', array_keys($a));
下面的程式會印出什麼?
<?php
$a = ['1' => 'a', 1 => 'b', true => 'c'];
echo count($a);
陣列是「值」,不是共用的物件
在 PHP 裡,$b = $a; 會讓 $b 得到 $a 的一份複本,之後改 $b 不會影響 $a。
把陣列傳給函式也一樣:函式拿到的是複本,在函式裡修改,外面的陣列不變,要把結果 return 回來。
這和 JavaScript、Python、Java 不同(它們的陣列、串列是物件,指派只是多一個名字指向同一個物件)。 PHP 的物件才是共用的,第 14 課會講到。
如果真的要兩個變數共用同一個陣列,用參照(reference)$ref = &$a;:之後透過 $ref 修改,$a 也看得到。
指派與傳入函式都會複製
看右邊:第 4 行執行完出現第二個陣列,$a 和 $b 各指向自己的一份。呼叫 addItem($items) 時,函式的 $list 又是另一份複本。最後第 16 行 $ref = &$a; 沒有複製,兩個變數指向同一個陣列。
多維陣列
陣列的值可以是另一個陣列,用來表示「一群有相同結構的資料」:每位學生是一個關聯陣列,全部學生放在一個索引陣列裡。
讀取時一層一層寫鍵:$students[2]['scores'][1] 是「第 3 位學生的第 2 個分數」。
| 寫法 | 意思 |
|---|---|
$students[0] |
第一位學生(一個關聯陣列) |
$students[0]['name'] |
第一位學生的姓名 |
$students[0]['scores'][1] |
第一位學生的第二個分數 |
$students[] = [...] |
加入一位新學生 |
用多維陣列計算每位學生的平均
右邊的外層陣列有 3 個格子,每個格子指向一位學生的關聯陣列,scores 再指向一個分數陣列。foreach 每一圈的 $s 是一位學生的複本,所以在迴圈裡改 $s 不會改到 $students。
常用的陣列函式
PHP 內建很多陣列函式,常用的有:
| 函式 | 作用 | 原陣列 |
|---|---|---|
count($a) |
元素個數 | 不變 |
array_sum($a)、max($a)、min($a) |
加總、最大、最小 | 不變 |
array_map(fn, $a) |
每個元素經過函式轉換,回傳新陣列 | 不變 |
array_filter($a, fn) |
留下函式回傳 true 的元素,保留原本的鍵 | 不變 |
array_reduce($a, fn, 初始值) |
把所有元素累積成一個值 | 不變 |
in_array($x, $a)、array_search($x, $a) |
有沒有這個值、它的鍵 | 不變 |
array_keys($a)、array_values($a) |
取出所有鍵、所有值(重新編號) | 不變 |
implode(',', $a)、explode(',', $s) |
陣列與字串互轉 | 不變 |
sort、rsort |
依值排序,鍵重新編號 | 直接修改 |
asort、arsort |
依值排序,保留鍵 | 直接修改 |
ksort、krsort |
依鍵排序 | 直接修改 |
usort($a, fn) |
用自己的比較函式排序 | 直接修改 |
排序函式直接修改傳進去的陣列(參數是參照),回傳值只是 true,所以不要寫 $a = sort($a);。
fn($n) => $n * $n 是箭頭函式,第 07 課介紹過;<=> 是太空船運算子,左邊小回傳 -1、相等 0、左邊大 1,很適合寫在比較函式裡。
map、filter、reduce 與排序
array_map、array_filter、array_reduce 會對每個元素呼叫一次你給的函式,動畫裡可以看到每次呼叫的參數與回傳值。注意第 7 行印出的 $evens:鍵是 2 和 5,array_filter 保留原本的鍵,不會重新編號。
搜尋、解構與展開
第 5 行示範 in_array 的陷阱:預設用寬鬆比較(==),字串 '0' 和整數 0 被當成相等。加上第三個參數 true 改用嚴格比較(===),型別也要相同。第 7、8 行是解構:一次把陣列的元素指派給多個變數。
想一想:複製、篩選與排序
下面的程式會印出什麼?
<?php
function double(array $a) {
foreach ($a as $k => $v) {
$a[$k] = $v * 2;
}
}
$n = [1, 2, 3];
double($n);
echo implode(' ', $n);
下面的程式會印出什麼?
<?php
$a = array_filter([10, 15, 20, 25], fn($x) => $x > 12);
echo $a[0] ?? '沒有', ' ', count($a);
下面的程式會印出什麼?
<?php
$s = ['b' => 2, 'a' => 3, 'c' => 1];
sort($s);
echo implode(',', array_keys($s));
動手做:列出及格的學生
每位學生有兩個分數。請算出每個人的平均,只列出平均 60 分以上的學生,依平均由高到低排列,每行印出「姓名:平均」。平均用 round(…, 1) 四捨五入到小數第一位。
重點回顧
- PHP 只有一種陣列:有順序的鍵值對照表。索引陣列的鍵自動是 0、1、2…,關聯陣列自己指定字串鍵
$a[] = 值加到最後,鍵是「目前最大的整數鍵 + 1」;unset($a[鍵])刪除,其他鍵不會重新編號- 長得像十進位整數的字串鍵(
'10')會變成整數鍵;true當鍵會變成 1 - 陣列是值:指派、傳給函式都會複製一份。要共用同一份,用參照
& - 字串裡的變數後面接中文時,寫成
{$變數} array_map、array_filter、array_reduce回傳新陣列,原陣列不變;array_filter保留原本的鍵sort會重新編號鍵,asort/arsort保留鍵,ksort依鍵排序;排序函式直接修改傳入的陣列in_array、array_search預設用寬鬆比較,需要時加上第三個參數true