cout << "\n"[a==N]; 的作用是什么?

64
在以下示例中:
cout<<"\n"[a==N];

我不知道在cout中使用[]选项是什么意思,但当a的值等于N时,它不会打印换行符。

5个回答

73
我不知道cout中的[]选项是做什么用的。
实际上,这并不是一个cout选项。发生的事情是"\n"是一个字符串字面量。字符串字面量的类型为n个const char的数组[]只是一个字符数组中的索引,在本例中它包含了:
\n\0

注意:所有字符串字面值都附加了 \0== 运算符的结果要么是 true,要么是 false,因此索引将为:
  • 如果为 false,即 a 不等于 N,结果为 \n,则索引为 0
  • 如果为 true,即 a 等于 N,结果为 \0,则索引为 1
这相当晦涩,可以用一个简单的 if 语句代替。
参考 C++14 标准(Lightness 确认草案与实际标准相符),最接近的草案为 N3936,在第 2.14.5 节 字符串字面值 [lex.string] 中指出(重点标出):

字符串字面值的类型为“长度为n的const char数组”,其中n是下面定义的字符串大小,并具有静态存储期(3.7)。

并且:

在必要的连接后,在第7个翻译阶段(2.2)中,会在每个字符串字面值后添加'\0',以便扫描字符串的程序可以找到其结尾。

4.5[conv.prom] 表示:

bool类型的prvalue可以转换为int类型的prvalue,其中false变成零,true变成一。

将空字符写入文本流

声称在文本流中写入空字符(\0)是未定义行为。

就我所知,这是一个合理的结论,因为我们可以从27.4.2 [narrow.stream.objects]中看到,cout是根据C流定义的。

“cout”对象控制着与“stdout”对象关联的流缓冲区的输出,在(27.9.2)中声明。C11草案标准在第7.21.2节“流”中说:“从文本流中读取的数据只有在数据仅由打印字符和控制字符水平制表符和换行符组成时才能与先前写入该流的数据相等;”,而“打印字符”在7.4“字符处理”中有所涵盖:“[...]控制字符这个术语是指一组不是打印字符的特定于语言环境的字符。199)所有字母和数字都是打印字符。”注脚199说:
在使用七位US ASCII字符集的实现中,打印字符是指值从0x20(空格)到0x7E(波浪号)的字符; 控制字符是指值从0(NUL)到0x1F(US)的字符和字符0x7F(DEL)。
最后,我们可以看到发送null字符的结果未指定,并且我们可以从第4节符合性中看到这是未定义的行为,该节说:
[...]否则,在本国际标准中,通过“未定义行为”或未明确定义行为来指示未定义行为。
我们还可以参考C99理由C99 rationale
“需要在文本流I/O中保留的字符集是编写C程序所需的字符集;这意味着标准应允许以最大程度的可移植方式编写C翻译器。像退格这样的控制字符并不需要用于此目的,因此它们在文本流中的处理没有被强制执行。”

3
请注意,我添加了这个答案,因为当时虽然有几个答案,但奇怪的是,没有一个解释什么是字符串字面值以及为什么可以对其进行索引。一旦这一点清楚,其余的就会迎刃而解。 - Shafik Yaghmour
1
除了条件的一个分支什么也不做之外,三元表达式需要一个相当丑陋的占位符字面量 "" - IMSoP
1
这个答案应该解释一下 cout << '\0' 的效果是什么。 - M.M
3
这是否意味着您现在将放弃抱怨人们引用N3936而不是购买C++14的副本? - M.M
@MattMcNabb:看起来这与我之前的评论完全相反。 :) 关键是,尽管您应该引用标准,但如果您有FDIS,则不必购买标准。 - Lightness Races in Orbit
显示剩余5条评论

39
cout<<"\n"[a==N];

I have no clue about what the [] option does in cout

C++运算符优先级表中,operator []operator <<的优先级更高,因此您的代码等同于:
cout << ("\n"[a==N]);  // or cout.operator <<("\n"[a==N]);
< p>换句话说, operator [] 不会直接影响 cout 。它仅用于字符串文字"\ n"的索引。

例如, for(int i = 0; i<3; ++i)std :: cout <<“ abcdef” [i] << std :: endl; 将在屏幕上连续打印字符a,b和c。


