使用 EOF 结束循环(不带回车)

Ending a Loop with EOF (without enter)

提问人:Michael Hübler 提问时间:10/26/2017 最后编辑:Komal12Michael Hübler 更新时间:10/29/2017 访问量:3081

问:

我目前正在尝试用这样的东西结束 while 循环:

#include <stdio.h>
int main() 
{
    while(getchar() != EOF)
    {
        if( getchar() == EOF )
            break;
    }
    return 0;

}

当我按下我的 Ubuntu 时,它会立即结束循环。但是在 Windows 上,我必须按下然后按下才能关闭循环。我可以摆脱 Windows 上的吗?CTRL+DCTRL+ZENTERENTER

C Linux Windows EOF

评论

0赞 Ctx 10/26/2017
您是否在开始新行后直接按 CTRL+Z?ENTER
0赞 Michael Hübler 10/26/2017
是的,我在开始新生产线后直接做到了
1赞 Chris Turner 10/26/2017
你的循环有 1 次调用太多 - 你只需要一个,并将它返回的值分配给一个变量,这样你就可以在循环中使用它getchar()

答:

7赞 Matteo Ragni 10/26/2017 #1

getchar 行为

对于 linux,EOF char 是用 + 编写的,而在 Windows 上,当您在通过 + 更改 CRT 库的内部状态后按下时,它是由控制台写入的(保留此行为是为了与非常旧的系统进行逆向兼容)。如果我没记错的话,它被称为文件的软端。我认为你不能绕过它,因为 EOF 字符实际上是在你按下时消耗的,而不是当你按 + 时。ctrldenterctrlzgetcharenterctrlz

以下报道:

在 Microsoft 的 DOS 和 Windows(以及 CP/M 和许多 DEC 操作系统)中,从终端读取永远不会产生 EOF。取而代之的是,程序识别源是终端(或其他“字符设备”),并将给定的保留字符或序列解释为文件结束指示符;最常见的是 ASCII Control-Z,代码 26。某些 MS-DOS 程序(包括Microsoft MS-DOS shell (COMMAND.COM) 和操作系统实用程序(如 EDLIN)的一部分)将文本文件中的 Control-Z 视为标记有意义数据的末尾,和/或在写入文本文件时将 Control-Z 追加到末尾。这样做有两个原因:

  • 向后兼容 CP/M。CP/M 文件系统仅以 128 字节“记录”的倍数记录文件的长度,因此按照惯例,如果有意义数据在记录中间结束,则使用 Control-Z 字符来标记该数据的结尾。MS-DOS 文件系统始终记录文件的确切字节长度,因此在 MS-DOS 上从不需要这样做。

  • 它允许程序使用相同的代码从终端和文本文件读取输入。

此处还报告了其他信息:

一些现代文本文件格式 (e.g. CSV-1203[6]) 仍然建议将尾随的 EOF 字符附加为文件中的最后一个字符。但是,键入 Ctrl+Z 不会将 EOF 字符嵌入到 MS-DOS 或 Microsoft Windows 中的文件中,这些系统的 API 也不会使用该字符来表示文件的实际结尾。

某些编程语言(例如 Visual Basic)在使用内置文本文件读取基元(INPUT、LINE INPUT 等)时不会读取“软”EOF,并且必须采用替代方法,例如以二进制模式打开文件或使用文件系统对象来超越它。

字符 26 用于标记“文件末尾”,即使 ASCII 将其称为 Substitute,并且还有其他字符。

如果像这样修改代码:

#include <stdio.h>

int main() {
  while(1) {
    char c = getchar();
    printf("%d\n", c); 
    if (c == EOF)      // tried with also -1 and 26
      break;
  }
  return 0;
}

然后你测试它,在 Windows 上你会看到 (-1) 它没有写在控制台中,直到你按 .Beore of that a 是由终端仿真器打印的(我怀疑)。根据我的测试,如果出现以下情况,则会重复此行为:EOFenter^Z

  • 使用 Microsoft 编译器进行编译
  • 使用 GCC 进行编译
  • 在 CMD 窗口中运行编译的代码
  • 在 Windows 的 Bash 模拟器中运行已编译的代码

使用 Windows 控制台 API 进行更新

按照@eryksun的建议,我成功地为Windows编写了一个(对于它可以做什么来说非常复杂)代码,该代码更改了conhost的行为,以实际获得“按+时退出”。它不能处理所有事情,它只是一个例子恕我直言,这是尽可能避免的事情,因为可移植性小于 0。此外,为了正确处理其他输入情况,应该编写更多的代码,因为这些东西将 stdin 与控制台分离,您必须自己处理它。ctrld

这些方法或多或少地工作如下:

  • 获取标准输入的当前处理程序
  • 创建一个输入记录数组,该结构包含有关主机窗口中发生的情况(键盘、鼠标、调整大小等)的信息。
  • 读取窗口中发生的情况(它可以处理事件数)
  • 遍历事件向量以处理键盘事件并拦截退出所需的 EOF(即 4,来自我测试的内容)或打印任何其他 ascii 字符。

