PHP学习笔记2:数组

PHP学习笔记2:数组

image-20211129162010327

图源:php.net

php不像其他编程语言那样,有数组、切片、映射、队列、集合等多种数据结构,这些在php中都可以用数组来进行表示。

语法

定义

php的数组由键值对组成,在概念上更像是Go语言的map或者Python的dict

$student = array(
    "name" => 'Li lei',
    "age" => 20
);

键值对由=>组成,并用,间隔,在多行书写时,最后的键值对后可以添加,,也可以不加,前者可以更便于后续继续添加新的键值对:

$student = array(
    "name" => 'Li lei',
    "age" => 20,
    "new_element" => 'test',
);

单行书写时,同样可加可不加,不过习惯上更倾向于不加。

此外,可以使用短数组语法[]替代array()

$student = [
    "name" => "Li lei",
    "age" => 20
];

两者本质上是相同的。

数组的key分为两种:数值索引和字符串索引。两者可以共存:

$students = array(
    0 => array("name" => "Li lei","age"=>20),
    "Han Mei" => array("name" => "Han Mei", "age"=>10),
);
echo $students[0]["name"].PHP_EOL;
// Li lei
echo $students["Han Mei"]["name"].PHP_EOL;
// Han Mei

在很多编程语言中,由纯数字索引组成的数组被称作“索引数组”,由纯字符串索引组成的数组被称作“关联数组”,但在php中,因为数组中可以同时出现数字索引和字符串索引,所以并不区分索引数组或关联数组。

数值索引由正整数构成,比较特别的是,某些类型会被转换为数值索引:

  • 包含十进制整数的数字字符串。
  • float类型。
  • bool类型。
  • null。
$array = array(
    "8"=>"8",
    "+8"=>"+8",
    "08"=>"08",
    3.5 => 3.5,
    true => true,
    false => false,
    null => null,
);
var_dump($array);
// array(7) {
//     [8]=>
//     string(1) "8"
//     ["+8"]=>
//     string(2) "+8"
//     ["08"]=>
//     string(2) "08"
//     [3]=>
//     float(3.5)
//     [1]=>
//     bool(true)
//     [0]=>
//     bool(false)
//     [""]=>
//     NULL
// }  

从示例可以看到,十进制的数字字符串、bool、float都被转换成了对应的整形,而null则被转换成了空字符串。

在很多编程语言中,无论是索引数组还是关联数组,其key都是不允许在定义时重复出现,否则会产生一个致命错误。但php并不会,不过只会保留最后一个重复定义的key的值,其余的会被丢弃:

$array = array(
    "test",
    0 => "test2",
    "key" => "test3",
    "key" => "test4",
);
var_dump($array);
// array(2) {
//     [0]=>
//     string(5) "test2"
//     ["key"]=>
//     string(5) "test4"
//   }

在创建数组时,key可以不指定,此时会以当前数组中最大的数值索引+1来作为新的key,如果还未指定过数值索引,从0开始:

$array = array(
    "a",
    "b",
    6=>"c",
    "d",
    "e",
);
var_dump($array);
// array(5) {
//     [0]=>
//     string(1) "a"
//     [1]=>
//     string(1) "b"
//     [6]=>
//     string(1) "c"
//     [7]=>
//     string(1) "d"
//     [8]=>
//     string(1) "e"
//   }

访问数组元素

在php8.0之前,使用[]{}都可以访问数组元素,8.0之后只有[]可以:

$student = array(
    "name" => "Xiao Li",
    "age" => 20,
);
echo "name:{$student["name"]}, age:{$student["age"]}".PHP_EOL;
// name:Xiao Li, age:20

试图访问一个未定义的key,会引发一个E_NOTICE级别的错误,并返回一个null值:

var_dump($student["test"]);
// Warning: Undefined array key "test" in D:\workspace\http\php-notes\ch2\get_value.php on line 8
// NULL

修改元素

可以使用[]新建或修改元素:

<?php
$student = array(
    "name" => "Li lei",
    "age" => 20,
);
$student["career"] = "student";
$student[] = "male";
var_dump($student);
// array(4) {
//     ["name"]=>
//     string(6) "Li lei"
//     ["age"]=>
//     int(20)
//     ["career"]=>
//     string(7) "student"
//     [0]=>
//     string(4) "male"
//   }

使用[] = value的方式给数组添加新元素的时候,会分配一个新的数值索引,其规则与定义时相同。需要注意的是,并不是基于数组中已有的最大数值索引来生成,而是数组“曾经有过的”最大数值索引来生成,除非这个数组的键被重新生成过:

$array = array("a","b","c","d");
unset($array[3]);
unset($array[2]);
var_dump($array);
$array[] = "e";
var_dump($array);
// array(2) {
//     [0]=>
//     string(1) "a"
//     [1]=>
//     string(1) "b"
//   }
//   array(3) {
//     [0]=>
//     string(1) "a"
//     [1]=>
//     string(1) "b"
//     [4]=>
//     string(1) "e"
//   }

