C语言中函数调用运算符的结合性

31

我正在学习C运算符的结合性。

在此过程中,我了解到函数调用运算符()是从左到右结合的。但是,结合性只有在表达式中出现多个具有相同优先级的运算符时才起作用。然而,我找不到任何涉及函数调用运算符且结合性起着关键作用的例子。

例如,在语句a = f(x) + g(x);中,结果取决于求值顺序而不是两个函数调用的结合性。类似地,调用f(g(x))将首先计算函数g(),然后计算函数f()。这里我们有一个嵌套的函数调用,再次结合性并不起任何作用。

这个优先级组中的其他C运算符是数组下标[]后缀++后缀--。但我找不到任何结合这些运算符与()的组合的例子,其中结合性在表达式求值中起作用。

因此,我的问题是函数调用的结合性被定义为从左到右是否会影响C中的任何表达式?能否提供一个示例,说明函数调用运算符()的结合性在表达式求值中起作用?


如果f本身就是一个表达式(比如从数组中选择的函数指针),该怎么办? - EOF
2
任何返回函数指针的函数都可以作为一个例子,因为使用函数调用运算符时不需要对函数指针进行解引用。所以你可以像f()()()()这样开始编写代码,它是合法的C语言。请参考格热戈日的答案,获取一个可能的示例。 - Filipe Gonçalves
28个赞……它必须从左到右。你想生活在这样一个世界里吗,其中f(a)(b)是对f的调用,传递b,然后使用a调用结果(也就是说(f(b))(a))- 你想吗? - Alec Teal
我不同意现有的答案,无论它们是否被投票赞成。它们确实很好地解释了一些基础知识,但是这个答案深入得多。是的,我们可以就C语法区分“主要”和“一元”运算符进行长时间的争论,但这种区分只是语法的产物。 - Jirka Hanika
3个回答

45

以下是一个示例,其中函数调用操作符的左右结合性很重要:

#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())();

1
太棒了。非常感谢你。 - Deepu
1
处理所有前缀或后缀一元运算符时,结合律在什么意义上是相关的?我认为它主要与前缀和后缀运算符都应用于同一项的情况有关,例如 *foo()。我无法想出任何定义 foo 的方式,使得 (*foo)()*(foo()) 在 C 中都合法(这种情况可能存在于 C++ 中),但是每种形式在某些情况下都是合法的,只有后者可以替换为 *foo(),这说明了 *() 的相对结合性。 - supercat
1
@GrzegorzSzpetkowski:在我看来,如果它们真的是运算符,那么就可以编写类似于 foo -> (expr ? bar: boz) 的代码,并且其含义等同于 expr ? foo->bar : foo->boz(实际上,想一想,让C语言允许这样做可能会使某些类型的代码更易读,而且不会使任何当前合法的结构模糊;但是,这将严重违反“不发明新事物”的规则)。然而,我认为更有用的是说二元运算符连接表达式,因此 .-> 不是二元运算符,而不是将这些东西视为... - supercat
1
@supercat:->.是运算符,但它们只有一个表达式操作数,位于左侧。右侧是一个标识符而不是表达式。因此,它们实际上是后缀一元运算符,而不是二元运算符。 - R.. GitHub STOP HELPING ICE
2
这并不是函数调用符号结合性很重要的一个例子。除了(bar())()之外,bar()()没有其他可能的解释方式;值得注意的是,bar(()())不是选项,不仅因为现在bar的参数不是语法上正确的,更根本的原因是现在添加的括号变成了“函数调用符号”本身,不能省略。所以,右(至左)结合性就没有任何意义。对于其他后缀运算符,它们也不会具有关联性,而是左关联;同样地,_前缀_运算符必须是右关联的。 - Marc van Leeuwen
显示剩余4条评论

15

除了 @GrzegorzSzpetkowski 的答案之外,您还可以尝试以下方法:

void foo(void) { }

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

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


好的回答。谢谢。 - Deepu
2
еҰӮжһңж•°з»„дёӯзҡ„йӮЈдәӣеҮҪж•°жҳҜint* (*p[1])(int)пјҢйӮЈд№ҲдҪ еҸҜд»ҘдҪҝз”Ёp[0](1)[2];гҖӮиҺ·еҸ–第дёҖдёӘеҮҪж•°жҢҮй’ҲпјҢз”ЁеҸӮж•°1и°ғз”Ёе®ғпјҢ并иҺ·еҸ–иҝ”еӣһзҡ„第дёүдёӘж•°з»„е…ғзҙ гҖӮCиЎЁиҫҫејҸзҡ„еӨҚжқӮеәҰеҮ д№ҺжІЎжңүйҷҗеҲ¶гҖӮ - MSalters

3
函数应用左结合的说法在C语言定义中完全没有必要。这种“关联性”是因为函数参数列表出现在函数表达式本身右侧并且必须用括号括起来。这意味着对于给定的函数表达式,其参数列表的范围从函数表达式后面的强制性开括号一直延伸到匹配的闭括号,永远不会有任何疑问。
函数表达式(通常只是一个标识符,但可能更加复杂)本身可能是一个(裸)函数调用,例如f(n)(1,0)。(当然,这需要f(n)返回值是可以使用参数列表(1, 0)进行调用的,C语言有一些可能性,而C ++则有更多,但这些都是语义上的考虑,在解析之后,它们应该被忽略以进行关联性讨论)。这意味着函数调用的语法规则是左递归的(函数部分本身可以是函数调用); 这可以通过说“函数调用左结合”来表述,但事实是显而易见的。相比之下,右部分(参数列表)不能是(裸)函数调用,因为需要使用括号,所以不能有右递归。
例如,考虑(a)(b)(c)(我加入了冗余的括号以暗示对称性和可能的歧义)。在这里,(b)(c)本身可能被视为使用参数c调用了冗余括号的b; 然而,这样的调用不能被认为是a的参数,因为那需要额外的括号,如(a)((b)(c))。因此,在没有提到关联性的情况下,很明显(a)(b)(c)只能意味着使用参数b调用a,并使用参数c调用结果值。

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