??!??! 是啥意思?运算符在 C 中做啥?

Posted

技术标签:

【中文标题】??!??! 是啥意思?运算符在 C 中做啥?【英文标题】:What does the ??!??! operator do in C???!??! 是什么意思?运算符在 C 中做什么? 【发布时间】:2011-12-11 02:56:28 【问题描述】:

我看到一行 C 看起来像这样:

!ErrorHasOccured() ??!??! HandleError();

它编译正确,似乎运行正常。似乎它正在检查是否发生了错误,如果发生了,它会处理它。但我不确定它实际上在做什么或它是如何做的。看起来程序员确实在尝试表达他们对错误的感受。

我以前从未在任何编程语言中见过??!??!,而且我在任何地方都找不到它的文档。 (Google 不提供像 ??!??! 这样的搜索字词)。它有什么作用以及代码示例如何工作?

【问题讨论】:

可悲的是这个程序的宝石won't work in C++17 和更新。 【参考方案1】:

??! 是一个trigraph,它转换为|。所以它说:

!ErrorHasOccured() || HandleError();

由于短路,相当于:

if (ErrorHasOccured())
    HandleError();

Possible origin of trigraphs 或@DwB 在 cmets 中指出,这更有可能是因为 EBCDIC 很困难(再次)。 This IBM developerworks 板上的讨论似乎支持该理论。

来自 ISO/IEC 9899:1999 §5.2.1.1,脚注 12 (h/t @Random832):

三字符序列允许输入未在不变代码集中定义为的字符 在 ISO/IEC 646 中描述,它是七位美国 ASCII 代码集的子集。

【讨论】:

原本需要三元组以防您的键盘没有例如“|”象征。这里要么是程序员故意烦人,要么是一些奇怪的编辑器“功能” 不一定是 EBCDIC - 需要三元组的字符集几乎完全匹配 ISO-646 中不变的字符集(即旧的“国家 ascii”标准)。 一个完全可读的替代方案是ErrorHasOccurred() && HandleError();,也就是说,如果你习惯于 shell 脚本。 :) 请注意,许多编码标准明确禁止使用三合字母和二合字母,许多编译器和静态分析器会标记它们的使用。 自 C++17 起无效:|【参考方案2】:

这是一个 C trigraph。 ??!|,所以??!??! 是运算符||

【讨论】:

trigraph 来自一些键盘没有他们现在拥有的所有键的时期。当某些文本编辑器为特殊事物保留特殊字符时,它也会有所帮助。它主要是过去的遗物和测验的推动者;) 因为有些键盘显然没有“|”所以有些人别无选择,只能反复用头撞键盘,直到出现一个三元组,为他们提供所需的符号。 还有<iso646.h>这个头文件。【参考方案3】:

嗯,为什么这一般存在可能与您的示例中存在的原因不同。

这一切都始于半个世纪前,将硬拷贝通信终端重新用作计算机用户界面。在最初的 Unix 和 C 时代,那是 ASR-33 Teletype。

这个设备很慢(10 cps),嘈杂和丑陋,它的 ASCII 字符集视图以 0x5f 结尾,所以它(仔细看图片)没有任何键:

 |  ~ 

The trigraphs 被定义为解决特定问题。这个想法是 C 程序可以使用在 ASR-33 上找到的 ASCII 子集,以及在其他缺少高 ASCII 值的环境中。

你的例子其实是两个??!,每个意思是|,所以结果是||

但是,几乎按照定义,编写 C 代码的人都拥有现代设备,1 所以我的猜测是:有人炫耀或自娱自乐,代码供您查找。

它确实有效,它导致了一个广受欢迎的 SO 问题。

                                          ASR-33 电传打字机


1。就此而言,三元组是由 ANSI 委员会发明的,它在 C 取得巨大成功之后第一次遇到,因此原始 C 代码或编码人员都不会使用它们。

【讨论】:

