【问题标题】:Ending a Loop with EOF (without enter)用 EOF 结束循环(不输入)
【发布时间】:2017-10-26 09:15:39
【问题描述】:

我目前正在尝试用这样的方式结束一个 while 循环:

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

}

当我在我的 Ubuntu 上按 CTRL+D 时,它会立即结束循环。但在 Windows 上,我必须按 CTRL+Z 然后按 ENTER 关闭循环。我可以摆脱 Windows 上的ENTER 吗?

【问题讨论】:

  • 您是否ENTER开始新行之后直接按了CTRL+Z?
  • 是的,我是在开始新行后直接完成的
  • 您的循环对getchar() 的调用次数过多 - 您只需要一个并将其返回的值分配给变量,以便您可以在循环中使用它

标签: c linux windows eof


【解决方案1】:

getchar 行为

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

据报道here

在 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 上从来没有必要。

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

其他信息也上报here

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

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

字符 26 用于标记“文件结束”,即使 ASCII 将其称为替代,并且有其他字符。

如果你这样修改你的代码:

#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 上您会看到 EOF (-1) 在您按下 enter 之前它不会写入控制台。除此之外,^Z 是由终端仿真器打印的(我怀疑)。根据我的测试,如果出现以下情况,则会重复此行为:

  • 您使用 Microsoft 编译器进行编译
  • 你使用 GCC 编译
  • 在 CMD 窗口中运行编译好的代码
  • 你在windows的bash模拟器中运行编译后的代码

使用 Windows 控制台 API 更新

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

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

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

这是代码:

#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;
}

【讨论】:

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

这里是不一致的。

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

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

你想做什么?

【讨论】:

  • 这么多年才看到这个,是的,我很确定应该只有一个 getchar()
猜你喜欢
  • 1970-01-01
  • 2012-05-14
  • 2015-06-07
  • 2013-10-21
  • 2022-01-18
  • 2015-05-30
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多