PHP中的数组函数学习(四)

今天我们来学习的是数组相关函数的第四篇文章,依然是一大堆的函数需要我们去了解记忆。不过也有很多很好玩的地方值得我们去深入的研究,特别是这些地方还很容易出面试题。相信一出现面试题这三个字大家就很感兴趣了吧,不要着急,我们一个一个地看。

返回数组中所有的值

之前我们已经学习过了一个 array_keys() 函数,作用是返回数组中的所有键。今天我们来学习的这个,则是返回数组中所有的值。

print_r(array_values(['a', '1.2', 'c', 4, 'g' => 1.2, 'c', 'd', 'e', '4']));
// Array
// (
//     [0] => a
//     [1] => 1.2
//     [2] => c
//     [3] => 4
//     [4] => 1.2
//     [5] => c
//     [6] => d
//     [7] => e
//     [8] => 4
// )

array_values() 相信不少同学都使用过,如果面试官问你,如何重建一个数组的索引?那么千万不要犯晕哦,使用的就是这个函数。注意看上面的代码,测试数据里面的是有字符串的键信息的,使用 array_values()  之后,这个信息的键也变成了数字下标的索引信息了,也就是说,整个数组的索引被重建了。

数组数量

实在是太简单了吧,这个还用说?没错,单纯地获取数组元素的数量非常简单。

echo count([1,2,3]), PHP_EOL; // 3

你以为这个 count() 只有这么简单的一个功能就完了?太天真了哦,接下来就是我曾经见过的一道面试题,而且就是用这个 count() 函数就可以解决。

面试题目是:如何判断一个数组是多维数组?在不往下看的情况下自己想一想吧!

首先我们来看一下 count() 的第二个参数,它是一个接受常量值的参数。

$arr = [1,2,3,4=>[5, 6]];
echo count($arr), PHP_EOL; // 4
echo count($arr, COUNT_RECURSIVE), PHP_EOL; // 6

从代码中可以看出,COUNT_RECURSIVE 这个常量给第二个参数之后,count() 获取到的就是包含所有子元素的多维数组的数量。相信不少小伙伴一看到这个地方就知道上面题目的答案是什么了。

var_dump(count($arr) === count($arr, COUNT_RECURSIVE)); // bool(false)

直接使用普通不带第二个参数的 count() 和带上 COUNT_RECURSIVE 参数的 count() 一起对指定的数组比较,就可以判断这个数组是不是多维数组了。如果他们两个相等的话,那么这个数组就是一维的,如果不相等的话,这个数组就是一个多维数组了。

数组与变量的转换

数组和变量是怎么扯上关系的?其实意思就是我们可以将某个数组中的全部元素抽取出来变成当前运行环境下的变量,也可以通过相反的操作,将当前环境下的变量转换到数组中成为数组的元素。

$a = '1';
$b = '2';
$c = '3';

print_r(compact('a'));
// Array
// (
//     [a] => 1
// )

// 7.3以后会 E_NOTICE
print_r(compact(['b', 'c', 'd'])); 
// Array
// (
//     [b] => 2
//     [c] => 3
// )

print_r(compact('a', 'b', ['c']));
// Array
// (
//     [a] => 1
//     [b] => 2
//     [c] => 3
// )

首先我们看到的这个 compact() 函数,就是将当前环境下的变量转换成一个数组。其实这个函数的应用范围不多,大家更喜欢自己去写键值。除非是在某些非常自动化的环境下,比如我们有不可知的动态变量名之类的情况下需要将它们转换成数组,就可以使用这个函数。当然,这种情况也非常少见。

extract(['aa'=>11, 'bb'=>22, 'cc'=>33]);
echo $aa, PHP_EOL; // 11
echo $bb, PHP_EOL; // 22
echo $cc, PHP_EOL; // 33

extract() 是将一个数组中的数据全部转换成变量。这个函数在不少老的框架中会有一定的机率出现,比如我们直接将接收到的 POST 数组全部转换成变量来使用。这个函数其实是不太建议使用的,因为它有一定的危险性。最主要的就是在默认情况下,它会覆盖已经存在的同名变量数据。

$aaa = '111';
extract(['aaa'=>1111111]);
echo $aaa, PHP_EOL; // 1111111

可以看到,原始的 aaa 变量的内容被替换成了数组里的 aaa 元素的内容。对于这种情况,我们需要添加额外的参数来解决同名变量覆盖的问题。

$aaa = '111';
extract(['aaa'=>1111111], EXTR_SKIP);
echo $aaa, PHP_EOL; // 111

$aaa = '111';
extract(['aaa'=>1111111], EXTR_PREFIX_SAME, "new");
echo $aaa, PHP_EOL; // 111
echo $new_aaa, PHP_EOL; // 1111111