因为在C++中字符串字面值总是以空字符('\0'L'\0'char16_t()等)结尾,所以字符串字面值"\n"是一个包含字符'\n''\0'const char[2]
在内存布局中,它看起来像这样:
+--------+--------+
|  '\n'  |  '\0'  |
+--------+--------+
0        1          <-- Offset
false    true       <-- Result of condition (a == n)
a != n   a == n     <-- Case

如果a == N为真(转换为1),则表达式"\n"[a == N]的结果为'\0',如果结果为假,则为'\n'
它在功能上与以下内容类似(但不完全相同):
char anonymous[] = "\n";
int index;
if (a == N) index = 1;
else index = 0;
cout << anonymous[index];

"\n"[a==N] 的值为 '\n''\0'

"\n"[a==N] 的类型为 const char


如果意图是不打印任何东西(这可能因平台和目的而异于打印'\0'),请使用以下代码行:
if(a != N) cout << '\n';

即使你的意图是在流中写入'\0''\n',也应该优先选择可读性更好的代码,例如:

即使你的意图是在流中写入'\0''\n',请优先使用易于阅读的代码,例如:

cout << (a == N ? '\0' : '\n');

2
它与另一个示例“不同”的方式是什么?仅仅是复制、打字错误和作用域泄漏吗? - Lightness Races in Orbit
2
如果意图是打印换行符或空字符,你仍然应该使用与原始代码不同的东西! - user1084944
@LightnessRacesinOrbit 是的,你说得对,这是复制+作用域泄漏 :) 感谢指出笔误,我现在会修复它。当我说类似时,我的意图是,即使匿名未在其他地方使用,编译器也可能决定生成不同的代码。 - Mohit Jain

9

这可能是一种奇怪的书写方式。

这段话与IT技术无关。
if ( a != N ) {
    cout<<"\n";
}
[] 操作符从数组中选择一个元素。字符串 "\n" 实际上是由两个字符组成的数组:一个换行符 '\n' 和一个字符串终止符 '\0'。因此,cout<<"\n"[a==N] 将打印一个 '\n' 字符或一个 '\0' 字符。
问题在于,在文本模式下,您不允许将 '\0' 字符发送到 I/O 流中。那段代码的作者可能已经注意到似乎什么也没发生,所以他认为 cout<<'\0' 是一种安全的方法什么都不做。
在 C 和 C++ 中,这是非常糟糕的假设,因为存在未定义行为的概念。如果程序执行的操作未被标准规范或特定平台的规范所覆盖,那么任何事情都可能发生。在这种情况下,相当可能的结果是流将完全停止工作 - 不会再有输出到 cout
总之,其效果是:

“如果 a 不等于 N,则打印一个换行符。否则,我不知道。崩溃或其他什么。”

…所以,教训是,不要写得那么难懂。

4
C++或C标准中没有关于在文本模式下向I/O流发送'\0'是否为未定义行为的规定。“文本模式”是一个Windows概念,在基于Unix的系统上没有文本模式和二进制模式之分。 - David Hammen
1
@LightnessRacesinOrbit:实际上,它可能是“未指定的”,如果我没记错的话,有几种情况下会(或曾经)让常识来判断给定行为是未定义还是未指定。例如,int的大小没有定义,但这使其成为未指定的。 - MSalters
1
@LightnessRacesinOrbit - Re 如果你没有定义某个东西,那么它就是未定义的。 这可能是C++中的工作方式,但不适用于C。 C标准非常小心地将行为定义为“未定义”。这似乎是矛盾的,但我喜欢它。 C标准告诉我哪些地方我不应该涉足。它甚至为我提供了一个漂亮的简短摘要(如果你可以称13页为“简短”)来概括所有未定义的行为。 C++标准偶尔会说“UB”,但它没有总结,并且更糟糕的是,有很多C++行为很可能是未定义的,但没有被指定为这样。 - David Hammen
1
@LightnessRacesinOrbit - 那是MSalters选择的例子,不是我的。手头的问题是写入'\0'std::cout是否是未定义行为。根据我对标准的阅读,它不是。仅仅因为Windows在这方面做了一些不同的事情,并不意味着它是UB。这只是意味着Windows再次做了一些不同的事情。 - David Hammen
1
@DavidHammen:我不反对 - Lightness Races in Orbit
显示剩余12条评论

