为什么PHP认为0等于一个字符串?

134

我有以下代码片段:

$item['price'] = 0;
/* Code to get item information goes in here */
if($item['price'] == 'e') {
    $item['price'] = -1;
}

目的是将商品价格初始化为0,然后获取有关商品的信息。如果以“e”的方式通知价格,这意味着进行交易而不是销售,将作为负数存储在数据库中。

还有可能将价格保留为0,或者因为商品是奖励,或者因为价格将在稍后设置。

但是,每当价格未设置,使其保持初始值为0时,上面指示的if循环将评估为true,并将价格设置为-1。也就是说,它将0视为等于'e'。

这个问题该如何解释?

当价格提供为0(初始化后),行为是不稳定的:有时if评估为true,有时评估为false。


1
我发现使用三个等号 === 而不是两个等号 == 可以得到预期的行为。但它仍然很奇怪。 - Sérgio Domingues
2
(参考) 在 PHP 手册的 类型转换 章节中有详细解释,并在 类型比较表格 中有说明。 - Gordon
1
如果唯一可能的字符串类型是'e',那么你不能只使用a is_string($item["price"])检查吗?这比使用===会更有效率。【需要引用】 - Jimmie Lin
在字符串和整数之间的弱比较中,字符串会被转换为整数(而不是将整数“提升”为字符串)。if((string)$item['price'] == 'e') 可以解决奇怪的行为。有关更多详细信息,请参见 https://dev59.com/Z2w15IYBdhLWcg3wFHtZ#48912540 - Paolo
请注意下面@Paolo在评论中提到的另一种情况,即当使用双等号运算符时,0(整数)等于任何其他字符串。 - Haitham Sweilem
在PHP<8中,当一个字符串与一个数字进行比较时,该字符串会自动转换为数字,如果非数字则变为0。然而,在PHP 8中,由于您所描述的奇怪行为,这种情况不再发生。在PHP 8中,0被转换为字符串,因此比较结果不相等。 - PHP Guru
9个回答

146

你正在使用==,这会自动为你进行类型转换。

0是一个整数,所以在这种情况下它会将'e'强制转换为一个整数。由于'e'不能被解析为整数,因此它将变成0。字符串'0e'将变成0并匹配成功!

应该使用===

来自PHP.net:

使用==和其他非严格比较运算符对字符串和数字之间的比较通常通过将字符串强制转换为数字,然后在整数或浮点数上执行比较来完成。这导致了许多令人惊讶的比较结果,其中最显着的是0 == "foobar"返回true。

但是,在PHP 8.0中更改了此行为:

与数字字符串进行比较时,PHP 8使用数字比较。否则,它将数字转换为字符串并使用字符串比较。

PHP 7

0 == 'foobar' // true
0 == '' // true
4 == '4e' // true (4e is cast as a number and becomes 4)

PHP 8在进行比较之前会将数字转换为字符串

0 == 'foobar' // false
0 == '' // false
4 == '4e' // false ('4e' is considered non-numeric therefore 4 is cast as a string and becomes '4')

这是一项重大更改,因此它已在新的主要PHP版本中实施。此更改会打破依赖旧行为的脚本的向后兼容性。


16
松散比较的另一个劣势。 - MC Emperor
8
有点棘手。我刚刚遇到这个问题,很惊讶为什么字符串等于0。得记住这一点。 - Grzegorz
2
在循环字符串键时,我也陷入了困境,但是数组有一个初始的“零”索引项,这导致第一个字符串键比较结果始终为true。我就像什么?怎么会这样…所以,确实,这个答案解决了我的问题!我惊讶于这整个问题没有得到任何被接受的答案。这只说明一些提问者是混蛋。 - IncredibleHat

48

这是由于PHP进行比较操作的方式导致的,==比较运算符指示:

如果您将数字与字符串进行比较或比较涉及数字字符串,则每个字符串都会转换为数字并执行数值比较。[...] 当比较是 === !==时,类型转换不会发生,因为这涉及比较类型以及值。

由于第一个操作数是数字(0),第二个操作数是字符串('e'),因此该字符串也会被转换为数字(另请参见表格各种类型的比较)。有关字符串数据类型的手册页面定义了如何执行字符串转数字转换

  

当在数字上下文中评估字符串时,将确定结果值和类型。

     

如果字符串不包含字符“.”,“e”或“E”,并且数值适合整数类型限制(由PHP_INT_MAX定义),则该字符串将被评估为整数。在所有其他情况下,它将被评估为浮点数。

在这种情况下,字符串是'e',因此它将被评估为浮点数:

该值由字符串的初始部分给出。如果字符串以有效的数字数据开头,则将使用该值。否则,该值将为0(零)。有效的数字数据是一个可选符号,后跟一个或多个数字(可选包含小数点),后跟一个可选指数。指数是一个'e'或'E',后跟一个或多个数字。
由于'e'不以有效的数字数据开头,因此它被解释为浮点数0

3
PHP 设计许多东西都很容易比较,但又加了一些陷阱来破坏我们的一天。这与 PHP 的其他设计理念不符合。除非欺骗是他们的哲学? - user3338098
1
特别是由于“e”转换为真和“”转换为假。 - user3338098

