标签归档:strict comparison

PHP的比较方式(loose and strict comparison)

PHP的官方文档PHP type comparison tables 列出PHP类型和比较运算符在松散和严格比较时的作用。当阅读这篇说明时, 其中有一个NOTE:HTML 表单并不传递整数、浮点数或者布尔值,它们只传递字符串。 道出了我们WEB开发过程中数据交互的本质。 一如XML,JSON等等都是字符串,只是依照不同的需求制定规则,让人们能够更好的理解。 再看HTTP协议,不管是request还是response,最终的载体都是字符串,只是依照不同的字段和规则实现所需要的缓存优化、信息传递等功能。 有点扯远了,回到今天的主题,PHP的比较方式。

PHP的一些语言结构以及函数在默认实现上都是以松散比较的方式实现,如switch结构、array_keys、in_array、array_search等函数。 如果我们要使用严格的比较,对于switch语言结构本身并没有提供相关的实现,我们可以通过先将变量转换成对应的类型后再使用switch, 对于array_keys等函数,在PHP5以后都提供了$strict参数,将其设置为TRUE即可。 PHP的松散和严格比较表现最明显的是==和===。 我们从这两个语法结构来查看PHP对于松散和严格比较的实现。

从词法分析开始,在Zend/zend_language_scanner.l文件中我们找到==和===对应的token,如下:

<ST_IN_SCRIPTING>"===" {
    return T_IS_IDENTICAL;
}

<ST_IN_SCRIPTING>"==" {
    return T_IS_EQUAL;
}

PHP在词法结构上将这两种方式进行彻底的区分,==对应T_IS_EQUAL标识,===对应T_IS_IDENTICAL标识, 如果我们在写程序的过程中同时使用了以上两种符号会出现什么呢?如下代码:

$num = 1;
$str = "1";

if ($num === $str == $num) {
    echo 'phppan';
}

这里就不给出答案了(^_^)。

我们接着看语法解析,在Zend/zend_language_parse.y文件中我们可以看到如下代码:

|   expr T_IS_IDENTICAL expr        { zend_do_binary_op(ZEND_IS_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC); }
|   expr T_IS_EQUAL expr            { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }

仅仅是通过上面的所给的代码,不去查看Zend/zend_complice.c中关于zend_do_binary_op函数的实现, 我们可以猜测出其最终会生成ZEND_IS_EQUAL和ZEND_IS_IDENTICAL中间代码。 最终所有的ZEND_IS_IDENTICAL中间代码对应的执行函数都会调用is_identical_function函数实现严格对比, 所有的ZEND_IS_EQUAL中间代码对应的执行函数都会调用compare_function函数实现松散对比。

compare_function函数的具体实现在Zend/zend_operators.c文件。 compare_function的比较过程是一个穷举遍历比较的过程,程序在实现过程中对于传入的两个变量做类型的识别, 以两个变量的类型对为key,如字符串与字符串比较,数组和数组比较,浮点型和整型的比较,构造的类型对如果顺序不同,其对应的处理也是不同的。 如下列表,为所有可直接识别的类型对:

  • 整型和整型(LONG and LONG)
  • 浮点型和整型(DOUBLE and LONG)
  • 整型和浮点型(LONG and DOUBLE)
  • 浮点型和浮点型(DOUBLE and DOUBLE)
  • 数组和数组(ARRAY and ARRAY)
  • NULL和NULL
  • NULL和BOOL
  • BOOL和NULL
  • 字符串和字符串(STRING and STRING)
  • NULL和字符串
  • 字符串和NULL
  • 对象和NULL
  • NULL和对象
  • 对象和对象(OBJECT and OBJECT)

除此之外,还有一些特殊的情况,如下代码:

$arr = array(1);
$num = 1;

if ($arr == $num) {
    echo 'yes';
}else{
    echo 'no';
}

这些代码最终会输出no,但是在compare_function返回的结果为1。 他不属于上面所说的任一种类型对,由于第一个操作数为数组,则其走的程序流程分支为:

else if (Z_TYPE_P(op1)==IS_ARRAY) {
    ZVAL_LONG(result, 1);
    return SUCCESS;
}

以上的代码将1赋值给result,但是在中间代码的执行函数中,执行完compare_function后会执行如下代码:

ZVAL_BOOL(result, (Z_LVAL_P(result) == 0));

因为比较的最终结果为真假,而在比较中除了0,其他的都是不等于。

如果是比较字符串和整数,则其最终都将字符串转化成数字(整数或浮点数)再进行比较,即最终比较的方法是可以识别的类型对之一。 如果是数组和数组的比较,由于存在数组与数组的对比,则其直接调用zend_compare_arrays完成比较。

与松散比较类似,严格比较也有一个对应的函数–is_identical_function。 同样,其实现也在Zend/zend_operator.c文件。 和loose comparison不同,strict comparison在严格意义上来说进行的不是一个纯粹的比较过程,它首先会判断两个变量所处的ZVAL容器的类型是否一样, 如果不一样直接返回0,即不相等。 如果两个ZVAL窗口的类型一样,则根据比较的第一个操作数的类型作区分,然后判断两个操作数的值是否一样, 这里之所以要分别对待每种类型,是因为在PHP中不同类型的值存储的位置不同,其对应的获取方法也不相同。

在看完了PHP关于松散比较和严格比较的源码,我们最后看一段代码,你觉得应该会输出什么?

$str = "0";
if (empty($str)) {
    echo 1;
}

$str = "00";
if (empty($str)) {
    echo 2;
}