三元条件运算符和赋值运算符优先级

35

我对直接赋值和三元条件运算符的优先级感到困惑:

#include<stdio.h>
int main(void)
{
    int j, k;

    j = k = 0;
    (1 ? j : k) = 1; // first
    printf("%d %d\n", j, k);

    j = k = 0;
    1 ? j : k = 1; // second
    printf("%d %d\n", j, k);
    return 0;
}

我希望输出结果为:

1 0
1 0

但是事实上是:

1 0
0 0

此外,我还收到了以下警告:

main.cpp:20: 警告:语句没有效果

这个警告是关于我在第二行注释的代码。

由于直接赋值运算符的优先级低于三目条件运算符,因此我原以为注释为第一行和第二行的代码等价。但不幸的是,情况并非如此。

我使用 g++ --version (Ubuntu 4.4.3-4ubuntu5) 4.4.3 尝试过这个问题。


6
C++是神器还是野兽?我用它编程已经有一半的人生了,但仍然会被一些简单表达式的含义搞糊涂。我不得不查询标准文件才能弄懂。 - Suma
6个回答

22
C/C++语言中的操作符优先级并不是由表格或数字定义的,而是由语法规定的。下面是来自C++0x draft5.16 Conditional operator [expr.cond]章节的条件运算符语法:
conditional-expression:
    logical-or-expression
    logical-or-expression ? expression : assignment-expression
因此,像这样的优先级表在左边使用赋值时是正确的,但在右边使用时则不正确。我不知道这种不对称的原因是什么,可能是历史原因:在C语言中,条件结果不是lvalue,因此对其进行赋值没有意义,在那个时候允许不用括号接受赋值可能看起来很聪明。

1
为什么将链接表(将?:和=组合在一起)放在冒号的右侧是错误的? - foxcub
2
@foxcub 当前版本的表格是正确的。在表格中,赋值运算符和条件运算符具有相同的优先级,并且结合性是从右到左的,这使得赋值操作在条件运算之前正确执行。答案时该表格是不正确的,原因是表格记录的时候存在错误,将条件运算符的优先级高于赋值运算符。 - Suma
C++的语法被定义为句法语法,但该语法的设计旨在与简单的优先级表保持一致。你说这个表在“冒号左侧使用时不正确”,但是按照这种推理,在表达式中使用a[b+c]时,它也会在]的左侧使用时不正确。优先级表的要求是,当两个运算符看到一个期望操作数的子表达式时,将其交给具有更高优先级的运算符(并通过结合性来解决平局)。这个要求从未被违反。?:=只是具有相同的优先级。 - Museful
这不是正确的。三元条件运算符和赋值运算符具有相等的优先级,并且始终从右到左进行评估。 - user21508463

14
第二行等价于:
1 ? (j) : (k = 1);

那就相当于:

j;

这与以下代码等价:

;
关键在于三元条件运算符的两个操作数可以是表达式,因此运算符优先级在这里不相关。只是第二个操作数是赋值表达式 k = 1

nod 这个问题根本与运算符优先级无关,而是因为 ?: 是一个短路运算符。 - Kaz Dragon
@KazDragon:我澄清了 - 关键是操作数是表达式,因此第二个操作数是整个赋值表达式。(顺便说一下,所有标准逻辑二元运算符都是短路的。短路指的是条件的评估,而不是结果语句。) - Kerrek SB
7
你说得没错,但问题是:为什么(j) : (k = 1)等价于1,而不是(1 ? j : k) = 1,因为条件运算符应该比赋值运算符的优先级高? - Suma
@Suma:这里与“优先级”无关,因为只涉及一个运算符,其操作数是表达式。你已经引用了解释相关部分语法的内容。 - Kerrek SB
3
有多个运算符参与其中(赋值也是一种运算符)。您并没有解释为什么赋值表达式优先于条件表达式(并且通常使用的优先级表也没有解释这一点)。 - Suma
@Suma:我明白了。你有什么建议? - Kerrek SB

9
(1 ? j : k) = 1;

等同于,
if(true) j = 1;
else k = 1;

同时,

1 ? j : k = 1;

等同于,
if(true) j;  // warning: statement has no effect
else k = 1;

3
在第二种情况下,
1 ? j : k = 1;

被评估为:

(1) ? (j) : (k = 1);

由于一个值为true,表达式的计算结果为j,它并不起作用。


0

之前的回答解释了结果,但并没有消除疑惑。我会尝试给出我的理解。如果有任何错误,请指出。

编译器可能按以下方式分析和生成代码:

  1. 将纯代码解析为树形结构(代码纯文本 -> 语义单元)。

    将原始代码分解为最小的语义单元并格式化树形结构。在此过程中,编译器需要知道每个运算符的优先级。根据C++ 运算符优先级,我们知道:

    • ?:(条件运算符)与=(直接赋值运算符)具有相同的优先级,优先级为16
    • 在此优先级水平(==16)中,结合性是从右到左的,因此k = 1高于?:

    因此,对于代码1 ? j : k = 1;,树可能如下所示:

                   op(?:)
          /         |          \
         /          |           \
      [condition] [true-branch] [false-branch]
          1           j             op(=)
                                    /  \
                                   k    1
    
  2. 确定评估顺序(生成中间表示)

    为了生成可运行的代码,编译器需要知道应该首先评估(运行)哪一部分。根据评估顺序,我们知道对于运算符?:,应首先评估[condition],然后根据条件结果进行评估。这是直观的和符合我们的直觉。

    因此,想象最终的运行过程,1被评估为true,然后评估真分支jk = 1将永远不会运行。

所以在了解了1,2之后,我们就知道了完整的原因。

根据cpp文档,步骤1属于编译时概念,而步骤2则属于运行时概念。但从编译器的角度来看,它们都属于编译过程,并且编译时/运行时概念只是从语言设计的角度来看的。


0

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