C++中三字符序列的目的是什么?

141

根据C++'03标准2.3/1的规定:

在任何其他处理之前,每次出现以下三个字符序列之一(“三连字符序列”),将被替换为表1中指定的单个字符。

----------------------------------------------------------------------------
| trigraph | replacement | trigraph | replacement | trigraph | replacement |
----------------------------------------------------------------------------
| ??=      | #           | ??(      | [           | ??<      | {           |
| ??/      | \           | ??)      | ]           | ??>      | }           |
| ??’      | ˆ           | ??!      | |           | ??-      | ˜           |
----------------------------------------------------------------------------
在现实生活中,这意味着代码printf( "What??!\n" );将打印出What|,因为??!是一种三字符序列,它被替换成了|字符。
我的问题是什么是使用三字符序列的目的?使用三字符序列是否有任何实际优势?
更新:回答中提到一些欧洲键盘没有所有标点符号,因此非美国程序员在日常工作中必须使用三字符序列吗?
更新2:Visual Studio 2010默认关闭三字符支持。

2
有时候,一些终端和/或虚拟化可能不允许您轻松访问某些字符。根据我的经验,主要的问题是波浪号。 - Francesco
1
在我的DE-deadkeys键盘上打字,#键位于回车键旁边,\是"AltGr"+"ß"(0旁边),^是"^"+"^"(因为有deadkeys;1旁边),[是"AltGr"+"8",]是"AltGr"+"9",|是"AltGr"+"<",{是"AltGr"+"7",}是"AltGr"+"0",是""+"~"(因为有deadkeys,就在#上面)。所以这并不是什么大问题。我的手指好像自己就能打出这些组合键 :-D - nonchip
1
我认为,在计算机上拥有两种键盘布局并根据工作进行切换是很正常的。这是中欧地区的常见方式。使用这些三字符符号非常令人沮丧。我赞成将其从标准中删除。 - V-X
因为C语言源程序的源字符集包含在7位ASCII字符集中,但是它是ISO 646-1983不变代码集的超集。 - Pacerier
1
@V-X 你的愿望已实现! - graham.reeds
显示剩余4条评论
9个回答

104

这个问题(关于密切相关的双字母组合)已经有了答案。

问题在于ISO 646字符集没有C语法的所有字符,因此有些键盘和显示系统无法处理这些字符(尽管我想现在这些情况相当少见)。

通常情况下,您不需要使用它们,但是您需要了解它们,以解决您遇到的问题。三字符组合是'?'字符具有转义序列的原因:

'\?'

以下是避免类似问题的几种方法:

 printf( "What?\?!\n" ); 

 printf( "What?" "?!\n" ); 

但是当您键入两个“?”字符时,需要记住可能会启动三字母组(这绝对不是我所考虑的事情)。
实际上,在日常工作中,我根本不担心三字母组和双字母组。但是,您应该意识到它们,因为每隔几年,您都会遇到与它们相关的错误(然后您将花费余下的时间咒骂它们的存在)。如果编译器能够配置警告(或错误),当它遇到三字母组或双字母组时,那就太好了,这样我就可以知道我必须要处理的问题。
只是为了完整起见,双字母组要少得多,因为它们被处理为标记,因此字符串文字中的双字母组不会被解释为双字母组。
有关C / C ++程序中各种有趣标点符号的良好教育(包括会让我抓狂的三字母组bug),请看Herb Sutter的GOTW#86文章

补充:

看起来GCC默认不会处理(并会发出警告)三字符组。一些其他编译器有关闭三字符组支持的选项(例如IBM的)。Microsoft在VS2008中开始支持一个警告(C4837),必须显式启用(使用-Wall或类似选项)。


