【问题标题】:Associativity of function call operator in CC中函数调用运算符的关联性
【发布时间】:2015-11-02 06:22:21
【问题描述】:

我正在讨论 C 运算符的关联性。

我发现函数调用运算符() 具有从左到右的关联性。但是只有当一个表达式中出现多个具有相同优先级的运算符时,关联性才会发挥作用。但我找不到任何涉及函数调用运算符的示例,其中关联性起着至关重要的作用。

例如在语句a = f(x) + g(x); 中,结果取决于评估顺序,而不取决于两个函数调用的关联性。 同样,调用f(g(x)) 将首先评估函数g(),然后是函数f()。这里我们有一个嵌套的函数调用,并且关联性再次不起作用。

此优先级组中的其他 C 运算符是数组下标 []postfix ++postfix --。但是我找不到任何涉及这些运算符与() 组合的示例,其中关联性在表达式评估中起作用。

所以我的问题是函数调用的关联性被定义为从左到右是否会影响 C 中的任何表达式?谁能提供一个例子,说明函数调用运算符 () 的关联性在表达式评估中很重要吗?

【问题讨论】:

  • 如果f 本身就是一个表达式(比如从数组中选择的一个函数指针)怎么办?
  • 任何返回函数指针的函数都可以用作示例,因为函数调用运算符不需要函数指针解引用。因此,您开始拥有f()()()() 之类的东西,它是有效的 C。有关可能的示例,请参见 Grzegorz 的答案。
  • 28 upvotes.... 它必须从左到右。你想生活在一个f(a)(b) 调用f 传递b 然后用a 调用结果的世界中(也就是说(f(b))(a)) - 你愿意吗?
  • 我不同意现有的答案,无论是否投票。他们确实很好地解释了一些基础知识,但this answer 更深入地方式。是的,我们可以就 C 语法区分“主要”和“一元”运算符进行长时间的争论,但这种区分也只是语法的产物。

标签: c associativity


【解决方案1】:

函数应用程序关联到左边的语句在 C 语言定义中完全是多余的。函数参数列表出现在函数表达式本身的右侧并且必须用括号括起来这一事实暗示了这种“关联性”。这意味着对于给定的函数表达式,它的参数列表的范围永远不会有任何疑问:它从函数表达式之后的强制左括号延伸到匹配的右括号。

函数表达式(通常只是一个标识符,但可能更复杂)本身可能是一个(裸)函数调用,如f(n)(1,0)。 (当然,这要求f(n) 返回的值是可以用参数列表(1,0) 调用的东西,C 有一些可能性,C++ 更多,但这些都是语义上的考虑,只能发挥作用解析后,因此在讨论关联性时应该忽略它们。)这意味着函数调用的语法规则是左递归的(函数部分本身可以是函数调用);这可以通过说“函数调用关联到左侧”来表述,但事实是显而易见的。相比之下,右部分(参数列表)不能是(裸)函数调用,因为需要括号,所以不能有右递归。

例如考虑(a)(b)(c)(我在其中添加了多余的括号以表明对称性和可能的​​歧义)。这里 (b)(c) 本身可能被认为是带有参数 cb 的调用(用括号括起来);然而,这样的调用不能被解释为a 的参数,因为这需要额外的括号,如(a)((b)(c))。所以不提任何关联性,很明显(a)(b)(c)只能表示a用参数b调用,结果值用c参数调用。

【讨论】:

    【解决方案2】:

    除了@GrzegorzSzpetkowski 的回答,你还可以有以下:

    void foo(void) { }
    
    int main(void) {
        void (*p[1])(void);
        p[0] = foo;
        p[0]();
        return 0;
    }
    

    这将创建一个函数指针数组,因此您可以将数组下标运算符与函数调用运算符一起使用。

    【讨论】:

    • 不错的答案。谢谢。
    • 如果数组中的那些函数是int* (*p[1])(int),你可以有p[0](1)[2];。获取第一个函数指针,使用参数 1 调用它,然后获取返回的第三个数组元素。 C 表达式的复杂性几乎不受限制
    【解决方案3】:

    这是一个例子,函数调用运算符的左右关联性很重要:

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo");
    }
    
    void (*bar(void))(void) // bar is a function that returns a pointer to a function
    {
        puts("bar");
        return foo;
    }
    
    int main(void)
    {
        bar()();
    
        return 0;
    }
    

    函数调用:

    bar()();
    

    相当于:

    (bar())();
    

    【讨论】:

    • 太棒了。非常感谢。
    • 在处理全是前缀或全是后缀的一元运算符时,关联性在什么意义上是相关的?我认为它主要适用于前缀和后缀运算符都应用于同一项目的情况,如*foo()。我想不出任何foo 的定义可以允许(*foo)()*(foo()) 在C 中是合法的(这样的事情可能存在于C++ 中),但事实上每种形式在某些情况下都是合法的并且只有后者可以替换为*foo() 才能证明*() 的相对关联性。
    • @GrzegorzSzpetkowski:在我看来,如果他们真的是运营商,可能会写成foo -&gt; (expr ? bar: boz)这样的东西,并且它的含义相当于expr ? foo-&gt;bar : foo-&gt;boz(实际上,想想它让 C 允许这可以使某些类型的代码更易读,并且不会使任何当前合法的结构模棱两可;但是,它会严重违反“无新发明”规则)。尽管如此,我认为说二元运算符连接表达式更有用,因此 .-&gt; 不是二元运算符,而不是考虑这些事情......
    • @supercat: -&gt;. 是运算符,但它们在左侧只有一个表达式操作数。 RHS 是一个标识符,但不是一个表达式。因此,它们实际上是后缀一元运算符,而不是二元运算符。
    • 这不是函数调用运算符的关联性很重要的示例。除了(bar())() 之外,没有其他方法可以将bar()() 解释为;值得注意的是bar(()()) 是没有选择的,不仅仅是因为现在bar 的参数不是语法,更根本的是因为现在添加的括号本身变成了“函数调用运算符”,并且不能省略。所以从右到左的关联性没有任何意义。其他左关联的后缀运算符也不会。类似地,prefix 运算符必然是右关联的。
    猜你喜欢
    • 1970-01-01
    • 2013-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 2017-05-22
    • 2011-02-23
    相关资源
    最近更新 更多