可以看到,虽然使用unset()删除了数字索引2和3对应的键值对,但新添加的元素给予的数字索引是4,而非2。

实用函数

array_values

unset()可以删除数组中的元素,但并不会重建索引,可以使用array_values()函数重建数字索引:

$array = array("a","b","c","d");
unset($array[3]);
unset($array[2]);
$array[] = "e";
$array = array_values($array);
var_dump($array);
// array(3) {
//     [0]=>
//     string(1) "a"
//     [1]=>
//     string(1) "b"
//     [2]=>
//     string(1) "e"
//   }

事实上array_values()的作用是用数组元素组成一个纯数字索引的数组后返回:

$student = array("name" => "Li Lei", "age" => 20);
$studentVals = array_values($student);
var_dump($studentVals);
// array(2) {
//     [0]=>
//     string(6) "Li Lei"
//     [1]=>
//     int(20)
//   }

这个过程会丢弃key中可能包含的信息。

array_keys

array_keys()array_values()作用相反,只会返回数组的key,不会返回值:

$array = array(
    0 => 'val1',
    7 => 'val2',
    "key1" => 'val3',
    "key2" => 'val4',
);
var_dump(array_keys($array));
// array(4) {
//     [0]=>
//     int(0)
//     [1]=>
//     int(7)
//     [2]=>
//     string(4) "key1"
//     [3]=>
//     string(4) "key2"
//   }

array_key_exists

array_key_exists()比较常用,它可以判断数组是否包含某个key:

$students = array(
    array("name" => "Li Lei", "Math" => 90, "English" => 100),
    array("name" => "Han Mei", "Math" => 80, "English" => 90),
    array("name" => "Xiao Li", "Math" => 70, "English" => 95),
);
$total = array();
foreach ($students as $std){
    foreach ($std as $key => $val){
        if ($key != "name" && is_int($val)){
            if (!array_key_exists($key, $total)){
                $total[$key] = 0;
            }
            $total[$key]+=$val;
        }
    }
}
foreach ($total as $key => $val){
    echo "[$key] total score: $val".PHP_EOL;
}
// [Math] total score: 240
// [English] total score: 285

这段代码中,$students包含多个学生的各科成绩,$total数组负责汇总,收集所有学生的成绩总和。因为事先不知道$students中包含哪些科目的成绩,所以$total中是空的。在实际遍历$students时我们需要判断$total是否已经含有科目条目,如果没有,就要进行初始化,然后在这基础上计算总和。

其中这段判断key是否存在并初始化的代码:

if (!array_key_exists($key, $total)){
    $total[$key] = 0;
}

在某些强类型语言(如Java或Go)中是可有可无的,原因是在强类型语言中,$total数组的元素类型是确定的(这里应当是整形),所以解释器可以在生成新元素时自动用相应类型的0值来初始化元素,而php就不同了,php中变量可以表示多种不同的类型,解释器无法判断具体需要用什么来进行初始化。

当然在这个例子中,注释这一段代码是不影响执行结果的,原因是$total[$key]+=$val;这段代码可以让解释器知晓数组新元素大概率是一个整形,并参与数学运算。但这依然会产生一个“访问了不存在的数组key”的E_NOTICE级别的错误,所以应当避免这种问题存在。

有些程序员会使用isset()来检测key是否存在:

            if (!isset($total[$key])){
                $total[$key] = 0;
            }

大多数情况下这样做并不会出现问题,但如果数组的元素是null:

$array = array("key" => null);
if (isset($array["key"])) {
    echo "key exists" . PHP_EOL;
} else {
    echo "key not exists" . PHP_EOL;
}
// key not exists

所以应当避免使用isset()来判断数组key是否存在。

in_array

in_array()也是一个比较常用的数组函数,它可以判断数组是否包含某个值:

function testHttpMehtodAllow($method){
    $httpMethod = ["GET","POST","DELETE","PUSH"];
    if (in_array($method, $httpMethod)){
        echo "$method is a validate http method.".PHP_EOL;
    }
    else{
        echo "$method is not a validate http method.".PHP_EOL;
    }
}
testHttpMehtodAllow('get');
testHttpMehtodAllow('GET');
// get is not a validate http method.
// GET is a validate http method.

就像上面展示的,in_array()往往被用于对输入的合法性验证。

需要注意的是,php中的比较是分为“宽松比较”和“精确比较”的,普通情况下都是进行宽松比较,相当于直接使用==进行比较,此时某些值会被认为是相等的,比如123"123"

function testInArray($value, $array)
{
    if (in_array($value, $array)) {
        echo "$value is in array." . PHP_EOL;
    }
}
$array = ["123"];
testInArray("123", $array);
testInArray(123, $array);
// 123 is in array.
// 123 is in array.

如果需要精确比较,可以通过in_array()的第三个参数指定:

function testInArray($value, $array)
{
    if (in_array($value, $array, true)) {
        echo "$value is in array." . PHP_EOL;
    }
}
$array = ["123"];
testInArray("123", $array);
testInArray(123, $array);
// 123 is in array.