27
"ABC" == 0

true的评估是因为首先"ABC"转换为整数并变成0然后0进行比较。

这是PHP语言的一个奇怪行为:通常人们会期望0被提升为字符串"0",然后与"ABC"进行比较并得到结果false。其他语言如JavaScript中可能会出现这种情况,弱类型比较"ABC" == 0的结果是false

进行严格比较可以解决这个问题:

"ABC" === 0

评估结果为false

如果我确实需要将数字字符串与数字进行比较怎么办?

"123" === 123

因为左右项类型不同,所以评估结果为false

实际需要的是一种没有PHP类型转换问题的弱比较。

解决方法是明确将术语提升为字符串,然后进行比较(严格或弱都无关紧要)。

(string)"123" === (string)123

true

(string)"123" === (string)0

false


应用于原始代码:

$item['price'] = 0;
/*code to get item information goes in here*/
if((string)$item['price'] == 'e') {
    $item['price'] = -1;
}

将其转换为字符串在我的情况下非常有帮助,谢谢! - Saghachi

8

即使不同类型的值,== 运算符也会尝试进行匹配。例如:

'0' == 0 will be true

如果你需要进行类型比较,可以使用 === 运算符:
'0' === 0 will be false

8

你的问题在于双等号运算符,它会将右侧成员转换为左侧的类型。如果你喜欢,可以使用严格模式。

if($item['price'] == 'e') {
    $item['price'] = -1;
}

让我们回到你的代码(如上所述)。在这种情况下,大多数情况下,$item['price'] 是一个整数(除非它等于 e)。因此,根据 PHP 的规则,PHP 将把 "e" 强制转换为整数,得到 int(0)。(不相信我吗?<?php $i="e"; echo (int)$i; ?>)。

要轻松避免这种情况,使用三个等号(精确比较)运算符,它将检查类型并且不会隐式进行类型转换。

P.S:PHP 的有趣事实:a == b 不意味着 b == a。以你的例子为例反过来:如果 $item['price'] 总是一个整数,则永远不会实际执行 if ("e" == $item['price'])。


6

为了避免类型不匹配的问题,应该使用 === 代替 ==。普通的操作符不会比较数据类型,而是试图对它们进行类型转换。

=== 则会考虑数据类型。

  • === 表示“相等”,
  • == 表示“有点像”

1
我明白了。现在这个程序可以正常工作了(使用了类型转换):if ((string)$item['price'] == 'e') { $item['price'] = -1; } - Sérgio Domingues
但是你不应该那样做,而是使用 === 运算符。 - tereško

6

PHP中有一种非常方便的方法可以验证"0"、"false"、"off"作为== false,"1"、"on"、"true"作为== true,这通常被忽视。它对于解析GET / POST参数特别有用:

filter_var( $item['price'], FILTER_VALIDATE_BOOLEAN );

这并不完全符合此用例,但考虑到相似性以及搜索结果往往会在验证(字符串)“0”为false时提供帮助,我想这将有助于其他人。

http://www.php.net/manual/zh/filter.filters.validate.php


4

基本上,始终使用 === 运算符来保证类型安全。

这里输入图片描述

这里输入图片描述


2

我认为最好通过我遇到相同奇怪行为时所做的示例来展示。查看我的测试案例,希望能帮助您更好地理解这种行为:

Original Answer 翻译成 "最初的回答"

// Normal comparison using the == Operator
echo (0 == "0"); // true
echo (0 == "a"); // true
echo (0 == "safta!"); // true
echo (1000 == "bla"); // false. It appears that PHP has a weird behavior only with the number / string 0 / "0" according to the past 3 examples.
echo (23 == "23"); // true. So as we said, PHP has a problem (not a problem but weird behavior) only when the number / string 0 (or "0") is present
echo (23 == "24"); // false. values aren't equal (unlike last example). The type is less relevant with the == operator as we can see.

// Now using the === and !== Operators
echo ("0" === 0); // false, since === requires both value and type to be the same. Here, type is different (int vs string)
echo ("0" !== 0); // true because they aren't the same in terms of === comparison (type is different and that's why it's true)
echo ("bla" === "blaa"); // false because the values are not the same. The type is the same, but === checks for both equal type and equal value.

//Now using casting and === Operator:
echo ((string)123 === "123"); // true. The casting of the int 123 to string changed it to "123" and now both variables have same value and are of same type
echo ((int)"123" === 123); // true. The casting of the string 123 to int, changed it to int, and now both variables are of same value and type (which is exactly what the === operator is looking for)

// Now using casting and == Operator. Basically, as we've seen above, the == care less for the
// type of var, but more to the value. So the casting is less relevant here, because even
// without casting, like we saw earlier, we can still compare string to int with the == operator
// and if their value is same, we'll get true. Either way, we will show that:
echo ((string)123 == "123"); // true. The casting of the int 123 to string changed it to "123" and now both vars have same value and are of same type
echo ((int)"123" == 123); // true. The casting of the string 123 to int, changed it to int, and now both vars are of same value and type (which is exactly what the === operator is looking for)

很棒的测试,我也做了同样的事情,但我制作了一个漂亮的表格。请看我的答案。 - user11534547

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接