为什么在一行的末尾放置EOF字符不起作用?

13

我正在学习C++,并试图理解为什么将EOF字符(在Windows上为Ctrl + Z)放在行末时无法打破while循环?

我的代码:

    int main() {
        char ch;
        while(cin >> ch) {
            cout << ch;
        }
    }

当我输入^Z时,循环会中断;
但当我输入12^Z时,它不会中断。


可能与12^z != ^z有关...12^z不会评估为false。 - Mare Infinitus
1
Unix系统的工作方式是相同的;在一行的中间按下CTRL-D会被忽略(或者可能不完全忽略;bash shell会发出哔哔声,但仍然会忽略它),它只在一行的开头起作用。我不知道是否有真正的原因,或者在Unix发明时,某个人认为这将是一个好东西,并且从那时起一直保持着,没有人知道为什么 :-) - Christian Stieber
1
@ChristianStieber:在类Unix系统中,单个Control-D会在行首触发文件结束条件;否则,两个 Control-D 会触发文件结束条件。 - Keith Thompson
3个回答

9

在C++标准中,您不会找到对您问题的答案。

cin >> ch只要没有遇到文件结尾或输入错误,就会是“真”的条件。如何触发文件结尾条件没有被语言规定,它可以且通常会因操作系统而异,即使在同一操作系统中的配置选项也会有所不同。 (例如,类Unix系统默认使用control-D,但这可以通过stty命令进行更改。)

Windows使用Control-Z来触发文本输入流的文件结尾条件;除了在行首之外,它实际上不会在其他位置触发文件结尾条件。

Unix的行为略有不同; 它在行首使用Control-D(默认情况下),或者在一行中使用两个 Control-Ds。

对于Unix,这仅适用于从终端读取时; 如果您正在从文件中读取,control-D只是另一个非打印字符,不会触发文件结尾条件。 当从磁盘文件中读取时,Windows似乎会认识到control-Z作为文件结尾触发器。

底线:不同的操作系统行为不同,很大程度上是因为历史原因。 C ++设计为与这些行为中的任何一种配合使用,这就是为什么它在某些细节上不具体说明的原因。


4
C和C++标准允许文本流在默认的文本模式下进行一些非常不可思议的操作。这些不可思议的事情包括将内部换行符转换为外部换行控制字符,以及将某些字符或字符序列视为文件结尾。在Unix系统中,这是不被允许的,但在Windows系统中,这是被允许的。因此,代码只能与原始的Unix约定相关联。
这意味着在Windows中,没有办法编写一个可移植的C或C++程序,将其输入完全复制到其输出中。
而在Unix系统中,这根本不是问题。
在Windows中,由单个[Ctrl Z]组成的行通常是文件的结束标志。这不仅适用于控制台,还适用于文本文件(取决于工具)。Windows从DOS继承了这一点,DOS又从CP/M继承了这一通用想法。
我不确定CP/M从哪里得到的,但它与Unix的[Ctrl D]只是相似,根本不同!
在Unix系统中,文件结尾的一般约定是“没有更多数据”。在控制台上,默认情况下[Ctrl D]会立即将您键入的文本发送到等待的程序。当您还没有在该行上键入任何内容时,将发送0字节,并且读取返回0字节表示遇到了文件结尾。
主要区别在于,在Windows中,文本文件的内部结尾标记是数据,可以出现在文件中,而在Unix中,它是缺少数据,不能出现在文件中。当然,Windows也支持普通的文本文件结尾(没有更多数据!)。这使得事情变得更加复杂——Windows就是更加复杂。
#include <iostream>
using namespace std;

int main()
{
    char ch;
    while(cin >> ch) {
        cout << 0+ch << " '" << ch << "'" << endl;
    }
}

我仍然不理解的是,当我的代码中的1和2被读取并放入ch时,它们应该从cin中消失,因此剩下的唯一字符是[Ctrl + Z],就像由单个EOF组成的行一样。然后cin.eof()应该返回true。 - Cutter
2
在输入中,单独一行的 [Ctrl Z] 是一回事。在 Windows 中它会被转义。而在文本翻译中幸存下来的 [Ctrl Z] 则完全不同。请尝试我现在添加到答案中的代码。 - Cheers and hth. - Alf

1

这是由于cin >> ^Z将被评估为false。

更详细地说:cin.eof()将在此返回true,因此隐式调用eof()的while将返回false,从而结束循环。

如果您输入12^Z,则eof()将返回false,因为它可以解析有效的输入值,因此它不会停止循环。

您可能还对此SO感兴趣:

关于标志语义的SO


1
谢谢您的澄清。然而,据我所知,当一个字符被放入ch中时,它就从cin中删除了。因此,在1和2被放入ch之后,只有^Z留在cin中,cin.eof()应该返回true。那么循环应该会终止,对吗? - Cutter
据我所知,输入的字符串不是按顺序计算的。它们被视为单个输入进行计算。只有当此输入为 eof 时,才将 eof 设置为 true。 - Mare Infinitus
但是为什么在读取完EOF之前的每个字符后,eof()没有被设置为true呢? - Cutter
正如我所说,据我所知,输入不是按顺序逐个读取的,而是以块为单位读取。 - Mare Infinitus
我对这是否解释清楚感到怀疑。即使它可以解析有效的输入值,它仍然应该在此之后触发eof,不是吗?而且块根本不需要通过换行符连接,尽管在实践中它们通常会这样做。 - leftaroundabout
不应该。那个块中有一些输入,特别不是 eof。但这似乎是一些品味问题。 - Mare Infinitus

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