三元运算符左结合性

47
在PHP手册中,我在“运算符”下面找到了以下的“用户贡献注释”

Note that in php the ternary operator ?: has a left associativity unlike in C and C++ where it has right associativity.

You cannot write code like this (as you may have accustomed to in C/C++):

<?php 
$a = 2; 
echo ( 
    $a == 1 ? 'one' : 
    $a == 2 ? 'two' : 
    $a == 3 ? 'three' : 
    $a == 4 ? 'four' : 'other'); 
echo "\n"; 
// prints 'four' 
我实际尝试了一下,它确实打印出了four。然而我无法理解其中的原因,仍然觉得应该打印twoother
请问有人可以解释一下这里发生了什么,为什么会打印出'four'吗?

4
加上暗示的结合律所表示的括号,你就会明白为什么了。 - Oliver Charlesworth
8
问题的视觉展示:http://i.imgur.com/1zgFd.jpg - Almo
我绘制了左结合流程的映射图,见为什么Perl条件运算符是右结合的 - brian d foy
1
PHP默认使用左结合。实际上,左结合几乎从来不是你真正想要的,所以PHP的默认设置并没有太多意义。事实上,在PHP 7.4中,这已经被弃用了。 - Joseph Silber
我不认为在SO上的陌生人是朋友,所以让我们现在就明确这一点。陌生人甚至不允许陌生人使用PHP。真的,远离它。 - Steven Lu
5个回答

68

在任何理智的语言中,三元运算符是右结合的,这意味着您希望代码被解释为:

$a = 2;
echo ($a == 1 ? 'one' :
     ($a == 2 ? 'two' :
     ($a == 3 ? 'three' :
     ($a == 4 ? 'four' : 'other'))));    # prints 'two'

然而,PHP三元运算符的结合方式很奇怪,它是从左往右结合的。因此,你的代码实际上等同于这个:

<?php
$a = 2;
echo (((($a == 1  ? 'one' :
         $a == 2) ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');   # prints 'four'

如果还没有清楚,评估的步骤如下:

echo ((((FALSE    ? 'one' :
         TRUE)    ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');

echo ((( TRUE     ? 'two' :
         $a == 3) ? 'three' :
         $a == 4) ? 'four' : 'other');

echo ((  'two'    ? 'three' :
         $a == 4) ? 'four' : 'other');

echo (    'three' ? 'four' : 'other');

echo 'four';

8
我理解的是字符串被强制转换为布尔值,这是否正确?(如果是的话,那太神奇了。) - Wildcard
1
@Wildcard 不是的,这里的三元运算符被包含在 echo 中,它会输出到控制台。它将根据 $a 在方程式中的计算结果之一打印字符串。但我仍然不理解“左”结合性! - the holla
3
@DianaHolland,但是在问号(?)之前的部分是一个字符串。 - Wildcard
12
@Wildcard:你阅读的是正确的,这在PHP世界中很平常。没有什么令人震惊的:) - Ярослав Рахматуллин
你真的让我理解了左右结合的概念,非常感谢。 - Layne Liu

26

由于整个表达式的求值结果就像是 (......) ? 'four' : 'other'。因为第一个元素很可能是真值,它会给出'four'。在更合理的语言中,其中 ?: 具有右结合性,整个表达式的求值结果就像是 $a == 1 ? 'one' : (......),如果 $a 不等于 1,你会继续测试其他东西。


1
当然,你应该无论如何都要给复杂的表达式加上括号,所以在我看来这个观点是无意义的。 - Niet the Dark Absol
21
依我之见,出于可读性的考虑,您应该使用括号。在我的观点中,在一个明智的(非 PHP)语言中,OP 中的代码是完全易读的,如果您使用括号反而会变得不太易读。 - Amadan
2
嵌套的三元运算符太令人困惑了,所以我从不使用它们,无论是否带括号。但是这个答案很棒。 - Ruan Mendes

7
这是我为了帮助自己理解三元运算符的左右结合性而想出来的方法。
// PHP

$a = "T";
$vehicle =  $a == "B" ? "bus" :
            $a == "A" ? "airplane" :
            $a == "T" ? "train" :
            $a == "C" ? "car" :
            $a == "H" ? "horse" : "feet";

            // (as seen by the PHP interpreter)
            // INITIAL EXPRESSION: ((((($a == "B" ? "bus" : $a == "A") ? "airplane" : $a == "T") ? "train" : $a == "C") ? "car" : $a == "H") ? "horse" : "feet");
            // STEP 1:             (((((FALSE ? "bus" : FALSE) ? "airplane" : TRUE) ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 2:             ((((FALSE ? "airplane" : TRUE) ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 3:             (((TRUE ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 4:             (("train" ? "car" : FALSE) ? "horse" : "feet")
            // STEP 5:             ("car" ? "horse" : "feet")
            // FINAL EVALUATION:   ("horse")

            // If you used the initial expression here (with the parenthesis) in a different language, it would also evaluate to "horse."

echo $vehicle; // gives us "horse"

这与以下情况相反:
// EVERY OTHER LANGUAGE

var a = "T";
var vehicle =   a == "B" ? "bus" :
                a == "A" ? "airplane" :
                a == "T" ? "train" :
                a == "C" ? "car" :
                a == "H" ? "horse" : "feet";

                // (as seen by the other language's interpreter)
                // INITIAL EXPRESSION: (a == "B" ? "bus" : (a == "A" ? "airplane" : (a == "T" ? "train" : (a == "C" ? "car" : (a == "H" ? "horse" : "feet")))));
                // STEP 1:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : (FALSE ? "car" : (FALSE ? "horse" : "feet")))))
                // STEP 2:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : (FALSE ? "car" : "feet"))))
                // STEP 3:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : "feet")))
                // STEP 4:             (FALSE ? "bus" : (FALSE ? "airplane" : "train"))
                // STEP 5:             (FALSE ? "bus" : "train")
                // FINAL EVALUATION:   ("train")

                // If you used the initial expression here (with the parenthesis) in PHP, it would also evaluate to "train."

console.log(vehicle); // gives us "train"

如果您注意一下 PHP 示例,最内层的表达式在左边,而第二个示例中最内层的表达式在右边。每一步都会评估下一个最内层的表达式,直到得到单个结果。如果您要在 PHP 中嵌套三元操作符,括号显然非常重要!


2
我认为你对“其他语言”的表述不正确。据我所知,大多数问题都会懒惰地评估条件运算符的最后两个子表达式,因此很多这些表达式永远不会被评估。 - Alexander

0

我无法理解来自以下示例:

https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/

所以我来到这里,但我仍然无法理解它,因此我不得不逐步进行。

@amadan 拥有最好的答案,我个人认为。

这会打印出 horse,而不是 train。

// 0
$arg = 'T';
$vehicle = 
    $arg == 'B' ? 'bus' :
    $arg == 'A' ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;
// 1
$vehicle = 
>   FALSE       ? 'bus' :
    $arg == 'A' ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 2
$vehicle = 
    FALSE       ? 'bus' :
>   FALSE       ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 3
$vehicle = 
>   (FALSE? 'bus' : FALSE? 'airplane' : TRUE)? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 4
$vehicle = 
>   true ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 5 
$vehicle = 
>   ('train' : $arg == 'C') ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 6 
$vehicle = 
>   (true ? 'car' : $arg == 'H') ? 'horse' :
    'feet' ;

// 7 
$vehicle = 
>   (true) ? 'horse' : 'feet' ;

如果我理解正确的话,您可以在第5步中看到左结合的含义。


-1
如果您添加括号,问题将得到解决。请看以下示例:
如果没有括号,当分数大于50时,成绩始终为D,但对于分数<=49,则正常工作。
为了使程序按预期工作,我已经添加了括号。如果像这样输入,很容易知道要输入多少个括号。
<?php
 $marks_obtained = 65;
 $grade = null;
 //Use parentheses () otherwise the final grade shown will be wrong.
//Excluding the first line, for each additional line, 
//we add a parenthesis at the beginning of each line and a parenthesis at the end of the statement.
echo $grade = $marks_obtained >= 90 && $marks_obtained <= 100 ?  "A+"
              : ($marks_obtained <= 89 && $marks_obtained >= 80 ? "A"
              : ($marks_obtained <= 79 && $marks_obtained >= 70 ? "B"
              : ($marks_obtained <= 69 && $marks_obtained >= 60 ? "C"
              : ($marks_obtained <= 59 && $marks_obtained >= 50 ? "D"
              :                                                     "F"))))
?>

4
需要添加括号只是因为语言有点混乱,这有点偏离了重点的意思。 - Anthony

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