运算符关联性、优先级

Operator associativity, precedence

提问人:Franc 提问时间:2/1/2021 最后编辑:Priyanshul GovilFranc 更新时间:2/1/2021 访问量:321

问:

我只是想知道,对于以下代码,编译器是否单独使用关联性/优先级或其他一些逻辑来计算。

int i = 0, k = 0;

i = k++;

如果我们根据关联性和优先级进行计算,则具有比 更高的优先级,因此(成为)首先被计算,然后是 ,现在 k 的值被分配给 。postfix ++=k++1=1i

所以 和 的值将是 。但是,和 的值。ik1i is 0k is 1

所以我认为编译器将其一分为二。所以这里编译器不去执行语句关联性/优先级,它也拆分了行。有人可以解释编译器如何解析这些类型的语句吗?i = k++;(i = k; k++;)

C 赋值-运算符 后增 关联性

评论

0赞 chux - Reinstate Monica 2/1/2021
“ 所以 k++(变成 1)” --> 不完全是。 变为 1,但其先前值 0 被“返回”。k
0赞 Jabberwocky 2/1/2021
“所以我认为编译器拆分了这个 i = k++;分为两个 i = k ; k++;”。你试过了吗?
4赞 kaylum 2/1/2021
它实际上不需要在编译器级别进行解释。该行为由 C 语言规范强制执行。从规范中:后缀 ++ 运算符的结果是操作数的值。作为副作用,操作数对象的值递增,然后有关优先级顺序指定的部分将如前所述。++=
1赞 klutt 2/1/2021
它与优先级无关。我与运算符返回的值有关。
0赞 Franc 2/1/2021
@kaylum,你能告诉我规格的链接吗?那是 C11 (ISO/IEC 9899:2011) 吗?

答:

0赞 Devolus 2/1/2021 #1

它类似于(仅带有一个额外的序列点用于说明):

i = k; // i = 0
k = k + 1;  // k = 1

评论

1赞 Eric Postpischil 2/1/2021
这是不一样的,因为它在赋值和增量之间有一个序列点,而原始代码中不存在该序列点。Stack Overflow 看到大量重复的关于运算符的问题,这些问题是因为学生不了解排序的重要性和影响而出现的,因此正确教授它很重要。++
1赞 Vlad from Moscow 2/1/2021 #2

后缀运算符的优先级高于赋值运算符。

此表达式与赋值运算符

i = k++

包含两个操作数。

它等效地可以重写,就像

i = ( k++ );

表达式的值为 。所以变量将得到值。k++0i0

赋值运算符的操作数可以按任何顺序计算。

根据 C 标准(6.5.2.4 后缀递增和递减运算符)

2 后缀 ++ 运算符的结果是操作数的值。 作为副作用,操作数对象的值递增( 是,将适当类型的值 1 添加到其中)。

和 (6.5.16 赋值运算符)

3 赋值运算符将值存储在由 左操作数。赋值表达式的值为 left 赋值后的操作数,111),但不是左值。的类型 赋值表达式是左操作数后面的类型 左值转换。更新储值的副作用 左操作数在左操作数的值计算之后排序 和正确的操作数。操作数的评估是未排序的。

评论

