【问题标题】:Can anyone show an example where this precedence matters?任何人都可以举一个例子说明这个优先级很重要吗?
【发布时间】:2019-06-12 15:38:07
【问题描述】:

我正在阅读优先级和关联性。在table中,我观察到了这两件事——

(i) 后缀递增(或递减)运算符的优先级大于前缀递增(或递减)运算符的优先级。

(ii) 后缀 inc.(或 dec.) 运算符的结合性是从左到右的,而前缀 increment(或 dec.) 运算符的结合性是从右到左的。

我不确定为什么需要它。任何人都可以通过显示代码(分别为每个案例)来帮助我,这表明需要这两个事实?谢谢。

我试图思考案例但没有得到任何类似的案例(因为我对编程很陌生)。

【问题讨论】:

  • 请链接您所指的资源。
  • 在 C++ 中显示这一点很简单(例如,在 C++ 中允许使用 ++++c),C 更难。我绞尽脑汁想了一会儿也没有用。添加律师标签以吸引专家。
  • 我不确定我是否遵循。必须至少隐式​​地对这些属性进行一些 选择,以指定如何计算表达式。您是否要求为特定选择提供理由
  • 还要注意,C 实际上并没有定义运算符优先级或关联性。这些是语言标准的解释
  • 您真的要特别挑选修复前和修复后增量运算符吗?或者您只是想知道为什么通常需要运算符优先级?因为为每个运算符分配 一些 优先级是从一般情况出发...

标签: c language-lawyer operator-precedence


【解决方案1】:

这对于像这样的表达式是必需的

data = *pointer++;

您获取数据中指针的值,然后将其递增到下一个元素。如果后缀的优先级不是更大,您最终会得到一个递增的数据值。

并且前缀运算符的关联性是从右到左的,因为你想要这样的表达式

data = **pointer_to_pointer;

从右到左进行评估,就像你会写一样

data = *(*pointer_to_pointer);

【讨论】:

  • 出于好奇,&* 语法何时在 C++ 中有意义?我承认我对 C++ 知之甚少,但我无法理解 &*ptr 不是多余的语法。
  • 同意使用不同的优先规则,效果会有所不同,但这如何证明特定的优先选择是“需要的”?
  • C++ 有运算符重载,所以许多“微不足道”的表达式可能会做任意事情。但是,在回答 C 问题时考虑 C++ 是危险的 - C 和 C++ 之间在特定运算符优先级方面存在细微差别。
  • &*ptr 在 C++ 中有意义,如果 ptr 不是一个简单的指针,但可能有一个 operator* 返回对实际对象的引用,或代理引用的实例。 & 然后将该实例的地址作为原始指针,通常情况下,它也可以被重载。
  • 我将“需要”的问题理解为这些规则在现实世界中有意义的应用。不能作为唯一选择的理由。当然可以定义不同的语义。 @JohnBollinger
【解决方案2】:

在 C 中,与大多数语言一样,后缀运算符的绑定比前缀运算符的绑定更紧密,而前缀运算符的绑定比二元运算符的绑定更紧密。 (第二部分有例外,但在 C 中没有。)这通常对应于对表达式含义的直觉。

例如,几乎每个人都会期望

-a[0]

表示“数组a 的元素0 的负数”,而不是“数组-a 的元素0”,特别是在像C 这样的语言中,“数组-a”没有意义。同样,

-a++

具有预期的含义“a 的当前值的负数,随后递增。”同样,增加 -a 是没有意义的,因为 -a 不是变量。

幼稚的直觉可能不适用于更晦涩的运算符,因此保持一致性很有用。虽然可以想象可能存在一个前缀运算符,它几乎总是需要用括号括起来,因为它绑定不够紧密,使该运算符成为一般规则的例外,我们可能会产生比它解决的更多的惊喜,而且很少有语言走这条路。

因此++-- 的前缀和后缀用法具有由该通用规则定义的语法。尽管如此,至少在 C 中,将两者应用于同一个操作数 (++a--) 是错误的,因为无论是修复前版本还是修复后版本,返回的值都不是左值,而操作数需要是一个左值。从这个意义上说,比较前缀和后缀 ++-- 的优先级的特殊情况永远不会出现在正确的程序中。但是前缀和后缀运算符的其他组合可以,并且优先级需要同质应用。

在另一种意义上,优先级和关联性声明是多余的。谈论两个前缀运算符之间或两个后缀运算符之间的关联性在语法上毫无意义。并且如果前缀和后缀运算符具有不同的优先级,那么谈论前缀和后缀运算符之间的关联性也是没有意义的。所以关联性是无关紧要的。

但是,您可以扩展关联性的概念,并说所有一元运算符具有相同的优先级,并且它们都与右侧相关联。这实际上会产生一个正确的解析器,并在 B(C 的前身)的定义中使用。但对于大多数不习惯语法分析的人来说,这确实太令人困惑了。