8

这不是cout的一个选项,而是"\n"的一个数组索引。

数组索引[a==N]的值为[0]或[1],并索引由"\n"表示的字符数组,其中包含换行符和空字符。

然而将空字符传递给iostream会导致未定义的结果,最好传递一个字符串:

cout << &("\n"[a==N]) ;

然而,这两种情况下的代码都不是特别可取的,除了混淆之外没有任何特别的目的;不要把它视为良好实践的例子。在大多数情况下,以下做法更可取:

cout << (a != N ? "\n" : "") ;

或者只是:
if( a != N ) cout << `\n` ;

你的第一个例子中不需要括号:cout << &"\n"[a==N] - eush77
@eush77:我知道,但是不了解&和[]的相对优先级会影响清晰度。 - Clifford

7
以下每行代码输出结果完全相同:

每个以下的语句都会产生完全相同的输出:

cout << "\n"[a==N];     // Never do this.
cout << (a==N)["\n"];   // Or this.
cout << *((a==N)+"\n"); // Or this.
cout << *("\n"+(a==N)); // Or this.

正如其他答案所指出的那样,这与std::cout无关。相反,它是由以下原因导致的:
  • C和C++中原始(未重载)下标运算符的实现方式。
    在两种语言中,如果array是原始数据类型的C风格数组,则array[42]*(array+42)的简写。更糟糕的是,array+4242+array之间没有区别。这会导致有趣的混淆:如果你的目标是彻底混淆代码,请使用42[array]代替array[42]。毫无疑问,如果你的目标是编写易懂、可维护的代码,编写42[array]是一个非常糟糕的想法。

  • 布尔值如何转换为整数。
    对于形如a[b]的表达式,ab必须是指针表达式,而另一个则必须是整数表达式。对于表达式"\n"[a==N]"\n"表示该表达式的指针部分,a==N表示该表达式的整数部分。在这里,a==N是一个布尔表达式,评估为falsetrue。整数提升规则指定,false在提升为整数时变为0,而true在提升为整数时变为1。

  • 字符串字面值如何被降级为指针。
    当需要指针时,在C和C++中,数组很容易被降级为指向数组第一个元素的指针。

  • 字符串字面值的实现方式。
    每个C风格的字符串字面值都以空字符'\0'结尾。这意味着你的"\n"的内部表示形式是数组{'\n', '\0'}


鉴于上述情况,假设a==N评估为false。在这种情况下,所有系统的行为都是定义良好的:你会得到一个换行符。另一方面,如果a==N评估为true,则行为高度依赖于系统。根据问题答案的评论,在Windows系统中,这段代码不会工作。在将std::cout管道传输到终端窗口的类Unix系统中,行为相对温和。什么都不会发生。


仅仅因为你可以编写这样的代码并不意味着你应该这样做。永远不要编写这样的代码。


@MarkHurd - 所有四个语句都完全做同样的事情。请了解一下C和C++中原始数组索引的工作方式。关于在文本模式下写入'\0'到输出,这在Unix和Linux机器上是完全可以的。这种情况经常发生。二进制模式,文本模式?那是什么?Unix和Linux不区分这两者。C和C++标准的某些部分向Windows屈服,其他部分向Unix和Linux屈服,还有其他部分向其他架构屈服。不要太以Windows为中心。 - David Hammen
1
我在你的后两个语句中没有看到你的*。抱歉。 - Mark Hurd
@MarkHurd - 我会添加一些间距,以使其更明显。 - David Hammen
现在我可以说,如果你从第二个和第三个语句中删除 *,它将更适用于所有环境 :-) - Mark Hurd
1
从第二个和第三个语句中删除 * 会导致 clang 发出一个愚蠢的警告。我必须使用 Wno-string-int 编译才能使其编译干净。在前两个语句中添加一个 ampersand 有相同的效果,即使没有该编译器选项也可以编译干净,并且它还具有增加混淆级别的附加好处。(显然,gnu 的 binutils 从 "some_string"+some_int 修改为 &"some_string"[some_int],因为这个编译器警告。) - David Hammen
1
请注意:上述评论“它还有增加混淆级别的额外好处”是非常玩笑的。良好的编程实践意味着避免增加混淆级别。 - David Hammen

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