兼容性是否是唯一的原因?在现代C++程序中是否可能满足它们? - Kirill V. Lyadvinsky
4
据我回忆,至少有一个我使用过的编译器(g++?)要求在三重斜杠和双重斜杠被翻译之前使用显式命令行选项,否则会发出警告但不进行替换。请注意,这里不提供解释或其他任何内容。 - KTC
1
@Jla3ep - 我个人从未需要三字符,但不幸的是编译器会处理带有它们的代码,因此您需要了解它们(以避免意外使用)。此外,如果您从其他地方获取代码,则可能会遇到它们的有意使用,但这将非常罕见。我想我在20多年中只遇到过一次有意使用的三字符(那是IBM大型机的某些代码)。 - Michael Burr
@MichaelBurr,如果你使用“无三字符”标志运行,就不应该有问题。 - Pacerier
1
只有当三字符序列在注释中被扩展以执行令人惊讶的操作时,它才会真正让我恼火。 - Joshua
显示剩余5条评论

31

今时今日的孩子们! :-)

是的,外国设备,例如 IBM 3270 终端。如果我没记错的话,3270终端没有大括号! 如果你想在IBM迷你/主机上编写C语言代码,你必须为每个块边界使用可悲的三字符组。幸运的是,我只需要编写C代码来模拟一些IBM小型计算机功能,而不是在 System/36 上实际编写C代码。

看看 "P" 键旁边:

keyboard

嗯。很难说。 "回车" 旁边有一个额外的按钮,我可能搞错了:也许是 " [ " / " ] " 对不齐。无论如何,如果你必须编写 C 代码,这种键盘会使你感到痛苦。

此外,这些终端显示 EBCDIC,即 IBM 的“本地”主机字符集,而不是 ASCII(感谢 Pavel Minaev 提供的提醒)。

另一方面,就像 GNU C 指南所说:“你不需要这种脑损伤。” GCC 编译器默认禁用此“特性”。


2
键盘上有一个重置按钮。太棒了!不过奇怪的是,这引起了我的注意。 - TtT23
18
谁想在 EBCDIC 机器上使用 C++17,就该因为恋尸癖而坐牢。 - SF.
除非一个平台上没有任何字符,除了ISO646中的字符,否则所有可以使用三字符组合完成的操作都可以通过要求每个实现定义反斜杠或者任何不在C字符集中的字符作为“元”字符,将标准中所有反斜杠的引用替换为“元”,并为任何不在ISO-646中的C字符集成员添加反斜杠/元转义来完成。 - supercat

22

来自The C++ Programming Language特别版,第829页

ASCII特殊字符[, ], {, }, |\在ISO中被指定为字母。在大多数欧洲国家的ISO-646字符集中,这些位置都被英文字母中没有的字母所占据。

一组三字符序列可用于以一种真正标准的极简字符集表达国家字符,从而实现程序的可移植性。这对于程序交换可能很有用,但它不会使人们更容易阅读程序。当然,这个问题的长期解决方案是C++程序员获得同时支持其母语和C ++ 的设备。不幸的是,这似乎对某些人来说是不可行的,并且引入新设备可能是一个令人沮丧的缓慢过程。


9
引入新设备的过程可能会非常缓慢令人沮丧,特别是与标准化编程语言功能的快速和无痛过程相比较。 - jforberg
4
如果这是为了键盘布局而做的临时解决方案,那么有趣的是,没有三字符组合可以输入缺失于意大利语和其他几种键盘布局中的“`”符号。 - badp
标准C(至少)不使用 \(以及 $@~),因此不需要支持它。据我所知,这不仅是为了键盘布局而进行的一种包装,而且也与字符集有关:ISO 646 愉快地从ASCII中删除了一些字符进行“自定义”,但是K&R会使用其中一些字符,因此当ISO开始标准化C时,他们遇到了棘手的问题。因此,标准不愿包括任何除语法严格要求的标点符号之外的标点符号(这本质上就是除上述四个字符之外的所有标点符号)。 - Alex Shpilkin
无论如何, \ 如其所定义(与历史用法相反)都有点奇怪:它的意思是代表打字机上的独立重音符号(而不是引号),所以你可以写成 \<BS>a(其中 <BS>是实际的退格,而非擦除!),然后得到à。同样的道理,下划线 也是如此,并用于下划线。(有趣的事实:即使在man使用的troff | less管道内部,加粗的a和下划线的a分别表示为a<BS>a<BS>a`。) - Alex Shpilkin

