几乎任何一门编程语言都有数组,它是计算机编程中最常见的数据结构。因为数组是编程语言自带的,通常效率很高,能够满足不同需求的数据存储。让我们来学习 PHP 中数组的工作原理,以及使用的场景。
PHP 中数组的定义
首先我们了解下,数组的标准定义:数组,是有序的元素序列,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量。
PHP 中的数组实际上是一个有序映射。映射是把 values 关联到 keys 的类型。这个类型在很多方面做了优化,因此可以把它当成是真正的数组,或列表(向量),散列表(是映射的一种实现),字典,集合,栈,队列以及更多可能性。另外,数组元素的值也可以是另一个数组,树形结构和多维数组都是可以的。
使用数组
PHP 中的数组很强大。有多种创建和存取元素的方法,也可以通过不同的方式对数组进行查找和排序。同时还提供了一系列函数,方便对数组做各种各样的操作。
创建数组
最简单的方式是使用 array() 或 [] 语言结构来创建一个数组变量:
<?php
$array = array();
$array = [];
使用这种方式创建数组,你将得到单元数量为 0 的空数组。可以通过内建的 count
方法来验证这一点:
$ php -r "echo count(array());"
0
另外,创建数组的时候还可以直接放入一组元素:
<?php
$array = array(1, 2, 3, 4, 5);
$array = [1, 2, 3, 4, 5];
echo count($array); // 5
在 PHP 中,数组中的元素可以是任意类型的值,这个和很多编程语言不同,如下所示:
<?php
$array = [1, 'apple', true, null];
可以使用 is_array
函数来判断一个对下是否是数组,如下所示:
<?php
$number = 3;
$array = [8, 16, 32];
var_dump(is_array($number)); // bool(false)
var_dump(is_array($array)); // bool(true)
我们讨论了创建数组的几种方式,其中从 5.4 起可以使用短数组定义语法,用 [] 代替 array()。
读取数组
在一条赋值语句中,可以使用 [] 操作符将数据赋值给数组,比如下面的循环,将 1~100 的数字赋值给一个数组:
<?php
$numbers = [];
for ($i = 0; $i < 100; $i++) {
$numbers[$i] = $i + 1;
}
还可使用 [] 操作符读取数组中的元素,如下所示:
<?php
$array = [1, 2, 3, 4, 5];
$sum = $array[0] + $array[1] + $array[2] + $array[3] + $array[4];
echo $sum; // 15
如果要依次读取数组中的所有元素,使用 for
循环会更加简单:
<?php
$numbers = [1, 3, 5, 9, 15, 18, 26];
$sum = 0;
for ($i = 0; $i < count($numbers); $i++) {
$sum += $numbers[$i];
}
echo $sum; // 77
其中,我们使用 count
函数来控制循环次数,count
可以计算出数组中的单元数目,从而确保循环遍历了数组中的所有元素。
由字符串生成数组
使用字符串函数 explode
可以用一个字符串分隔另一个字符串,该方法通过一些常见的分隔符,比如分隔单词的空格,将一个字符串分成几部分,并将每部分作为一个元素保存到一个新建的数组中。
下面一个小程序演示了 explode
方法的工作原理:
<?php
$sentence = 'Hi zhanbai, I like this book!';
$words = explode(' ', $sentence);
for ($i = 0; $i < count($words); $i++) {
echo 'word ' . $i . ': ' . $words[$i] . PHP_EOL;
}
输出:
word 0: Hi
word 1: zhanbai,
word 2: I
word 3: like
word 4: this
word 5: book!
另外,str_split
函数也可以将字符串转换为数组,不同的是它是通过设定元素的长度进行分割。preg_split
函数可以通过一个正则表达式分隔字符串为数组。
对数组的整体性操作
有些操作是将数组作为一个整体进行的。首先,可以将一个数组赋给另一个数组:
<?php
$numbers = [];
for ($i = 0; $i < 10; $i++) {
$numbers[$i] = $i + 1;
}
$same_numbers = $numbers;
当把一个数组赋值给另一个数组时,相当于创建了一个新的数组,当你修改了赋值数组的值,被赋值的数组并不会改变。下面的代码展示了这种情况:
<?php
$numbers = [];
for ($i = 0; $i < 10; $i++) {
$numbers[$i] = $i + 1;
}
$same_numbers = $numbers;
$numbers[0] = 20;
echo $same_numbers[0]; // 1
如果我们想要被赋值的数组随着原数组的改变而改变,可以使用引用赋值的方式,简单的将一个 &
符号加到要赋值的数组前(源数组):
<?php
$numbers = [];
for ($i = 0; $i < 10; $i++) {
$numbers[$i] = $i + 1;
}
$same_numbers = &$numbers;
$numbers[0] = 20;
echo $same_numbers[0]; // 20
另一个将数组作为整体操作的是 print_r
函数,用它可以打印数组中的元素,比如:
<?php
$numbers = [1, 2, 3, 4, 5];
print_r($numbers);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
这样的输出,以易于理解的格式打印数组,当你想要看看一个数组列表时,很方便。
存取函数
PHP 提供了一组用来访问数组元素的函数,这些函数返回目标数组的某种变体。
查找元素
array_search
在数组中搜索给定的值,如果成功则返回首个相应的键名(索引)。下面是一个例子:
<?php
$names = ['zhangsan', 'lisi', 'wangwu', 'lisi', 'zhaoliu'];
fwrite(STDOUT, 'Enter a name to search for: ');
$name = trim(fgets(STDIN));
$position = array_search($name, $names);
if ($position !== false) {
echo 'Found ' + $name + ' at position ' + $position . PHP_EOL;
} else {
echo $name . ' not found in array.' . PHP_EOL;
}
执行该程序,并且输入 lisi
:
$ php a.php
Enter a name to search for: lisi
Found lisi at position 1
如果输入 qianqi
:
Enter a name to search for: qianqi
qianqi not found in array.
如果数组中包含多个相同的元素,array_search
函数总是返回第一个与参数相同的元素的索引。其它相关的函数有:
- array_keys — 返回数组中部分的或所有的键名
- array_key_exists — 检查数组里是否有指定的键名或索引
- array_key_first — 返回数组中第一个元素的键名或索引
- array_key_last — 返回数组中最后一个元素的键名或索引
数组的字符串表示
implode
函数可以将一个一维数组的值转化为字符串,各元素之间用自定义的分隔符隔开。比如下面的例子:
<?php
$names = ['zhangsan', 'lisi', 'wangwu', 'lisi', 'zhaoliu'];
$names_str = implode(',', $names);
echo $names_str; // zhangsan,lisi,wangwu,lisi,zhaoliu
其中分隔符 ,
是自定义的分隔字符串,其中也可以不填,默认为空字符串。
由已有数组创建新数组
array_merge
和 array_slice
函数可以通过已有数组创建新的数组。array_merge
可以合并一个或多个数组创建一个新数组,array_slice
可以从数组中取出一段创建一个新数组。
array_merge
的工作原理:将一个或多个数组的单元合并起来,一个数组中的值附加在前一个数组的后面。返回作为结果的数组。如果输入的数组中有相同的字符串键名,则该键名后面的值将覆盖前一个值。然而,如果数组包含数字键名,后面的值将不会覆盖原来的值,而是附加到后面。如果只给了一个数组并且该数组是数字索引的,则键名会以连续方式重新索引。请看下面的例子:
<?php
$names = ['zhangsan', 'lisi', 'wangwu', 'zhaoliu'];
$another_names = ['qianqi', 'sunba', 'lisi', 'zhoujiu'];
$new_names = array_merge($names, $another_names);
print_r($new_names);
输出结果:
Array
(
[0] => zhangsan
[1] => lisi
[2] => wangwu
[3] => zhaoliu
[4] => qianqi
[5] => sunba
[6] => lisi
[7] => zhoujiu
)
array_slice
第一个参数是被截取的数组,第二个参数 offset 可以设置偏移量开始的位置,如果为正数则从距离首端这么远开始,为负数则从距离末端这么远开始。第三个参数,决定了元素的数量,为正,则序列中将具有这么多的单元。为负,则序列将终止在距离数组末端这么远的地方。如果省略,则序列将从 offset 开始一直到 array 的末端。 看下面的例子:
<?php
$names = ['zhangsan', 'lisi', 'wangwu', 'zhaoliu'];
$new_names = array_slice($names, 1, 2);
print_r($new_names);
输出:
Array
(
[0] => lisi
[1] => wangwu
)
其它相关的函数有:
- array_merge_recursive — 递归地合并一个或多个数组
- array_replace — 使用传递的数组替换第一个数组的元素
- array_combine — 创建一个数组,用一个数组的值作为其键名,另一个数组的值作为其值
- array_splice — 去掉数组中的某一部分并用其它值取代
- array_chunk — 将一个数组分割成多个
可变函数
为数组添加元素
有两个方法可以为数组添加元素:array_push
和 array_unshift
。array_push
将一个或多个单元压入数组的末尾(入栈):
<?php
$nums = [1, 2, 3, 4, 5];
print_r($nums);
array_push($nums, 6);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
)
也可以通过数组的长度为数据添加元素:
<?php
$nums = [1, 2, 3, 4, 5];
print_r($nums);
$nums[count($nums)] = 6;
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
)
和在数组的末尾添加元素比起来,在数组的开头添加元素更难。如果不利用数组提供的可变函数,则新的元素添加进来后,需要把后面每个元素都相应的向后移动一个位置,下面代码展示了这一过程:
<?php
$nums = [2, 3, 4, 5];
$new_num = 1;
$length = count($nums);
for ($i = $length; $i >= 1; --$i) {
$nums[$i] = $nums[$i - 1];
}
$nums[0] = $new_num;
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
随着数组中存储的元素越来越多,上述代码将会变得越来越低效。
array_unshift
方法可以将元素添加在数组的开头,下述代码展示该方法的用法:
<?php
$nums = [2, 3, 4, 5];
print_r($nums);
$new_num = 1;
array_unshift($nums, $new_num);
print_r($nums);
$nums = [3, 4, 5];
array_unshift($nums, 1, 2);
print_r($nums);
输出:
Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 5
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
其中,我们可以看到,可以通过一次调用,为数组添加多个元素。
从数组中删除元素
使用 array_pop
方法,可以弹出数组最后一个单元(出栈):
<?php
$nums = [1, 2, 3, 4, 5, 6];
array_pop($nums);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
如果没有可变函数,从数组中删除第一个元素需要将后续元素各自向前移动一个位置,和在数组开头添加一个元素一样的低效:
<?php
$nums = [8, 1, 2, 3, 4, 5];
print_r($nums);
$length = count($nums);
for ($i = 0; $i < $length; $i++) {
$nums[$i] = $nums[$i + 1];
}
print_r($nums);
输出:
Array
(
[0] => 8
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] =>
)
除了将后续元素前移一位,还多出了一个元素。
array_shift
方法,可以将数组开头的单元移出数组,下述代码展示了该方法的用法:
<?php
$nums = [8, 1, 2, 3, 4, 5];
array_shift($nums);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
)
返回数组不会多一个元素。array_pop
和 array_shift
都是将删掉的元素作为返回值,因此可以用一个变量来保存删除的元素:
<?php
$nums = [6, 1, 2, 3, 4, 5];
$first = array_shift($nums);
array_push($nums, $first);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
)
从数组中间位置添加和删除元素
删除数组中的第一个元素和在数组开头添加一个元素存在同样的问题,两种操作都需要将数组中剩余的元素向前或者向后移动位置,然而 array_splice
方法可以帮助我们执行其中任意一种操作。
使用 array_splice
方法,需要以下参数:
- 输入的数组
- 起始的位置(偏移量)
- 需要删除的元素的数量
- 想要添加的数组元素
可以看一个简单的例子,下面程序在数组的中间插入元素:
<?php
$nums = [1, 2, 3, 7, 8, 9];
$new_nums = [4, 5, 6];
array_splice($nums, 3, 0, $new_nums);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
[8] => 9
)
如果替换的元素只有一个单元,不需要定义成数组也可以。
下面使用 array_splice
从数组中删除元素:
<?php
$nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
array_splice($nums, 3, 3);
print_r($nums);
输出:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 7
[4] => 8
[5] => 9
)
为数组排序
PHP 有一些用来排序数组的函数,第一个方法 rsort
按照值对数组进行逆向排序(最高到最低),下面展示这个方法的使用:
<?php
$nums = [1, 2, 3, 4, 5];
rsort($nums);
print_r($nums);
输出:
Array
(
[0] => 5
[1] => 4
[2] => 3
[3] => 2
[4] => 1
)
sort
方法可以按照值对数组进行正向排序(最低到最高):
<?php
$names = ['apple', 'orange', 'banana', 'pear'];
sort($names);
print_r($names);
输出:
Array
(
[0] => apple
[1] => banana
[2] => orange
[3] => pear
)
其它还有许多对数组排序的函数,比如:
- array_multisort — 对多个数组或多维数组进行排序
- asort — 对数组进行排序并保持索引关系
- arsort — 对数组进行逆向排序并保持索引关系
- ksort — 对数组按照键名排序
- krsort — 对数组按照键名逆向排序
- natsort — 用“自然排序”算法对数组排序
- natcasesort — 用“自然排序”算法对数组进行不区分大小写字母的排序
- shuffle — 打乱数组
- uasort — 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联
- uksort — 使用用户自定义的比较函数对数组中的键名进行排序
- usort — 使用用户自定义的比较函数对数组中的值进行排序
迭代器
可在内部迭代自己的外部迭代器或类的接口。下面例子展示了使用 foreach
时,迭代器方法的调用:
<?php
class myIterator implements Iterator {
private $position = 0;
private $array = array(
'firstelement',
'secondelement',
'lastelement',
);
public function __construct() {
$this->position = 0;
}
function rewind() {
$this->position = 0;
}
function current() {
return $this->array[$this->position];
}
function key() {
return $this->position;
}
function next() {
++$this->position;
}
function valid() {
return isset($this->array[$this->position]);
}
}
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
输出:
int(0)
string(12) "firstelement"
int(1)
string(13) "secondelement"
int(2)
string(11) "lastelement"
聚合式的迭代器接口,创建外部迭代器的接口。下面例子展示了用法:
<?php
class myData implements IteratorAggregate {
public $property1 = "Public property one";
public $property2 = "Public property two";
public $property3 = "Public property three";
public function __construct() {
$this->property4 = "last property";
}
public function getIterator() {
return new ArrayIterator($this);
}
}
$obj = new myData;
foreach($obj as $key => $value) {
var_dump($key, $value);
echo "\n";
}
输出:
string(9) "property1"
string(19) "Public property one"
string(9) "property2"
string(19) "Public property two"
string(9) "property3"
string(21) "Public property three"
string(9) "property4"
string(13) "last property"
二维数组和多维数组
一个数组中的值可以是另一个数组,另一个数组的值也可以是一个数组。依照这种方式,我们可以创建二维或者三维数组。
创建二维数组
二维数组类似一种由行和列构成的数据表格。
在 PHP 中创建二维数组,需要先创建一个数组,然后让数组的每个元素也是一个数组。我们需要知道二维数组要包含多少行,有了这个信息,就可以创建一个 n 行 1 列的二维数组了:
<?php
$twod = [];
$rows = 5;
for ($i = 0; $i < $rows; $i++) {
$twod[$i] = [];
}
可以仅用一行代码就创建并使用一组初始值来初始化一个二维数组:
<?php
$grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
echo $grades[2][2]; // 显示 89
对于小规模的数据,这是创建二维数组最简单的方式。
处理二维数组的元素
处理二维数组中的元素,有两种最基本的方式:按列访问和按行访问。我们将使用前面创建的数组 $grades 来展示这两种方式的工作原理。
对于两种方式,我们均使用一组嵌入式的 for
循环。对于按列访问,外层循环对应行,内层循环对应列。以数组 $grades 为例,每一行对应一个学生的成绩记录。我们可以将该学生的所有成绩相加,然后除以科目数得到该学生的平均成绩。下面的代码展示了这一过程:
<?php
$grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
$total = 0;
$average = 0;
for ($row = 0; $row < count($grades); $row++) {
for ($col = 0; $col < count($grades[$row]); $col++) {
$total += $grades[$row][$col];
}
$average = $total / count($grades[$row]);
echo 'Student ' . ($row + 1) . ' average: ' . round($average, 2) . "\n";
$total = 0;
$average = 0;
}
输出:
Student 1 average: 81.33
Student 2 average: 79.67
Student 3 average: 91.33
对于按行访问,只需要稍微调整 for
循环的顺序,使外层循环对应列,内层循环对应行即可。下面的程序计算了一个学生各科的平均成绩:
<?php
$grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
$total = 0;
$average = 0;
for ($col = 0; $col < count($grades); $col++) {
for ($row = 0; $row < count($grades[$col]); $row++) {
$total += $grades[$row][$col];
}
$average = $total / count($grades[$col]);
echo 'Student ' . ($col+ 1) . ' average: ' . round($average, 2) . "\n";
$total = 0;
$average = 0;
}
输出:
Test 1 average: 85.33
Test 2 average: 84.33
Test 3 average: 82.67
参数不齐的数组
参差不齐的数组是指数组中每行的元素个数彼此不同。很多编程语言在处理这种参差不齐的数组时表现都不是很好,但是 PHP 却表现良好,因为每一行的长度是可以通过计算得到的。
假设数组 $grades 中,每个学生成绩记录的个数是不一样的,不用修改代码,依然可以正确计算出正确的平均分:
<?php
$grades = [[89, 77],[76, 82, 81],[91, 94, 89, 99]];
$total = 0;
$average = 0;
for ($row = 0; $row < count($grades); $row++) {
for ($col = 0; $col < count($grades[$row]); $col++) {
$total += $grades[$row][$col];
}
$average = $total / count($grades[$row]);
echo 'Student ' . ($row + 1) . ' average: ' . round($average, 2) . "\n";
$total = 0;
$average = 0;
}
输出:
Student 1 average: 83
Student 2 average: 79.67
Student 3 average: 93.25
练习
- 创建一个记录学生成绩的对象,提供一个添加成绩的方法,以及一个显示学生平均成绩
的方法。 - 将一组单词存储在一个数组中,并按正序和倒序分别显示这些单词。
- 创建这样一个对象,它将字母存储在一个数组中,并且用一个方法可以将字母连在一
起,显示成一个单词。