李涛自留地
回到顶部
首页 > php学习 > php计算数组长度的函数有哪些,count与sizeof差异

php计算数组长度的函数有哪些,count与sizeof差异

文章目录

php计算数组长度的函数并不多,日常真正会被调用的只有 count 和它的别名 sizeof。很多人写到第三年才发现两者几乎等价,却在递归参数和 Countable 对象上踩过坑。这篇把两者的边界、参数含义和常见误用讲清楚,也解释为什么有些统计结果总比预期大一圈,顺带给出几个能照抄的判断方式。

php计算数组长度的函数:count 与 sizeof 是同一份实现

PHP 手册对 count 的说明是统计数组元素个数,或统计实现了 Countable 接口的对象属性个数。sizeof 在同一页被标注为 count 的别名,两者共用一份实现,行为没有区别。

两块同型号固态盘并排插在同一机架位

纠结用哪个名字意义不大,php计算数组长度的函数就这么两个入口,团队统一一个就好。混着写只会让读代码的人多停顿一秒,猜这是刻意区分还是顺手敲的。选 count 更常见,语义也更贴它的作用。

真正要留意的是参数。php计算数组长度的函数在默认模式下只数第一层,嵌套结构里的元素不会被展开。这个默认值救过很多人,也让很多人对不上账。

返回值类型也是 php计算数组长度的函数容易忽略的地方。count 返回 int,PHP 7.2 之后对不可计数的值会发警告并返回 1,PHP 8 起直接抛 TypeError。跨版本升级时,这类老代码会集中暴露。

sizeof 只是 php计算数组长度的函数的一个别名

sizeof 来自 PHP 早期的命名习惯,那时不少函数带 size 前缀。保留到现在主要为了兼容,官方文档把它列在 count 的别名位置,没有独立实现。

流传较广的说法是 sizeof 略慢,因为多一层别名解析。这种差异在业务代码里测不出来,也不该当作选型依据。真正该关心的是递归统计带来的遍历成本。

写扩展或做静态分析时,两者在语法树里仍是两个不同的函数名,只是运行时落到同一份实现。也正因为名字不同,PHP-CS-Fixer 才有专门规则把 sizeof 改写成 count,团队想统一写法直接启用这条规则就行。

<?php
$tags = ['php', 'array', 'count'];
echo count($tags);   // 3
echo sizeof($tags);  // 3,指向同一个函数

两行输出一致,说明别名关系在调用层面成立。会分叉的地方在参数上,也就是递归模式。

count 的第二个参数:COUNT_RECURSIVE 该不该传

php计算数组长度的函数都接受第二个参数,手册给出的签名是 count(array|Countable $value, int $mode = COUNT_NORMAL)。第二个参数接受 COUNT_NORMAL 或 COUNT_RECURSIVE,前者是默认值,后者会沿嵌套结构逐层统计。

三层嵌套方框,光带把容器层也描出来

传 COUNT_RECURSIVE 时,返回的是所有层级的元素总数,子数组本身也算一个元素。一个二维数组若有 3 个键、每个键下 2 个值,结果不是 6 而是 9。

这个把容器也算进去的行为,是统计对不上的根源。做报表、拼缓存 key、校验导入行数时,直接把递归结果当业务条数,就会得到一个固定偏差。

第二个参数对对象同样生效。实现了 Countable 的对象由自己的 count 方法决定返回什么,递归标志在对象层面未必被采纳,具体以该对象的实现为准。

<?php
$orders = [
    'paid'   => ['A1', 'A2'],
    'unpaid' => ['B1'],
];
echo count($orders);                  // 2
echo count($orders, COUNT_RECURSIVE); // 5

想让结果等于业务条数,正确做法是对每个子数组单独 count 再累加,不要依赖递归模式。它适合快速判断结构里有没有内容,不适合精确报表。

多维数组递归计数为什么会多算一层

多维数组递归计数的偏差来自定义本身。子数组在父层是一个元素,递归展开时它又贡献了全部子元素。同一个节点被计了两次,一次当容器,一次当内容。

层级越深,偏差越大。三层结构里,中间层和顶层容器都会被额外计入,误差不再是常数,而是跟分支数量挂钩。

项目里这种结构常见于配置树、菜单权限、商品分类。数值差一层,页面上显示的条数就和数据库对不上,排查时先怀疑递归计数往往更省时间。

密集硬盘托架,光只落在最内层小托架

还有个隐性风险是循环引用。PHP 数组是值语义,正常构造不会自引用,但 Countable 对象的属性互相持有时会。对自引用结构跑 count($arr, COUNT_RECURSIVE),PHP 自己会检测到并抛出 Recursion detected 警告,返回值不可信,不会把进程拖死;真正会一路走下去的是对象里自己实现的递归 count(),那种实现才需要提前判深度。

稳妥的写法是显式遍历,让每层只加一次,结果才可预期。下面这个函数只统计叶子节点,跳过了所有容器。

<?php
function leafCount(array $rows): int
{
    $n = 0;
    foreach ($rows as $row) {
        $n += is_array($row) ? leafCount($row) : 1;
    }
    return $n;
}

调用它对上面的订单结构会返回 3,正好是三条记录。代价是多写几行,换来的是语义清晰、边界可测。

遇到不确定的场景怎么验证

版本差异、对象实现、递归深度这几件事靠记忆容易出错。php计算数组长度的函数在这几个维度上都有边界情况,最省事的办法是在目标版本上跑一段最小示例,把返回值与异常信息都打印出来。

项目里如果散布着大量 php计算数组长度的函数调用,可以先用静态分析扫一遍参数,找出带 COUNT_RECURSIVE 的位置,再逐个确认语义是否符合预期。

空数组和 null 也要分开看。count([]) 是 0,count(null) 在 PHP 8 会直接报错。拿可空变量做长度判断,isset 或 empty 更稳妥。

单点验证完之后,把结论写进注释或测试用例,比记在脑子里可靠。长度统计这种事,一次写对能省掉后面很多次排查。

php计算数组长度的函数的官方页面有完整参数表和更新记录,遇到边界情况直接对照原始资料,比看二手总结准确。

若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。