15

这些字符集用于那些缺少C++基本字符集中某些字符的系统上。毋庸置疑,这样的系统极其罕见。


2
这是否意味着我永远不会在现实生活中使用它们? - Kirill V. Lyadvinsky
1
你住在哪个国家?并非所有语言的键盘都有必要的按键。 - David Thornley
2
是的,但是你可能需要注意它们的存在,以防在字符串字面值中遇到时导致意外结果。 - CB Bailey
4
大多数现代系统支持C++的所有基本字符,即使它们不在传统位置或需要修改序列进行输入。只有在无法在系统字符集中实际表示该字符的系统上才需要在源代码中维护三字符序列。我仍然坚持这样的系统非常罕见。 - CB Bailey

9

在C++0x中,三字符序列被提议移除。尽管如此,仍然有强有力的论据支持它们 - 可以看看C++委员会文件N2910进行讨论。显然,在EBCDIC中是需要它们的一个主要堡垒。


是的,那个“外语”! :-) - Roboprog
除了“客户反馈的内部调查结果”外,它们并没有说太多。但是,嗯,我很惊讶EBCDIC仍然被广泛使用(并且这些系统希望使用C++0x编译器)。 - peterchen

5

我曾经看到过三字符组(trigraphs)在90年代早期被用来协助将主机上的PL/1程序转换到PC上运行/编译/调试。

他们试着在PC上编辑PL/I代码,使用PL/I转C编译器,希望当代码移回不支持大括号的主机上时仍然能够正常工作。我建议他们可以使用类似于

#def BEGIN {    
#def END }  

作为更友好的PL/I替代方案
#def BEGIN ??<
#def END ??>

如果他们真的想变得高级,他们可以尝试一下。
#ifdef MAINFRAME
    #def BEGIN ??<
    #def END ??>
#else
    #def BEGIN {    
    #def END }  
#endif

然后程序看起来就像是用Pascal编写的。他们只是奇怪地看着我,一整天都不和我说话。我想我不能怪他们。 :)

导致该努力失败的不是三字符组,而是平台之间的IO系统差异。在PC上打开文件与大型计算机完全不同,这将引入太多的补丁程序以使相同的代码在两者上运行。


PL/1是IBM版的C语言(多多少少)。看我的评论:IBM终端没有“{”/“}”键 :-( 在这种情况下写C [++]有点困难。 - Roboprog

3
一些欧洲键盘上没有美国键盘上所有的标点符号,因为它们需要用于其非常规字母字符的键。例如(这只是举个例子),瑞典键盘上会有一个A-ring字符,而不是花括号。为了适应这些用户,三字符序列是一种使用最常见的ASCII字符输入标点符号的方法。

5
三字母组并不是关于数据输入的(它们会使代码变得难以阅读),而更多地涉及到那些实际上没有所需字符的系统。如果一个系统可以记录和显示该字符,即使需要键入三字母组序列,也会更容易不保留源中的三字母组序列。 - CB Bailey

3

主要是因为1989年C标准推出时,一些机器上存在与三字符映射相关的字符问题。到1998年C++标准发布时,对于三字符的需求已经不大了。它们是C语言上的瑕疵,同样也是C++上的瑕疵。尤其是在英语以外的国家,存在对它们的需求,这就是为什么它们被添加到C中的原因。


1
我一直怀疑IBM不会说英语 :-) - Roboprog

2

这些三字符序列大多是由历史原因留下来的。现在,对于大多数语言,大多数现代键盘都允许访问所有这些字符,但在某些欧洲键盘上,曾经存在这样的问题。 这就是三字符序列被发明的原因。

如果你不知道它们的用途,就不应该使用它们。

然而,了解它们仍然很有好处,因为您可能会在代码中意外或无意地使用其中之一。


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