这不是键盘和字符集中缺少字符的唯一情况。很多 30 多岁及以上的人可能更熟悉 Commodore 64 - 显示的字符集都缺少大括号(可能还有横杠和波浪号) - 在这种情况下,因为“ASCII”不是 ASCII .在 ECMA-6(几乎总是称为 ASCII,但不是 US-ASCII)中,有 18 个区域特定的代码,但我不知道它们是哪些代码。我可以肯定地说的一件事 - 在英国的“ASCII”中,# 被替换为 £。在其他地区,也许“ASCII”没有大括号等。 Atari 8 位计算机的类似 ATASCII 字符集也缺少 以及 ~ 和 `。 参见 these two ***文章。我差不多老了,还记得 7 位国家字符集的时代(尽管我确信它们仍然在一些黑暗的未扫过的角落里徘徊),而且我第一次学习 C 的那本书发现有必要警告一下if (x || y) a[i] = '\0'; 在错误的字符集中看起来像 if (x öö y) ä aÄiÅ = 'Ö0'; å 的可能性。 另一个有趣的历史记录是 Unix(它是 C 所依赖的大平台)可能是第一个具有任何意义的系统(也许是第一个整体),将默认字母值改为小写而不是比大写。虽然我没有亲眼见过很多当代系统,但我认为这是一个真正成熟的标志。除了作为真正唯一体面的操作系统之外,Unix 还将您的大写字母转换为小写字母,而不是反之亦然。那些家伙真的很酷。 我得告诉你一个有趣的故事……IBM RS/6000 工作站的 XL Fortran 编译器是从 XL C 编译器开发的。在最初的几个版本中,它们不小心留在了三元组处理中,因此有一些合法的 Fortran 字符序列(在文字字符串中,IIRC)被误解为 C 三元组,导致一些有趣的错误!【参考方案4】:

如前所述,??!??! 本质上是两个 trigraphs??!??! 再次)混合在一起,被替换-翻译为 ||,即 logical OR,由预处理器。

包含每个三元组的下表应该有助于消除替代三元组组合的歧义:

Trigraph   Replaces

??(        [
??)        ]
??<        
??>        
??/        \
??'        ^
??=        #
??!        |
??-        ~

来源:C: A Reference Manual 5th Edition

所以看起来像??(??) 的三元组最终将映射到[]??(??)??(??) 将被[][] 替换等等,你明白了。

由于在预处理过程中替换了三元组,您可以使用 cpp 自己查看输出,使用愚蠢的 trigr.c 程序:

void main() const char *s = "??!??!";  

并处理它:

cpp -trigraphs trigr.c 

你会得到一个控制台输出

void main() const char *s = "||"; 

如您所见,必须指定选项-trigraphs,否则cpp 将发出警告;这表明 三元组已成为过去,除了让可能碰到它们的人感到困惑之外,没有任何现代价值


至于引入三元组的原理,看the history section of ISO/IEC 646就更好理解了:

ISO/IEC 646 及其前身 ASCII (ANSI X3.4) 在很大程度上认可了电信行业中有关字符编码的现有做法。

由于 ASCII 没有提供英语以外的语言所需的大量字符,制作了一些国家变体,用所需的字符替换了一些不常用的字符 .

(强调我的)

因此,从本质上讲,某些国家变体中替换了一些需要的字符(存在三合符的字符)。这导致了使用由其他变体仍然存在的字符组成的三元组的替代表示。

【讨论】:

很好的解释......这也说明了为什么诸如char *date = "??-??-??!" 之类的占位符可能不会产生您期望的结果(这实际上会产生char *date = "~~|";

以上是关于??!??! 是啥意思?运算符在 C 中做啥?的主要内容,如果未能解决你的问题,请参考以下文章

++ 运算符在 Python 中做啥? [复制]

“|”是啥意思(单管道)在 JavaScript 中做啥?

$ 在 Haskell 中是啥意思/做啥?

~> 运算符是做啥的? [复制]

脚本是啥意思,是做啥的啊?

是啥!!运算符在 R 中的意思,特别是在上下文中 !!sym("x")