此时相当于使用===进行比较,只有类型和值都一致才会认为是true。

其它更多的数组相关的函数,可以阅读官方手册数组 函数

字符串索引

在php8以前,不加引号的字符串索引也可以使用,但在php8中已经被禁止了:

$student = array();
$student[name] = "Li Lei";
// Fatal error: Uncaught Error: Undefined constant "name" in ...

现在会产生一个致命错误。在以前的版本中,未定义的常量,比如示例中的name,会被解析为相应的字符串,所以代码依然可以运行,但这样会产生一些潜在的问题,比如说后续代码重构中该常量被定义。

正确的做法是总是为字符串索引字面量添加上引号(双引号或单引号):

$student = array();
$student["name"] = "Li Lei";
echo "${student['name']}".PHP_EOL;
echo "${student["name"]}".PHP_EOL;
echo "{$student['name']}".PHP_EOL;
echo "{$student["name"]}".PHP_EOL;
// Li Lei
// Li Lei
// Li Lei
// Li Lei

在字符串中使用数组元素,可能需要结合变量解析的相关语法,就像上面展示的那样。具体可以阅读PHP学习笔记1:基础中字符串的部分。

转换为数组

对于一般的类型(整形、字符串、浮点型、bool、resource),尝试将其转换为数组会得到一个以0为key,以对应数据为值的数组,也就是说(array)$targetarray($target)的结果是一致的:

var_dump((array)1);
var_dump((array)1.5);
var_dump((array)"hello");
var_dump((array)true);
// array(1) {
// [0]=>
// int(1)
// }
// array(1) {
// [0]=>
// float(1.5)
// }
// array(1) {
// [0]=>
// string(5) "hello"
// }
// array(1) {
// [0]=>
// bool(true)
// }

比较特别的是对于对象,转换为数组后是以属性的变量名为key,以属性值为value:

class Student
{
    var $name;
    var $age;
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}
$student = new Student("Li Lei", 20);
var_dump((array)$student);
// array(2) {
//     ["name"]=>
//     string(6) "Li Lei"
//     ["age"]=>
//     int(20)
//   }

这样设计是有意义的,因为我们往往需要将对象转换为字符串,比如json、XML或持久化对象。而数组往往比对象要简单,可以借由数组让这种转化变得更容易。

需要注意的是,对于priaveprotected属性,转换后的键名会有所区别:

class Student
{
    private $name;
    protected $age;
    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}
$student = new Student("Li Lei", 20);
var_dump((array)$student);
// array(2) {
//     ["Studentname"]=>
//     string(6) "Li Lei"
//     ["*age"]=>
//     int(20)
//   }

对于private的属性,会在变量名前添加一个类名,对于protected的属性,会在变量名前添加一个*。需要注意的是,额外添加的字符会以NUL字节间隔:

var_export((array)$student);
// array (
//     '' . "\0" . 'Student' . "\0" . 'name' => 'Li Lei',
//     '' . "\0" . '*' . "\0" . 'age' => 20,
//   )

数组比较

利用array_diff可以比较两个数组的值是否相等:

/**
 * 如果数组相等,返回true,否则返回false。
 * @param array $arr1 
 * @param array 4arr2
 * @return bool
 */
function array_compare($arr1, $arr2)
{
    $diff1 = array_diff($arr1, $arr2);
    $diff2 = array_diff($arr2, $arr1);
    return empty($diff1) && empty($diff2);
}
$arr1 = array("a", "b", "c");
$arr2 = array("b", "d", "e");
var_dump(array_compare($arr1, $arr2));
// bool(false)
$arr1 = array("a","b","c");
$arr2 = array("b","c","a","e");
var_dump(array_compare($arr1, $arr2));
// bool(false)
$arr1 = array("a","b","c","e");
$arr2 = array("b","c","a");
var_dump(array_compare($arr1, $arr2));
// bool(false)
$arr1 = array("a",7=>"b","c");
$arr2 = array("b","c","a");
var_dump(array_compare($arr1, $arr2));
// bool(true)

这种情况下只会比较值,而不比较索引,所以array("a",7=>"b","c")array("b","c","a")相等的。关于array_diff()函数的详细说明可以阅读官方文档array_diff

此外,还可以使用数组运算符来进行比较:

/**
 * 如果数组相等,返回true,否则返回false。
 * @param array $arr1 
 * @param array 4arr2
 * @return bool
 */
function array_compare($arr1, $arr2)
{
    return $arr1 == $arr2;
}
$arr1 = array("a","b","c");
$arr2 = array("b","c","a");
var_dump(array_compare($arr1, $arr2));
$arr1 = array("a","b","c");
$arr2 = array("a","b","c");
var_dump(array_compare($arr1, $arr2));
// bool(false)
// bool(true)

这种比较相对更为严格,不仅要键和值都一样,而且键出现的顺序也要完全一致才行。

更多的数组运算符相关说明可以查阅官方文档数组运算符

以上就是数组相关的全部内容,谢谢阅读。

本系列文章的全部代码都收录在php-notes

相关文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值