V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
SimbaPeng
V2EX  ›  PHP

哪位 PHP 大神来解答一下优先级和结合性的问题???

  •  
  •   SimbaPeng · 2017-12-13 00:13:41 +08:00 · 3497 次点击
    这是一个创建于 2319 天前的主题,其中的信息可能已经有所发展或是发生改变。
    php version: 7.0.26
    不要问我这个问题有什么意义,谢谢,我只是单纯的想弄清楚 php 的优先级和结合性问题!!!


    首先来看一个例子:
    >>> $i = 1;
    => 1
    >>> $i + $i++;
    => 3

    这个很好理解,++优先级高于+, $i++这个表达式的值为 1,虽然是后置递增但$i++在跟$i 相加之前$i 已经完成了自增,所以是 2+1=3。


    再看一个例子:
    >>> $i = 1;
    => 1
    >>> $i + ++$i + $i++;
    => 6

    那谁能告诉我这个例子为什么等于 6 ?

    按照我的算法:

    1.应该先算最右边的$i++, 因为++优先级高于+, 然后++的结合性是从右到左,那么$i++这个表达式的值为 1,然后$i 递增为 2

    2.然后算++$i, $i 自增为 3,++$i 表达式的值也为 3

    3.然后就是 3 + 3 + 1 = 7

    想不通为什么等于 6,求大神解答。



    还有个问题:
    >>> function foo(){ return 0; }
    >>> if (!$a = foo()) { echo 1; }
    1⏎

    为什么这个例子不会报错?!的优先级不是比=号高么,为什么不是先算!$a???
    虽然官方文档说这种操作是允许的,但是这到底算什么啊,feature?
    第 1 条附言  ·  2017-12-13 00:55:08 +08:00
    >>> $i = 1;
    => 1
    >>> $i + ++$i + $i++;
    => 6

    抱歉,这个例子我搞错了, $i + ++$i + $i++ 应该会被当成 $i++ + $i + $i++, 是等于 6,2333333333.


    但是这样写为什么还是等于 6 呢:
    >>> $i = 1;
    => 1
    >>> $i + $i++ + ++$i;
    => 6

    按照我的算法:
    1. 先算 ++$i, ++$i 表达式值为 2,$i 自增为 2

    2.再算$i++, $i++表达式值为 2,$i 自增为 3

    3. 3 + 2 + 2 = 7
    34 条回复    2017-12-17 20:20:19 +08:00
    wwww961h
        1
    wwww961h  
       2017-12-13 00:23:31 +08:00
    !$a == foo()少了个=号,
    SimbaPeng
        2
    SimbaPeng  
    OP
       2017-12-13 00:27:50 +08:00
    @wwww961h 你可能没 get 到点,就是=号,他会把 foo()的返回值给$a, 再算!$a 的真假
    shiny
        3
    shiny  
       2017-12-13 00:32:24 +08:00
    @SimbaPeng 如果 f ( (!$a) = foo()) 就语法错误了
    SimbaPeng
        4
    SimbaPeng  
    OP
       2017-12-13 00:33:50 +08:00
    @shiny 是啊,我就是问为什么不报错,!的优先级比=高,不用打括号就该先算阿
    shiny
        5
    shiny  
       2017-12-13 00:38:45 +08:00
    @SimbaPeng 手册里加了 note,这算是个例外,原因不知道。http://php.net/manual/zh/language.operators.precedence.php
    gbin
        6
    gbin  
       2017-12-13 01:26:24 +08:00 via Android
    ++$i 表达式为 2,表达式的取值为自增之前的值。是这样吧?
    gbin
        7
    gbin  
       2017-12-13 01:30:59 +08:00 via Android
    gbin
        8
    gbin  
       2017-12-13 01:35:44 +08:00 via Android
    #6,#7 瞎了,没看到 APPEND。请忽略!
    hjzx050935
        9
    hjzx050935  
       2017-12-13 01:40:22 +08:00
    http://www.php-internals.com/book/?p=chapt02/02-03-01-lex-and-yacc

    深入一点可以看看 php 怎么生成抽象语法树的
    jfcherng
        10
    jfcherng  
       2017-12-13 02:02:37 +08:00
    居然允許 `if (!$a = foo()) { echo 1; } `... 根本是個坑
    abusizhishen
        11
    abusizhishen  
       2017-12-13 02:38:29 +08:00 via Android
    对应的值是 2+3+1=6,数字的大小也是计算的顺序
    ryd994
        12
    ryd994  
       2017-12-13 04:25:57 +08:00 via Android
    死背这个真没用
    多写括号,良好缩进,自己方便,看的人也方便
    zjsxwc
        13
    zjsxwc  
       2017-12-13 08:39:32 +08:00
    看文档: https://secure.php.net/manual/en/language.operators.increment.php

    $i + ++$i + $i++;
    = ($i + ++$i + $i), ($i+=1)
    =2 + 2 + 2
    zjsxwc
        14
    zjsxwc  
       2017-12-13 08:44:46 +08:00
    $i*1 + ++$i + $i++;
    的结果是 5

    还是多谢几行代码好了, 这种晦涩的表达式完全是自己找死
    sagaxu
        15
    sagaxu  
       2017-12-13 09:05:34 +08:00 via Android
    写了十几年代码,也没弄明白这种事情。因为我是多语言用户,要考虑这种写法在不同语言之间的差异,还要提防未定义行为,干脆就避开不用了,反正括号和换行不要钱
    lights
        16
    lights  
       2017-12-13 09:08:08 +08:00 via iPhone
    从第一天学代码,我从来不主动写这样晦涩难懂的代码。
    WytheHuang
        17
    WytheHuang  
       2017-12-13 10:29:02 +08:00
    还是加括号吧, 有时候真的难以理解的
    ```
    $i = 1;
    echo $i + $i + $i++; // 3
    $i = 1;
    echo $i + $i++ + $i; // 5
    ```
    wwww961h
        18
    wwww961h  
       2017-12-13 23:51:20 +08:00   ❤️ 1
    @SimbaPeng 个人认为,如果一直纠结这些东西,你这辈子不需要提升了,还是研究点有用的东西吧,
    humpy
        19
    humpy  
       2017-12-15 16:32:50 +08:00
    要知道这个表达式「$i + $i++ + ++$i 」的求值顺序,应用三条语法规则就清楚了:

    1. 「+」 是双目运算符
    2. 同一表达式中多个「+」 的结合方向是从左到右「同一行中的运算符具有相同优先级,此时它们的结合方向决定求值顺序」
    3. 递增「 i++/++i 」优先级高于 「+」

    所以这个表达式的求值顺序即为:
    1. $i + $i++
    = $i++ + $i
    = 1 + 2
    = 3

    2. 3 + ++$i
    = ++$i + 3
    = 3 + 3
    = 6

    实际上你将它们的 opcode dump 出来就很清晰了:
    L2 #0 EXT_STMT
    L2 #1 ASSIGN $a 1
    L4 #2 EXT_STMT
    L4 #3 POST_INC $a ~1
    L4 #4 ADD $a ~1 ~2
    L4 #5 PRE_INC $a @3
    L4 #6 ADD ~2 @3 ~4
    L4 #7 ECHO ~4
    L6 #8 RETURN 1
    SimbaPeng
        20
    SimbaPeng  
    OP
       2017-12-15 17:47:54 +08:00
    @humpy ++的结合性是从右到左,为什么不是先算 ++$i 呢?
    humpy
        21
    humpy  
       2017-12-15 18:09:16 +08:00
    至于这个问题,
    >>> function foo(){ return 0; }
    >>> if (!$a = foo()) { echo 1; }
    1⏎

    刚才翻了一下源码,似乎是 parser 定义的锅...

    Zend/zend_language_parser.y :

    | '!' expr { $$ = zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BOOL_NOT, $2); }

    「!$a = foo()」这条语句,在语法解析时,会先解析 「$a = foo()」为赋值语句,然后再匹配到 「'!' expr 」

    另外,根据这里的语法定义,似乎「~」运算符也有同样的问题...
    humpy
        22
    humpy  
       2017-12-15 18:10:48 +08:00
    @SimbaPeng + 是双目运算符,++ 是单目运算符
    SimbaPeng
        23
    SimbaPeng  
    OP
       2017-12-15 18:13:52 +08:00
    @humpy 单目运算符有什么问题么,++运算符的优先级高于+,不应该把所有++算完了,再相加起来吗?
    humpy
        24
    humpy  
       2017-12-15 18:19:58 +08:00
    @SimbaPeng 你对单目 /双目运算符的理解有一点偏差

    单目运算符的意思就是它只需要一个 oprand 就可以组成一个表达式
    双目运算符需要两个 oprand

    对于这个表达式「$i + $i++ + ++$i 」,按照语法规则,转换一下就清晰了:

    $a = $i++
    $b = ++$;

    $i + $i++ + ++$i = $i + $a + $b = ($i + $a) + $b
    SimbaPeng
        25
    SimbaPeng  
    OP
       2017-12-15 20:32:36 +08:00
    @humpy 单目 /双目运算符我知道,你还是没告诉我+号为什么会在第二个++之前运算?你这么说我好迷茫啊,我对优先级的理解是整个表达式中优先级最高的运算符先计算,然后依次计算优先级低的, 同级别按结合性。
    比如:

    2 + 3 * 2 + 4 / 2

    这个表达式不是先算 3 * 2 再算 4 / 2 , 最后再相加吗?

    难道是 先算 3 * 2 然后加上 2 , 再算 4 / 2,再相加?

    虽然结果都一样

    但是优先级到底是什么时候触发的?
    humpy
        26
    humpy  
       2017-12-15 23:11:21 +08:00
    @SimbaPeng 我明白你的意思,但是对编程语言的求值顺序不能按自己的数学习惯套,你把运算符优先级和实际求值顺序搞混了。运算符优先级高不代表它的求值更早,优先级只是说明某些运算符比另一些运算符的「组合能力」更强,是中缀程序语言为了避免歧义的设计,它处于语法设计层面;求值顺序更多是语言实现层面的事,但因为求值过程中可能会产生副作用,为了避免程序员掉坑,所以文档里会写上。

    这是顶楼表达式的语法树,求值顺序就是后序遍历的顺序;如果按照你所期望的顺序求值,你觉得应该怎么遍历这颗语法树呢?
    SimbaPeng
        27
    SimbaPeng  
    OP
       2017-12-16 04:14:07 +08:00
    @humpy 我明白了,一直以为优先级就是求值顺序,原来优先级只是确定操作数的组合方式,并不等于求值顺序,求值顺序是不确定的,副作用的发生时间也是不确定的,由编译器的行为所决定。除了 && || , 等等这些操作符。所以这题应该是没有答案,4,5,6,7 都可以。。。
    xiaohanqing
        28
    xiaohanqing  
       2017-12-16 10:46:06 +08:00 via Android
    写这样的东西就是坑自己,研究透了有什么用,就算 !$a 先执行也不是报错啊,就是个警告
    SimbaPeng
        29
    SimbaPeng  
    OP
       2017-12-16 12:24:13 +08:00
    @xiaohanqing 研究透了才知道自己缺的是编译原理方面的知识懂吗?肯定得买本书补补了。写这种东西谁不知道是坑人?关键是自己不去弄懂这些东西就是坑自己,拈轻怕重谁不会?不知道不可怕,可怕的是自己根本不想去知道。

    怕是也只有 php 在使用未定义的变量时只是报个 Notice。。。
    sagaxu
        30
    sagaxu  
       2017-12-16 22:48:59 +08:00 via Android
    @SimbaPeng 最好直接去学编译原理,而不是从连个语言规范都没有的 php 中去悟。
    SimbaPeng
        31
    SimbaPeng  
    OP
       2017-12-16 23:59:54 +08:00
    @sagaxu 谁说我从 php 中去悟了,php 的文档也不会去讲这些东西。我是看到 humpy 道友的解释,加上自己查 c 语言的书关于优先级那一张才弄明白的,只是刚好在 php 的这个问题上发现自己不足而已,php 的语法不规范刚好暴露了我自己对优先级理解的不透彻。没有疑惑就没一探究竟的动力。
    cxbig
        32
    cxbig  
       2017-12-17 09:12:37 +08:00
    谁在工作中这样写逻辑的?
    探索这些不如多来几个括号解决实际问题,至少别人审阅代码也不用想太多。
    SimbaPeng
        33
    SimbaPeng  
    OP
       2017-12-17 18:57:38 +08:00   ❤️ 1
    @cxbig 我在整个帖子中有说过有人会在工作中这样写吗?还是我鼓励你们这样写了?我在这里请教一下语法层面的东西跟我在实际工作中如何去写有关系吗?有冲突吗?我在这里请教为什么,你们却过来告诉我怎么做?答非所问?语文理解能力差?

    另外你要是觉得我在这讨论的东西在你眼里不值一提,你大可一笑而过,至少我看到<<C 程序设计语言>>这本书里用了一页纸来说明求值顺序和操作符的副作用。
    mingyun
        34
    mingyun  
       2017-12-17 20:20:19 +08:00
    貌似面试喜欢考这些。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5265 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 08:39 · PVG 16:39 · LAX 01:39 · JFK 04:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.