什么是C中的>>> =运算符?


294

一位同事对此感到困惑,我无法弄清楚这个C程序实际上是如何编译和运行的。这是什么>>>=运算符和奇怪的1P1文字?我已经在Clang和GCC中进行了测试。没有警告,输出为“ ???”

#include <stdio.h>

int main()
{
    int a[2]={ 10, 1 };

    while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )
        printf("?");

    return 0;
}

36
其中一些是有向图
juanchopanza

12
@Kay,在这种情况下为否::> =]然后a [...] >> = a [...]
Adriano Repetti

6
@Marc我不认为它可以是“ >>> =”,因为它不能编译,但是上面的代码实际上可以编译。
CustomCalc

21
0x.1P1是一个十六进制字面量指数。该0x.1是多少的一部分,或1/16这里。“ P”后面的数字是该数字的2的乘方。因此0x.1p1,实际上是1/16 * 2或1/8。如果你想知道0xFULL,这只是0xF,并且ULL是后缀为unsigned long long
jackarms

71
C语法-为专家和琐事爱好者提供的无尽资料,但最终并不是那么重要。
Kerrek SB 2014年

Answers:


468

该行:

while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )

包含有向图 :><:,分别翻译为][,因此等效于:

while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] )

文字0xFULL与相同0xF(代表的十六进制15);在ULL刚刚指定的是它是一个unsigned long long文字。无论如何,作为布尔值是正确的,因此0xFULL ? '\0' : -1求值为'\0',这是一个字符文字,其数值为simple 0

同时,0X.1P1是等于2/16 = 0.125 的十六进制浮点文字。无论如何,如果为非零值,则作为布尔值也是如此,因此!!再次将其取反两次会产生1。因此,整个过程简化为:

while( a[0] >>= a[1] )

运算符>>=是一个复合赋值,它将其左操作数向右移右操作数给定的位数,然后返回结果。在这种情况下,右操作数a[1]始终具有值1,因此等效于:

while( a[0] >>= 1 )

或等效地:

while( a[0] /= 2 )

初始值为a[0]10。向右移动一次后,变为5,然后(向下舍入)2,然后为1,最后为0,此时循环结束。因此,循环体将执行三次。


18
能否请您阐述的P0X.1P1
凯-SE是邪恶的

77
@Kay:与中的相同,区别e在于10e5您必须使用p十六进制文字,因为e它是十六进制数字。
Dietrich Epp 2014年

9
@Kay:十六进制浮点文字是C99的一部分,但GCC也在C ++代码中接受它们。正如Dietrich所说,p分隔符将尾数和指数分开,就像e普通的科学浮点符号一样;一个区别是,对于十六进制浮点数,指数部分的底数是2而不是10,因此0x0.1p1等于0x0.1 = 1/16乘以2¹=2。(无论如何,这里都不重要;任何非零值同样适用。)
Ilmari Karonen 2014年

6
@chux:显然,这取决于代码是编译为C还是(最初标记为)C ++。但是我将文本固定为说“字符文字”而不是“ char文字”,并添加了Wikipedia链接。谢谢!
Ilmari Karonen 2014年

8
很好的减少。
科里2014年

69

它是涉及一些相当模糊代码有向图,分别<::>这对替代令牌[]分别。条件运算符也有一些用途。还有一个移位运算符,右移赋值>>=

这是一个更具可读性的版本:

while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] )

以及更具可读性的版本,将中的表达式替换[]为其解析为的值:

while( a[0] >>= a[1] )

更换a[0]a[1]他们的价值观应该可以很容易找出环路做,即相当于:

int i = 10;
while( i >>= 1)

它只是在每次迭代中执行2的(整数)除运算,从而产生序列5, 2, 1


我没有运行它- ????但是,这不会产生,而不是???像OP 那样产生吗?(呵呵codepad.org/nDkxGUNi 确实产生了???
usr2564301 2014年

7
@Jongware 10在第一次迭代时就产生了分歧。因此,循环所评估的值为5、2、1和0。因此它仅打印3次。
MysticXG 2014年

42

让我们从左到右浏览一下表达式:

a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]

我注意到的第一件事是,我们使用的是三元运算符?。所以子表达式:

0xFULL ? '\0' : -1

的意思是“如果0xFULL非零,则返回return '\0'else -10xFULL是具有无符号长-后缀的十六进制文字-表示它是类型的十六进制文字unsigned long long。但这并不重要,因为它0xF可以容纳在常规整数中。

同样,三元运算符将第二和第三项的类型转换为它们的公共类型。'\0'然后转换为int,即0

的值0xF远大于零,因此可以通过。表达式现在变为:

a[ 0 :>>>=a<:!!0X.1P1 ]

接下来:>是有向图。它是一个扩展为]

a[0 ]>>=a<:!!0X.1P1 ]

>>=是带符号的右移运算符,我们可以a将其隔开以使其更清晰。

此外,<:是有向图,可以扩展为[

a[0] >>= a[!!0X.1P1 ]

0X.1P1是带有指数的十六进制文字。但是无论值如何,!!非零的值都是正确的。0X.1P10.125非零的,因此变为:

a[0] >>= a[true]
-> a[0] >>= a[1]

>>=是签署向右移位运算符。它通过将其操作符右侧的值前移其位来更改其左操作数的值。10在二进制是1010。所以这是步骤:

01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000

>>=返回其操作结果,因此,只要a[0]每次其位右移一位,移位都保持为非零,循环就会继续。第四个尝试是a[0]变为0,因此永远不会进入循环。

结果,?被打印了三遍。


3
:>是有向图,而不是有向图。它不是由预处理程序处理的,只是被识别为等效于的令牌]
基思·汤普森

@KeithThompson谢谢
0x499602D2 2014年

1
三元运算符(?:)的类型是第二和第三项的通用类型。第一项始终是有条件的,并且具有类型bool。由于第二和第三项都具有类型int,因此三元运算的结果将int不是unsigned long long
科里2014年

2
@KeithThompson可以由预处理器处理。预处理器必须知道有向图,因为#并且##有有向图的形式。在早期翻译阶段,没有什么能阻止实现将有向图转换成无向图的实现
MM

@MattMcNabb我已经知道了很久了,但是由于其他要求,IIRC导致有向图必须保持其有向图的形式,直到将pp-token转换为标记为止(就在翻译阶段开始时) 7)。
zwol 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.