【发布时间】:2015-09-02 12:39:08
【问题描述】:
在以下示例中:
cout<<"\n"[a==N];
我不知道[] 选项在cout 中的作用,但是当a 的值等于N 时,它不会打印换行符。
【问题讨论】:
标签: c++ cout string-literals
在以下示例中:
cout<<"\n"[a==N];
我不知道[] 选项在cout 中的作用,但是当a 的值等于N 时,它不会打印换行符。
【问题讨论】:
标签: c++ cout string-literals
我不知道 [] 选项在 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.2Streams 部分中的 C11 草案标准说:
[...]从文本流中读取的数据必须与数据比较 仅在以下情况下才写入该流:数据仅包含打印 字符和控制字符水平制表符和换行符;
和打印字符覆盖在7.4字符处理
[...]术语控制字符 指的是特定于语言环境的字符集的成员,这些字符不打印 characters.199) 所有字母和数字都是打印字符。
脚注199 说:
在使用七位 US ASCII 字符集的实现中,打印字符是那些 其值从 0x20(空格)到 0x7E(波浪号);控制字符是那些 值从 0 (NUL) 到 0x1F (US),字符 0x7F (DEL)。
最后我们可以看到没有指定发送空字符的结果,我们可以从4 Conformance 部分看到这是未定义的行为:
[...]否则未定义的行为 在本国际标准中用“未定义行为”或 省略任何明确的行为定义。[...]
我们也可以查看C99 rationale,上面写着:
需要在文本流 I/O 中保留的字符集是编写 C 所需的字符集 程式;其目的是标准应该允许 C 翻译器以最大程度地编写 便携时尚。为此目的不需要诸如退格之类的控制字符,因此它们的 文本流中的处理不是强制性的。
【讨论】:
""。
cout << '\0'的作用是什么
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');
【讨论】:
这可能是一种奇怪的写作方式
if ( a != N ) {
cout<<"\n";
}
[] 运算符从数组中选择一个元素。字符串"\n" 实际上是一个包含两个字符的数组:一个新行'\n' 和一个字符串终止符'\0'。所以cout<<"\n"[a==N] 将打印'\n' 字符或'\0' 字符。
问题是您不能在文本模式下将'\0' 字符发送到 I/O 流。该代码的作者可能已经注意到似乎没有发生任何事情,因此他认为cout<<'\0' 是一种安全的不做任何事情的方法。
在 C 和 C++ 中,由于未定义行为的概念,这是一个非常糟糕的假设。可以发生。在这种情况下,一个相当可能的结果是流将完全停止工作 - 根本不会再出现到 cout 的输出。
总的来说,效果是,
“如果
a不等于N,则打印一个换行符。否则,我不知道。崩溃什么的。”
……道德是,不要写得这么神秘。
【讨论】:
int 的大小未定义,但这使其未指定。
'\0' 到std::cout 是否是未定义的行为。从我对标准的阅读来看,事实并非如此。仅仅因为 Windows 做了一些不同的事情并不意味着它是 UB。这只是意味着 Windows 再次做了一些不同的事情。
不是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]
以下每一行都会产生完全相同的输出:
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 系统上,这种行为是相当良性的。什么都没有发生。
仅仅因为您可以编写这样的代码并不意味着您应该这样做。永远不要写那样的代码。
【讨论】:
'\0' 写入输出,这在 unix 和 linux 机器上是完全可以的。它一直在发生。二进制模式,文本模式?那是什么? Unix 和 linux 不区分这两者。 C 和 C++ 标准的某些部分向 Windows 致敬,其他部分向 unix 和 linux 致敬,而其他部分向其他体系结构致敬。不要那么以 Windows 为中心。
*。对不起。
* 会导致带有clang 的愚蠢警告。我必须用Wno-string-int 编译才能使编译干净。在前两个语句中添加一个 & 符号具有相同的效果,即使没有该编译器选项也可以编译干净,并且它具有增加混淆级别的额外好处。 (显然由于这个编译器警告,gnu 的 binutils 从 "some_string"+some_int 修改为 &"some_string"[some_int]。)