cout << "\n"[a==N]; 是啥意思?做?
Posted
技术标签:
【中文标题】cout << "\\n"[a==N]; 是啥意思?做?【英文标题】:what does cout << "\n"[a==N]; do?cout << "\n"[a==N]; 是什么意思?做? 【发布时间】:2015-09-02 12:39:08 【问题描述】:在以下示例中:
cout<<"\n"[a==N];
我不知道[]
选项在cout
中的作用,但是当a
的值等于N
时,它不会打印换行符。
【问题讨论】:
【参考方案1】:我不知道 [] 选项在 cout 中的作用
这实际上不是cout
选项,发生的事情是"\n"
是string literal。字符串文字的类型为 array of n const char,[]
只是一个字符数组的索引,在这种情况下包含:
\n\0
注意\0
被附加到所有字符串文字中。
==
运算符导致 true 或 false,因此索引将为:
0
如果为假,如果 a
不等于 N
导致 \n
1
如果为真,如果 a
等于 N
导致 \0
这是相当神秘的,可以用简单的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 类型的纯右值可以转换为 int 类型的纯右值,用 假变零,真变一。
将空字符写入文本流
声称将空字符 (\0
) 写入文本流是未定义的行为。
据我所知,这是一个合理的结论,cout
是根据 C 流定义的,正如我们从 27.4.2
[narrow.stream.objects] 中看到的那样:
对象 cout 控制输出到与对象 stdout 关联的流缓冲区,在
(27.9.2).
7.21.2
Streams 部分中的 C11 草案标准说:
[...]从文本流中读取的数据必须与数据比较 仅在以下情况下才写入该流:数据仅包含打印 字符和控制字符水平制表符和换行符;
和打印字符覆盖在7.4
字符处理
[...]术语控制字符 指的是特定于语言环境的字符集的成员,这些字符不打印 characters.199) 所有字母和数字都是打印字符。
脚注199
说:
在使用七位 US ASCII 字符集的实现中,打印字符是那些 其值从 0x20(空格)到 0x7E(波浪号);控制字符是那些 值从 0 (NUL) 到 0x1F (US),字符 0x7F (DEL)。
最后我们可以看到没有指定发送空字符的结果,我们可以从4
Conformance 部分看到这是未定义的行为:
[...]否则未定义的行为 在本国际标准中用“未定义行为”或 省略任何明确的行为定义。[...]
我们也可以查看C99 rationale,上面写着:
需要在文本流 I/O 中保留的字符集是编写 C 所需的字符集 程式;其目的是标准应该允许 C 翻译器以最大程度地编写 便携时尚。为此目的不需要诸如退格之类的控制字符,因此它们的 文本流中的处理不是强制性的。
【讨论】:
嗯更好,虽然你仍然引用了一些不是标准的东西:(如果它是一个 FDIS,那么你可以假装你从紧随其后的国际标准中提取了文本(根据定义,在内容),但否则我认为您不应该使用它。如果有帮助,我可以确认 C++14 在具有相同编号的部分中按原样包含此措辞。[编辑:实际上,n3936 是 C++14 FDIS! 所以我认为你可以更新标签以引用 C++14] 注意,我添加了这个答案,因为当时虽然有几个答案,但奇怪的是,没有人解释什么是字符串文字以及为什么对它进行索引是有效的。一旦明确了这一点,其余的就会随之而来。 @KarolyHorvath 除了条件的一个分支是什么都不做,需要在三元表达式中使用相当难看的占位符文字""
。
这个答案应该解决cout << '\0'
的作用是什么
@LightnessRacesinOrbit 这是否意味着您现在将放弃抱怨人们引用 N3936 而不是购买 C++14 的副本?【参考方案2】:
cout<<"\n"[a==N];
我不知道 [] 选项在 cout 中的作用
在C++ operator Precedence table中,operator []
比operator <<
绑定得更紧密,所以你的代码相当于:
cout << ("\n"[a==N]); // or cout.operator <<("\n"[a==N]);
或者换句话说,operator []
不直接与cout
做任何事情。它仅用于字符串文字的索引"\n"
例如,for(int i = 0; i < 3; ++i) std::cout << "abcdef"[i] << std::endl;
将在屏幕上连续打印字符 a、b 和 c。
因为C++
中的string literals总是以空字符('\0'
、L'\0'
、char16_t()
等)终止,所以字符串文字"\n"
是@987654336 @拿着字符'\n'
和'\0'
在内存布局中是这样的:
+--------+--------+
| '\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'
,也更喜欢可读的代码,例如:
cout << (a == N ? '\0' : '\n');
【讨论】:
与其他示例有什么“不同”?仅仅是副本、错字和范围泄漏吗? 如果打算打印换行符或空字符,您仍然应该更喜欢与原始代码行不同的东西! @LightnessRacesinOrbit 是的,你是对的,它是复制 + 范围泄漏 :) 感谢您指出错字,我现在会修复它。当我说类似时,我的意图是,即使在其他地方没有使用匿名,编译器也可能决定生成不同的代码。【参考方案3】:这可能是一种奇怪的写作方式
if ( a != N )
cout<<"\n";
[]
运算符从数组中选择一个元素。字符串"\n"
实际上是一个包含两个字符的数组:一个新行'\n'
和一个字符串终止符'\0'
。所以cout<<"\n"[a==N]
将打印'\n'
字符或'\0'
字符。
问题是您不能在文本模式下将'\0'
字符发送到 I/O 流。该代码的作者可能已经注意到似乎没有发生任何事情,因此他认为cout<<'\0'
是一种安全的不做任何事情的方法。
在 C 和 C++ 中,由于未定义行为的概念,这是一个非常糟糕的假设。可以发生。在这种情况下,一个相当可能的结果是流将完全停止工作 - 根本不会再出现到 cout
的输出。
总的来说,效果是,
“如果
a
不等于N
,则打印一个换行符。否则,我不知道。崩溃什么的。”
……道德是,不要写得这么神秘。
【讨论】:
C++ 或 C 标准中没有关于在文本模式下将 '\0' 发送到 I/O 流是未定义的行为。 “文本模式”是一个 Windows 概念。在基于 Unix 的系统上,文本模式和二进制模式没有区别。 @LightnessRacesinOrbit:实际上,它可能是 Unspecified,并且 IIRC 在某些情况下,无论给定的行为是未定义的还是未定义的(或曾经是)常识。未指定。例如。int
的大小未定义,但这使其未指定。
@LightnessRacesinOrbit - Re 如果你没有定义某些东西,那么它是未定义的。这可能是它在 C++ 中的工作方式,但在 C 中却不是。C 标准是非常小心地将行为定义为“未定义”。看似矛盾,但我喜欢。 C 标准准确地告诉了我不应该踏足的地方。它甚至给了我所有未定义行为的简短摘要(如果您可以将超过 13 页称为“简短”)。 C++ 标准偶尔会说“UB”,但它没有总结,更糟糕的是,有很多 C++ 行为很可能是未定义的,但没有明确说明。
@LightnessRacesinOrbit - 这是 MSalters 选择的例子,不是我的。手头的问题是写'\0'
到std::cout
是否是未定义的行为。从我对标准的阅读来看,事实并非如此。仅仅因为 Windows 做了一些不同的事情并不意味着它是 UB。这只是意味着 Windows 再次做了一些不同的事情。
@DavidHammen:我不同意这一点【参考方案4】:
不是cout
的选项,而是"\n"
的数组索引
数组索引[a==N]
计算结果为 [0] 或 [1],并索引由 "\n"
表示的字符数组,其中包含一个换行符和一个空字符。
但是将 nul 传递给 iostream 会产生未定义的结果,最好传递一个字符串:
cout << &("\n"[a==N]) ;
但是,这两种情况下的代码都不是特别可取的,除了混淆之外没有任何特殊用途;不要将其视为良好做法的示例。在大多数情况下,以下是更可取的:
cout << (a != N ? "\n" : "") ;
或者只是:
if( a != N ) cout << `\n` ;
【讨论】:
第一个示例中不需要括号:cout << &"\n"[a==N]
@eush77 :我知道,但是我们在不知道 & 和 [] 的相对优先级的情况下提供了清晰的服务。【参考方案5】:
以下每一行都会产生完全相同的输出:
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+42
和 42+array
之间没有区别。这会导致有趣的混淆:如果您的目标是完全混淆您的代码,请使用 42[array]
而不是 array[42]
。不用说,如果您的目标是编写可理解、可维护的代码,那么编写 42[array]
是一个糟糕的主意。
如何将布尔值转换为整数。
给定a[b]
形式的表达式,a
或b
必须是指针表达式,而另一个;另一个必须是整数表达式。给定表达式"\n"[a==N]
,"\n"
表示该表达式的指针部分,a==N
表示表达式的整数部分。这里,a==N
是一个布尔表达式,其计算结果为false
或true
。整数提升规则指定 false
在提升为整数时变为 0,true
变为 1。
字符串文字如何降级为指针。 当需要指针时,C 和 C++ 中的数组很容易降级为指向数组第一个元素的指针。
如何实现字符串字面量。
每个 C 风格的字符串文字都附加了空字符 '\0'
。这意味着"\n"
的内部表示是数组'\n', '\0'
。
鉴于上述情况,假设 a==N
的计算结果为 false
。在这种情况下,行为在所有系统中都是明确定义的:您将得到一个换行符。另一方面,如果a==N
的计算结果为true
,则该行为高度依赖于系统。根据 cmets 对问题的回答,Windows 不会喜欢这样。在std::cout
通过管道传送到终端窗口的类Unix 系统上,这种行为是相当良性的。什么都没有发生。
仅仅因为您可以编写这样的代码并不意味着您应该这样做。永远不要写那样的代码。
【讨论】:
@MarkHurd - 所有四个语句都完全做同样的事情。请阅读索引如何在 C 和 C++ 中的原始数组上工作。关于以文本模式将'\0'
写入输出,这在 unix 和 linux 机器上是完全可以的。它一直在发生。二进制模式,文本模式?那是什么? Unix 和 linux 不区分这两者。 C 和 C++ 标准的某些部分向 Windows 致敬,其他部分向 unix 和 linux 致敬,而其他部分向其他体系结构致敬。不要那么以 Windows 为中心。
我在你的后两个陈述中没有看到你的*
。对不起。
@MarkHurd - 我将添加一些间距以使其明显。
从后两个语句中删除*
会导致带有clang 的愚蠢警告。我必须用Wno-string-int
编译才能使编译干净。在前两个语句中添加一个 & 符号具有相同的效果,即使没有该编译器选项也可以编译干净,并且它具有增加混淆级别的额外好处。 (显然由于这个编译器警告,gnu 的 binutils 从 "some_string"+some_int
修改为 &"some_string"[some_int]
。)
请注意:上面的评论还有增加混淆级别的额外好处是非常开玩笑的。良好的编程实践意味着避免增加混淆级别。以上是关于cout << "\n"[a==N]; 是啥意思?做?的主要内容,如果未能解决你的问题,请参考以下文章