一位同事把它当成一个谜,我不知道这个C程序是如何编译和运行的。这个>>>=运算符和奇怪的1P1字是什么?我在Clang和GCC进行过测试。没有警告,输出为""。??"
1 2 3 4 5 6 7 8 9 10 11
| #include <stdio.h>
int main ()
{
int a [2]={ 10, 1 };
while( a [ 0xFULL ?'\0':-1:>>>=a <:!!0X.1P1 ] )
printf("?");
return 0;
} |
- 提示:它不是一个运算符。它是相邻的多个运算符。
- 其中一些是有向图。
- C使用最长的匹配来查找令牌,因此>>>=a<:!!被解析为链接>> >= a < : ! !。>> >=不能以有效的顺序出现。
- @凯,不,在这种情况下::>=]然后是一个[…]>>=a[…]
- @Marc我不认为它可以是">>>=",因为这不会编译,但是上面的代码实际上是编译的。
- 0x.1P1是一个十六进制的带指数的文字。0x.1是数字部分,或这里的1/16。"p"后面的数字是2的幂,该数字乘以。所以0x.1P1实际上是1/16*2,或者1/8。如果你想知道0xFULL,那就是0xF,ULL是unsigned long long的后缀。
- C句法——对专家和琐事爱好者来说是无止境的材料,但最终并不是那么重要。
- 空白处都错了!摇头
- >>>=号?当然不是Java吗?
- 这就是为什么我不是C开发人员。
- 我很喜欢一个完全错误的评论比一个公开的评论有更多的赞成票。
- @你什么意思?你认不出一个超级下行话务员吗?
- 你的同事有更多的难题吗?
- @奥利1511这显然是一个来自一次性账户的捏造问题,很可能是由下面的一个回答者发布的。
- @Kerreksb最可怕的部分是,它中有多少是丑陋的黑客插入的,以允许不完整的键盘在模糊的电传打字机从时间的黎明,而没有其他目的。Digraphs?真的吗?啊!!!!!!!
- @德马克:很容易对我们的花式平板电脑和第二任妻子感到愤世嫉俗,但回到那个时代,有向图帮助真正的人做真正的工作,把真正的面包放在桌子上……
- 为什么不问问你的同事而不是作弊呢?;)
- 对不起,我需要更多的咖啡
线:
1
| while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] ) |
包含有向图:>和<:,分别转换为]和[,因此相当于:
1
| while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] ) |
文字0xFULL与0xF相同(对于15为十六进制);ULL只是指明它是unsigned long long文字。在任何情况下,作为一个布尔值,它都是真的,所以0xFULL ? '\0' : -1的计算结果是'\0',它是一个字符文字,其数值仅为0。
同时,0X.1P1是十六进制浮点文字,等于2/16=0.125。在任何情况下,非零的情况下,作为一个布尔值也是正确的,因此用!!否定它两次会再次产生1。因此,整个过程简化为:
运算符>>=是一个复合赋值,它按右操作数给定的位数将左操作数右移,并返回结果。在这种情况下,右操作数a[1]的值总是1的值,因此它相当于:
或者,等价地:
a[0]的初始值为10。右移一次后,它变为5,然后(四舍五入)2,然后是1,最后是0,此时循环结束。因此,循环体被执行三次。
- 你能详细介绍一下0X.1P1中的P吗?
- @凯:这和10e5中的e是一样的,只是你必须用P来表示十六进制文字,因为e是十六进制数字。
- @ KEY:十六进制浮点文字是C99的一部分,但GCC也接受C++代码。正如Dietrich所指出的,P将尾数和指数分开,就像普通的科学浮点数法中的e一样;一个区别是,使用十六进制浮点数时,指数部分的基部是2而不是10,所以0x0.1p1等于0x0.1=1/16乘以2&185;=2。(在任何情况下,这些都不重要;任何非零值在那里都同样有效。)
- 小调:某些'\0'是int字。试试printf("%zu %zu
", sizeof ('\0'), sizeof (char));。
- 显然,这取决于代码是编译成C还是(原来是标记的)C++。但是我把文本改成了"character literal"而不是"charliteral",并添加了一个维基百科链接。谢谢!
- 很好的减少。
- 太好了,现在你把我的乐趣给宠坏了:(
- 六羟甲基三聚氰胺六甲醚。。。我试过if (str.length() && str[str.length() :> != L'\\' && str<:str.length()] != L'/'),但它给出了编译错误,这段代码有什么问题吗?
- 只需说明:标点符号或有向图的语义参见C99标准第6.4.6节(即<:)。open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf文件
这是一些相当模糊的有向图代码,即<:和:>,分别是[和]的替代标记。还有一些条件运算符的用法。还有一个位移位运算符,即右移位指定>>=。
这是一个更易读的版本:
1
| while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] ) |
以及更可读的版本,将[]中的表达式替换为它们解析的值:
将a[0]和a[1]替换为它们的值应该可以很容易地了解循环在做什么,即等效于:
1 2
| int i = 10;
while( i >>= 1) |
它只是在每次迭代中执行(整数)除以2,生成序列5, 2, 1。
- 我没有运行它-这不会产生????,但是,而不是像OP那样产生????(哈)codepad.org/ndkxguni确实生产???。
- @在第一次迭代中,Jongware对10进行了划分。所以循环计算的值是5、2、1和0。所以只能打印3次。
让我们从左到右看表达式:
1
| a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] |
我注意到的第一件事是,我们使用的是来自?的三元运算符。所以子表达式:
表示"如果0xFULL为非零,则返回'\0',否则返回-1。0xFULL是一个带有无符号长后缀的十六进制文字,也就是说它是unsigned long long类型的十六进制文字。但这并不重要,因为0xF可以放入一个正整数中。
另外,三元运算符将第二个和第三个术语的类型转换为它们的公共类型。然后将'\0'转换为int,即0。
0xF的值远远大于零,所以它通过了。现在表达式变为:
接下来,:>是一个有向图。它是一个扩展到]的结构:
>>=是有符号的右移位运算符,我们可以把它从a中去掉,使其更清晰。
此外,<:是一个有向图,它扩展到[上:
0X.1P1是一个十六进制的带指数的文字。但无论价值如何,任何非零的东西的!!都是正确的。0X.1P1为0.125非零,故:
1 2
| a[0] >>= a[true]
-> a[0] >>= a[1] |
>>=是签名的右移位运算符。它通过按运算符右侧的值向前移动其位来更改左操作数的值。二进制中的10是1010。下面是步骤:
1 2 3 4
| 01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000 |
>>=返回其操作的结果,只要移位a[0]保持非零,每次其位右移一位,循环将继续。第四次尝试是在a[0]变成0的地方,所以循环从未进入。
因此,?打印了三次。
- :>是有向图,不是三向图。它不是由预处理器处理的,只是被认为是等同于]的令牌。
- @基思汤普森谢谢
- 三元运算符(?:的类型是第二和第三项的通用类型。第一个术语总是有条件的,其类型为bool。由于第二个和第三个术语都是int型,所以三元运算的结果是int型,而不是unsigned long long型。
- @科里更新了,谢谢你指出。
- @基思汤普森它可以由预处理器处理。预处理器必须了解有向图,因为#和##具有有向图形式;在早期的翻译阶段,没有什么能阻止实现将有向图翻译为非有向图。
- @Mattmcnab我已经很长时间不知道了,但是IIRC由于其他要求,有向图必须保持有向图的形式,直到PP令牌转换为令牌为止(就在翻译阶段7的开始)。
- @corey:?:的第一个操作数是标量类型,不需要bool类型(1999年才引入到该语言中,?:更老),也不需要转换为bool类型。顺便说一下,0xFULL是unsigned long long型,不是long long型。
- @Mattmcnab:是的,预处理器必须知道有向图,这是因为%:和%:%:,并且因为#if表达式可以使用它们,但是没有理由转换它们。标准将有向图描述为与它们等价的符号。