这是代码:

#include <windows.h>
#include <stdio.h>

#define Kev input_buffer[i].Event.KeyEvent // a shortcut

int main(void) {
  HANDLE h_std_in;                // Handler for the stdin
  DWORD read_count,               // number of events intercepted by ReadConsoleInput
        i;                        // iterator
  INPUT_RECORD input_buffer[128]; // Vector of events

  h_std_in = GetStdHandle( // Get the stdin handler
    STD_INPUT_HANDLE       // enumerator for stdin. Others exist for stdout and stderr
  ); 

  while(1) {
    ReadConsoleInput( // Read the input from the handler
      h_std_in,       // our handler 
      input_buffer,   // the vector in which events will be saved
      128,            // the dimension of the vector
      &read_count);   // the number of events captured and saved (always < 128 in this case)

    for (i = 0; i < read_count; i++) {    // and here we iterate from 0 to read_count
      switch(input_buffer[i].EventType) { // let's check the type of event 
        case KEY_EVENT:                   // to intercept the keyboard ones
          if (Kev.bKeyDown) {             // and refine only on key pressed (avoid a second event for key released)
            // Intercepts CTRL + D
            if (Kev.uChar.AsciiChar != 4)
              printf("%c", Kev.uChar.AsciiChar);
            else
              return 0;
          }
          break;
        default:
          break;
      }
    }
  }

  return 0;
}

评论

0赞 Eryk Sun 10/29/2017
从本质上讲,控制台没有预先确定的控制字符来指示正常返回,而无需按回车键。如果一行以 Ctrl+Z (“\x1a”) 开头,则返回读取的 0 个字符,该实现在 C 运行时中也很常见。控制台本身 (conhost.exe) 没有这样的行为,如果您调用(以获得 Unicode 支持),则必须手动实现此行为。也就是说,它有一个更好的行为,这正是 OP 想要的——它的参数可以实现一个类似 Unix 的 Ctrl+D。ReadFileReadConsoleWReadConsoleWpInputControl
0赞 Eryk Sun 10/29/2017
仅供参考,没有CMD窗口。CMD 是一个标准的 I/O 应用程序,可以像任何其他控制台应用程序一样使用控制台(conhost.exe实例)。它具有控制台输入和屏幕缓冲区的标准句柄,并可以访问与任何其他 Windows 应用程序相同的控制台 API 函数(例如 、 等)。GetConsoleModeSetConsoleTitle
0赞 Matteo Ragni 10/29/2017
谢谢你,我已经按照你的建议更新了答案,我得到了一个“EOF退出”......这很糟糕(主要是因为我以前从未关心过使用 winapi,而且我显然不擅长),但我不想花太多时间在上面。此外,我理解你在说什么“没有cmd窗口”。对我来说,C:\Windows\system32\cmd.exe是命令窗口。使用 conhost 和 windows API 的事实不会改变它在常见 Windows 安装中默认为 CMD 的事实。
0赞 Eryk Sun 10/31/2017
cmd.exe不是控制台,也从未出现在任何版本的 Windows 中。cmd.exe不再是控制台,而是powershell.exe或python.exe。CMD 不创建任何窗口。它甚至不加载user32.dll。它是一个简单的控制台客户端,它使用其 、 和 句柄作为控制台输入和屏幕缓冲区 -- 或重定向到管道、文件、NUL 等。作为控制台应用程序,它还具有连接到其附加控制台的连接句柄,即 PEB 中的 。许多控制台函数隐式使用此句柄。StandardInputStandardOutputStandardErrorConsoleHandleProcessParamaters
0赞 Eryk Sun 10/31/2017
我建议与 pInputControl 参数一起使用,而不是低级函数。具体来说,我建议使用位掩码,例如.它留下控制字符(例如 或 ),因此由您的代码来搜索它们并执行任何您想做的事情(例如,将其替换为 NUL)。ReadConsoleWReadConsoleInputdwCtrlWakeupMaskinputCtrl.dwCtrlWakeupMask = 1 << ('D' - '@') | 1 << ('Z' - '@')L"\x04"L"\x1A"
1赞 alinsoar 10/26/2017 #2
    while(getchar() != EOF)
    {
        if( getchar() == EOF )
            break;
    }
    return 0;

这里是不一致的。

如果它将进入循环,否则(如果)它不会进入循环。因此,没有理由检查循环内部。getchar() != EOFgetchar() == EOFgetchar() == EOF

另一方面,您调用 2 次,您等待输入 2 个字符而不是仅输入 1 个字符。getchar()

你试着做了什么?

评论

0赞 Michael Hübler 1/3/2022
这么多年后才看到这个,是的,我很确定应该只有一个 getchar()