EXTR_SKIP 常量的意思是有同名的变量存在不会去覆盖。EXTR_PREFIX_SAME 则是如果有同名的变量存在的话,就使用第三个参数做为前缀并添加这个带前缀的变量。不管是哪种方式,其实都不是非常推荐使用这个函数,还是一个一个的取出来更好。当然,也和上面的 compact() 场景一样,除非是某些特别的情况下,我们不知道数组的键信息,而要将它们全部转换成变量的时候,就可以使用这个函数。同样的,这样的场景也并不常见。

遍历数组

提到遍历数组很多人第一个想到的就是 array_map() ,这个函数我们已经讲过了,现在来看的是另外一个 array_walk() 函数。

它的作用和 array_map() 其实是很相似的,都是针对数组中的每个元素使用回调函数进行自定义操作的。

$arr = ["d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple"];
array_walk($arr, function ($value, $index) {
    echo $index . " : " . $value, PHP_EOL;
});
// d : lemon
// a : orange
// b : banana
// c : apple

不过和 array_map() 不同的是,这个 array_walk() 函数不需要 return 一个值,也就是不需要根据返回值来确定返回的数组元素内容。当然,它的返回值也不是一个数组,而是一个布尔类型。这种情况下,如果我们要对数组的元素进行操作呢?直接给回调函数的参数设置为引用类型就好了。

array_walk($arr, function (&$value, $index, $prefix) {
    $value = $prefix . ' . ' . $index . " : " . $value;
}, 'fruit');
print_r($arr);
// Array
// (
//     [d] => fruit . d : lemon
//     [a] => fruit . a : orange
//     [b] => fruit . b : banana
//     [c] => fruit . c : apple
// )

除了回调函数的返回值问题之外,array_walk() 的回调函数的参数也更多一些,有值、键、前缀符号等。而 array_map() 只有值。

print_r(array_map(function ($v) {
    if (strpos($v, 'c') !== false) {
        return 'No Map ' . $v;
    } else {
        return 'Map ' . $v;
    }
}, $arr));
// Array
// (
//     [d] => Map fruit . d : lemon
//     [a] => Map fruit . a : orange
//     [b] => Map fruit . b : banana
//     [c] => No Map fruit . c : apple
// )

如果我们不 return 任何内容的话,会怎么样呢?

var_dump(array_map(function ($v) {

}, $arr));
// array(4) {
//     ["d"]=>
//     NULL
//     ["a"]=>
//     NULL
//     ["b"]=>
//     NULL
//     ["c"]=>
//     NULL
//   }

可以看到,遍历之后键还在,但因为没有返回值,所以值都成为了 NULL 。当然,我们也可以不给这个回调函数,这样的话这个数组其实就是原样返回了。

var_dump(array_map(null, $arr));
// array(4) {
//     ["d"]=>
//     string(17) "fruit . d : lemon"
//     ["a"]=>
//     string(18) "fruit . a : orange"
//     ["b"]=>
//     string(18) "fruit . b : banana"
//     ["c"]=>
//     string(17) "fruit . c : apple"
//   }

最后,就是我们在这个系列文章中一开始就讲到过的,这个 array_map() 函数是可以支持多个数组的,回调函数中的参数也是根据多个数组的数量来确定的。

var_dump(array_map(function ($a, $b, $c) {
    return $a . ' * ' . $b . '*' . $c;
}, $arr, [1, 2, 3], ['a', 'b', 'c', 4]));
// array(4) {
//     [0]=>
//     string(23) "fruit . d : lemon * 1*a"
//     [1]=>
//     string(24) "fruit . a : orange * 2*b"
//     [2]=>
//     string(24) "fruit . b : banana * 3*c"
//     [3]=>
//     string(22) "fruit . c : apple * *4"
//   }

数组去重

去重的含义就不用多解释了,就是去除数组中相同的值,其实就是让数组回归到原来数学意义上集合的概念。大家可以回忆一下高中学过的集合的概念,集合里是不能用重复的数据的。

print_r(array_unique(['a', 'b', 'c', 'b', 'c', 'd', 'e']));
// Array
// (
//     [0] => a
//     [1] => b
//     [2] => c
//     [5] => d
//     [6] => e
// )

print_r(array_unique(['a', '1.2', 'c', 4, 'g' => 1.2, 'c', 'd', 'e', '4']));
// Array
// (
//     [0] => a
//     [1] => 1.2
//     [2] => c
//     [3] => 4
//     [5] => d
//     [6] => e
// )

数组求和

对于数组中的元素来说,我们可以直接对所有的元素进行求和操作。

echo array_sum([1, 2, 3]), PHP_EOL; // 6

echo array_sum([1.1, 2, 3.3]), PHP_EOL; // 6.4

不管是整数还是浮点数都是没有问题的,但如果元素中有非数字类型的数据呢?