1赞 Eric Postpischil 2/1/2021
这篇文章从来没有回答过这个问题;它从未解释增量是与主要评估分开的操作。它确实引用了标准,但没有解释它或说明为什么引用是相关的,并且只是在关于优先级和重写表达式的无用文本之后。(OP 不会混淆 应用于 的事实,这将受到优先级的影响,但增量不会影响用于 的值,这是运算符的特征,而不是优先级。然后这个答案继续引用一个无关紧要的段落。++kk
0赞 klutt 2/1/2021 #3

与 C++ 不同,C 没有“通过引用传递”。只有“按值传递”。我要借用一些C++来解释。让我们将 ++ 的功能实现为后缀和前缀作为常规函数:

// Same as ++x
int inc_prefix(int &x) { // & is for pass by reference
    x += 1;
    return x;
}

// Same as x++
int inc_postfix(int &x) {
    int tmp = x;
    x += 1;
    return tmp;
}

因此,您的代码现在等同于:

i = inc_postfix(k);

编辑:

对于更复杂的事情,它并不完全等价。例如,函数调用引入了序列点。但以上足以解释 OP 会发生什么。

评论

0赞 Eric Postpischil 2/1/2021
显示的代码不等效。函数调用引入了序列点,从而彻底改变了语义。
2赞 Eric Postpischil 2/1/2021 #4

++做两件不同的事情。

k++做两件事:

  • 它的值为 before 执行任何增量。k
  • 它递增 .k

这些是分开的:

  • 产生 的值 occurs 作为 的主要评估的一部分。ki = k++;
  • 递增是一种副作用。它不是主要评估的一部分。程序可以在计算表达式的其余部分后或在计算期间增加 的值。它甚至可以在表达式的其余部分之前递增值,只要它“记住”要用于表达式的预增值。kk

不涉及优先级和关联性。

这实际上与优先级或关联性无关。运算符的增量部分始终与表达式的主计算分开。使用的值始终是增量前的值,而不管存在其他运算符。++k++k

补充

重要的是要明白,增量部分与主评估分离,并且在时间上“漂浮”——它没有锚定在代码中的某个点,你无法控制它何时发生。这很重要,因为如果操作数有其他用途或修改,例如 ,增量可以发生在对其他事件的主要计算之前、期间或之后。发生这种情况时,C 标准不会定义程序的行为。++k * k++

0赞 Lundin 2/1/2021 #5

运算符关联性在这里不适用。运算符优先级仅表示哪个操作数坚持使用哪个运算符。在这种情况下,它不是特别相关,它只是说表达式应该被解析为而不是作为,这没有任何意义。i = (k++);(i = k)++;

从那时起,如何计算/执行此表达式由每个运算符的特定规则指定。后缀运算符的行为方式为 (6.5.2.4):

结果的值计算在副作用之前进行排序 更新操作数的存储值。

也就是说,保证计算到 然后在以后的某个时间点增加 1。我们真的不知道什么时候,只知道它发生在被评估的点之间,但在下一个序列点之前,在本例中发生在行的末尾。k++0kk++;

赋值运算符的行为类似于 (6.5.16):

更新左操作数的存储值的副作用是 在左操作数和右操作数的值计算之后排序。

在本例中,右操作数 的值在更新左操作数之前计算。=

在实践中,这意味着可执行文件可以如下所示:

  • k计算结果为 0
  • 设置为 0i
  • 增加 1k
  • 分号/序列点

或者这个:

  • k计算结果为 0
  • 增加 1k
  • 设置为 0i
  • 分号/序列点
0赞 Steve Summit 2/1/2021 #6

这里的根本问题是,优先级不是思考什么的正确方式

i = k=+;

方法。

让我们谈谈实际含义。的定义是,if 给你 的旧值 ,然后在 的存储值上加 1。(或者,换一种说法,它取旧值 ,加 1,并将其存储回 中,同时为您提供旧值 。k++k++kkkkk

就表达式的其余部分而言,重要的是 is 的值是什么。所以当你说k++

i = k++;

“什么被存储在?”这个问题的答案是,“的旧值”。ik

当我们回答“什么被存储在?”这个问题时,我们根本不考虑优先级。我们考虑后缀运算符的含义。i++

另请参阅这个较旧的问题

后记:另一件你必须非常小心的事情是,当你考虑一个附带问题时,“它什么时候将新值存储到?事实证明,这是一个很难回答的问题,因为答案并不像你希望的那样明确。新值被存储回它所在的较大表达式结束之前的某个时间(形式上是“在下一个序列点之前”),但我们不知道它发生在之前还是之后,比如说,事物被存储到的点,或者在表达式中其他有趣的点之前或之后。kki

-1赞 Priyanshul Govil 2/1/2021 #7

啊,这是一个非常有趣的问题。为了帮助您更好地理解,这就是实际发生的情况。

我将尝试使用C++中的一些运算符重载概念进行解释,因此,如果您不了解C++,请耐心等待。

这是使运算符过载的方式:postfix-increment

int operator++(int) // Note that the 'int' parameter is just a C++ way of saying that this is the postfix and not prefix operator
{
    int copy = *this;  // *this just means the current object which is calling the function
    *this += 1;
    return copy;
}

从本质上讲,运算符的作用是创建操作数的副本,增加原始变量,然后返回副本postfix-increment

在您的例子中,确实首先发生,但返回的值实际上是(将其视为函数调用)。然后,这将被分配给 。i = k++k++ki

评论

0赞 Jabberwocky 2/1/2021
假设他不懂C++,你如何期望问C问题的人理解你的答案?
0赞 Priyanshul Govil 2/1/2021
@Jabberwocky我想我已经解释了流程执行的实际工作原理。此外,这是一段非常不言自明且直观的代码。我认为任何人都不应该有问题从中抽象出细节。
0赞 John Bode 2/1/2021 #8

优先级和关联性仅影响运算符和操作数之间的关联方式,而不会影响表达式的计算顺序。优先规则规定

i = k++

解析为

i = (k++)

而不是类似的东西

(i = k)++

后缀运算符具有结果副作用。在表达式中++

i = k++

结果是 的当前值,它被赋值给 。副作用是递增。k++kik

这在逻辑上等同于写作

tmp = k
i = tmp
k = k + 1

需要注意的是,分配和更新可以按任何顺序进行 - 操作甚至可以相互交错。重要的是获取增量之前的值,并且该值会递增,而不一定是这些操作发生的顺序。ikikk