Co un() 的作用与嵌套调用规则
解读
在国内一线/二线大厂的 PHP 面试里,count() 几乎是“必问基础题”。
面试官通常不会只问“它是干什么的”,而是给一段嵌套代码让你预测返回值,或者追问“会不会触发警告”“时间复杂度是多少”。
核心考点有三层:
- 作用:取得数组/Countable 元素的个数;
- 行为:对非数组、非 Countable 对象、null、标量分别怎么处理;
- 嵌套:当 count() 的返回值再被 count() 时,结果是什么,会不会产生隐式类型转换或警告。
把这三层答清楚,就能从“知道”变成“精通”,直接拉开与普通候选人的差距。
知识点
- 函数原型:int count(mixed mode = COUNT_NORMAL)
- 模式位:
COUNT_NORMAL(默认)(0):只统计第一层;
COUNT_RECURSIVE(1):递归统计所有“数组元素”的个数,遇到非数组分支停止。 - 返回值规则:
数组:返回元素个数;
Countable 对象:调用其 count() 方法;
其他类型(null、bool、int、float、string、resource、非 Countable 对象):返回 1,并触发 E_WARNING(PHP 8 之前是 E_WARNING,PHP 8 起仍为警告,未被移除)。 - 嵌套本质:count() 永远返回 int。因此二次 count(int) 必然得到 1,并伴随一条警告。
- 时间复杂度:O(1),因为 HashTable 内部已维护 nNumOfElements,无需遍历。
- 常见坑:
- 把 COUNT_RECURSIVE 当成“多维数组元素总和”——实际只统计“数组节点”数;
- 在 PHP 8 的 strict_types=1 文件里,count() 依旧会做隐式强制转换,不会抛 TypeError;
- 在代码审计场景,count(userInput 可能为字符串,会返回 1 导致逻辑绕过。
答案
作用:
count() 用于取得数组或 Countable 对象的元素个数;通过第二个参数可开启递归模式。
嵌套调用规则:
- 第一次 count($var) 返回一个整数;
- 该整数属于标量类型,再次 count(整数) 时 PHP 会返回 1,并产生一条 E_WARNING “count(): Parameter must be an array or an object that implements Countable”;
- 因此无论嵌套多少层,从第二层起结果恒为 1,且每层都会产生警告;
- 如果业务代码需要链式或嵌套使用,必须先保证内层返回的是数组/Countable,否则既浪费 CPU 又污染日志。
示例:
$arr = [[1, 2], [3, 4]];
echo count($arr); // 2
echo count($arr, COUNT_RECURSIVE); // 6(2 个一级数组 + 4 个二级元素)
echo count(count($arr)); // 1,并触发警告
拓展思考
-
性能对比:
在 FPM 高并发接口里,用 count() 判断“数组为空”比 empty() 多一次函数调用,但两者都是 O(1)。
实际压测(PHP 8.2 + Opcache)差距在 3% 以内,可忽略;但 empty() 不会触发警告,写法更安全。 -
与 sizeof() 的关系:
sizeof() 是 count() 的别名,源码层完全复用,面试时说出“别名”即可,无需展开。 -
自定义 Countable:
在 SPL 里实现 Countable 接口,可以把数据库结果集、内存表等封装成“可计数”对象,使 count($object) 直接返回 SELECT COUNT(*) 的结果,避免一次性加载数据。 -
代码审计案例:
某 CMS 插件用if (count($_GET['ids']) > 0)判断是否有批量 ID,攻击者传入?ids=1导致 _GET['ids'])再 count,或直接!empty($_GET['ids'])`。 -
未来演进:
PHP 社区曾讨论“让 count() 对标量返回 0”,但因破坏 BC 搁置;面试时可提及“保持向下兼容是 PHP 设计哲学”,体现对语言历史的理解。