echo array_sum([1, 2, 'c', '4d']), PHP_EOL; // 7

可以发现如果是纯字符串的话,它会直接忽略了这个非数字类型的元素,而如果是可以转换成数字类型的字符串,那么它会根据 PHP 的转换规则进行转换之后再计算。关于数字、字符串类型的转换规则在最早期的文章中有过详细的讲解,这里就不多说了。

数组截取

数组截取和字符串的截取其实没有什么不同,都是指定好开始的位置,指定一个长度就可以了。

$input = ["a", "b", "c", "d", "e"];
print_r(array_slice($input, 2));
// Array
// (
//     [0] => c
//     [1] => d
//     [2] => e
// )

print_r(array_slice($input, -2, 1));
// Array
// (
//     [0] => d
// )

print_r(array_slice($input, 0, 3));
// Array
// (
//     [0] => a
//     [1] => b
//     [2] => c
// )

print_r(array_slice($input, 2, -1));
// Array
// (
//     [0] => c
//     [1] => d
// )

array_slice() 的第二个参数是偏移量,也就是从什么位置开始,而第三个参数是要截取的长度。从上面的代码中,我们可以看出,这些参数都是可以写负数的,如果是第二个参数为负数的话,就是从距离数组最末端(最后一个元素)的位置开始。第三个参数如果是负数的话,则截取的长度会终止在距离数组末端指定的这个距离的地方。不过大部分情况下,我们还是应该都以正数来进行计算使用。

除了这两个参数外,array_slice() 还有第四个参数,用于确定是否需要保留原来的键名。

print_r(array_slice($input, 2, -1, true));
// Array
// (
//     [2] => c
//     [3] => d
// )

另外,第二个参数偏移量不是以数组下标为依据的,而是以数据在数组中的位置,比如说我们使用 Hash 形式的数组。

$inputKV = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5];
print_r(array_slice($inputKV, 2));
// Array
// (
//     [c] => 3
//     [d] => 4
//     [e] => 5
// )

截取替换

刚刚我们介绍的只是截取一段数组的内容,下面学习的则是即截取又替换的一个函数。

$input = ["red", "green", "blue", "yellow"];
print_r(array_splice($input, 2));
// Array
// (
//     [0] => blue
//     [1] => yellow
// )

$input = ["red", "green", "blue", "yellow"];
print_r(array_splice($input, 1, -1));
// Array
// (
//     [0] => green
//     [1] => blue
// )

array_splice() 的参数和 array_slice() 的参数概念差不多,它的返回值是截取的内容。那么替换的功能是如何体现的呢?

$input = ["red", "green", "blue", "yellow"];
print_r(array_splice($input, 1, count($input), "orange"));
print_r($input);
// Array
// (
//     [0] => green
//     [1] => blue
//     [2] => yellow
// )
// Array
// (
//     [0] => red
//     [1] => orange
// )

没错,从上面的代码就可以看出,array_splice() 的第一个需要传递进去的数组参数是一个引用类型的参数。替换之后的结果就是直接修改了的原始的那个引用参数的变量。在这里,我们截取的内容是从第 2 个元素之后所有的数据,然后把它们替换为 orange 这个元素。于是最后的数组就只有 red 和 oragne 这两个元素了。

同样的,它最后的替换参数也是可以支持一个数组去批量替换的。

$input = ["red", "green", "blue", "yellow"];
print_r(array_splice($input, -1, 1, ["black", "maroon"]));
print_r($input);
// Array
// (
//     [0] => yellow
// )
// Array
// (
//     [0] => red
//     [1] => green
//     [2] => blue
//     [3] => black
//     [4] => maroon
// )

在这段代码中,我们将最后一个元素替换成了两个元素,函数的参数也是直接放一个数组过来就可以了。

总结

今天学习的内容其实都是一些很有意思的小函数,但是很多时候可能对我们的面试很有帮助,比如说那个判断数组是否是多维数组的,又或者是 array_map() 和 array_walk() 的异同,也有可能会问你 array_slice() 和 array_splice() 的区别。而且最主要的是,这些面试题还真是我遇到过的,并不是瞎编的,而且有时候我也会拿这些题目来问我面试的人。其实大家也不用太担熟心,最核心的就是要混个脸,千万别说你都不知道这个函数是干什么的就行了,至少要能知道这些函数大概的意思,比如 array_map() 需要一个返回值,array_splice() 的返回值是截取的内容,引用参数是替换之后的结果。掌握了核心的内容之后,面试丢不了的。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/04/source/3.PHP%E4%B8%AD%E7%9A%84%E6%95%B0%E7%BB%84%E5%87%BD%E6%95%B0%E5%AD%A6%E4%B9%A0%EF%BC%88%E5%9B%9B%EF%BC%89.php

参考文档:

https://www.php.net/manual/zh/ref.array.php