【讨论】:

    【解决方案3】:

    (i) 后缀运算符的优先级大于 前缀运算符。

    考虑代码:

    int x[] = {1,2,3};
    int *p = x;
    int y;
    y = ++p[0]; 
    

    这将增加x 的第一个元素并将其分配给y。

    与此相反,我们明确更改优先级,因此前缀 ++ 获得更高的优先级:

    y = (++p)[0];
    

    不会增加x 元素,但会将p 移动到x 的第二个元素并为其分配y

    (ii) 后缀运算符的结合性是从左到右,而前缀运算符的结合性是从右到左。

    这意味着: p->x->y 应该读作(p->x)->y,而不是p->(x->y),这甚至没有任何意义。

    对于后缀组的其他其他运算符也是如此 - RTL 关联性对它们没有意义: p[x][y][z]((p[x])[y])[z] 相同,但 p[x]([y]([z])) 无意义(且非法)。

    【讨论】:

    • 有趣,我会把-> 称为一个中缀?
    • 我知道这一点,他们认为这是一个后缀让我感到惊讶。
    • @deets 运算符-> 不将表达式作为其右手操作数,而只是一个名称。由于名称已在编译时固定,因此在运行时 ->name 充当不可分割的单元。这个单元可以被视为一个后缀运算符。
    【解决方案4】:

    从历史的角度来看,运算符的优先级受到B and BCPL programming languages 的影响。在文章The Development of the C Language 中,Dennis Ritchie 解释了他是如何选择优先级的。

    语法上的意外导致了语言的复杂性。间接运算符,在 C 中拼写为 *,在语法上是一元前缀运算符,就像在 BCPL 和 B 中一样。这在简单表达式中效果很好,但在更复杂的情况下,需要括号来指导解析。例如,为了区分函数返回值的间接性和调用指针指定的函数,可以分别写成 *fp() 和 (*pf)()

    关于优先级没有明确的逻辑——你需要记住它或者当你不确定优先级时使用括号。

    【讨论】:

      【解决方案5】:

      运算符的优先级和结合性不属于语言语法。对于后缀和一元运算符,如下所示:

      postfix-expression:
          primary-expression
          postfix-expression [ expression ]
          postfix-expression ( argument-expression-listopt )
          postfix-expression . identifier
          postfix-expression -> identifier
          postfix-expression ++
          postfix-expression --
          ( type-name ) { initializer-list }
          ( type-name ) { initializer-list , }
      
      unary-expression:
          postfix-expression
          ++ unary-expression
          -- unary-expression
          unary-operator cast-expression
          sizeof unary-expression
          sizeof ( type-name )
          _Alignof ( type-name )
      
      unary-operator: one of
          & * + - ~ !
      
      cast-expression:
          unary-expression
          ( type-name ) cast-expression
      

      C 2011 Online Draft,附录 A.2 阶段结构语法

      那么,这如何确定优先级和关联性,为什么重要?让我们从*p++ 之类的表达式开始——我们是取消引用p++,还是递增*p?这是两个非常不同的操作,因此语法的结构很重要。让我们追溯一下:

              *            p            ++
              |            |            |
              |         primary         |
              |        expression       |
              |            |            |
              |         postfix         |
              |        expression       |
              |            |            |
              |            +------+-----+
              |                   |
              |                postfix
              |               expression
              |                   |
              |                 unary
              |               expression
              |                   |
            unary                cast
           operator           expression
              |                   |
              +---------+---------+
                        |
                      unary
                    expression
      

      英文:

      1. <em>unary-expression</em> 产生 <em>unary-operator cast-expression</em>
      2. <em>unary-operator</em> 产生 *
      3. <em>cast-expression</em> 产生 <em>unary-expression</em>
      4. <em>unary-expression</em> 产生 <em>postfix-expression</em>
      5. <em>postfix-expression</em> 产生 <em>postfix-expression</em> ++
      6. <em>postfix-expression</em> 产生 <em>primary-expression</em>
      7. <em>primary-expression</em> 产生 p

      这意味着表达式*p++ 被解析为*(p++) - * 运算符将应用于p++结果

      *p[i] 也是同样的事情——我们最终将取消引用 p[i] 处的指针,而不是下标 *p

      对于一个涉及关联性的稍微复杂的示例,让我们看一下成员选择运算符-&gt;,如表达式foo-&gt;bar-&gt;bletch-&gt;blurga-&gt; 成员选择运算符的语法是

      <em>postfix-expression</em> -&gt; <em>identifier</em>

      这告诉我们foo-&gt;bar-&gt;bletch 简化为postfix-expressionblurga 简化为identifier。因此,运算符的关联性是从右到左的,表达式解析为((foo-&gt;bar)-&gt;blurga)-&gt;bletch,而不是foo-&gt;(bar-&gt;(blurga-&gt;bletch))

      您正在查看的这些表是语法设置的摘要。语法是这样设置的,因此运算符和操作数分组有点直观。您希望像 ++foo.bar[i] 这样的表达式增加 foo.bar[i],您希望 *f() 取消引用从函数返回的指针值,等等。

      【讨论】:

        猜你喜欢
        • 2023-03-09
        • 1970-01-01
        • 2012-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-02
        • 1970-01-01
        相关资源
        最近更新 更多