【问题标题】:Why does this code print lines and not single chars为什么此代码打印行而不是单个字符
【发布时间】:2019-03-16 23:55:54
【问题描述】:
#include <stdio.h>
int main()
{
  int c;
  while((c=getchar())!=EOF)
  putchar(c);
  return 0;
}

在我对 C 的有限接触中,只要 C!=EOF 就会执行 while 条件,它基本上会打印 c,然后等待下一个字符并尝试检查是否违反条件。因此,对于我们输入的每个字符,putchar 都会执行。 但是,当我运行可执行文件时,似乎发生的事情是打印语句仅在我按下换行符(Enter)时才起作用,并且它具有某种类型的缓冲区,可以在输入中打印整行。 在按下新行之前,我看不到代码中将保存字符的任何缓冲区,那么这里正在做什么? 在按下新行之前,这些字符将存储在哪里?

【问题讨论】:

  • 在您的终端中,按回车后,在用于标准输入的FILE 结构中。
  • 你如何区分你是在读取行块中的字符还是单独读取字符?您的程序运行得非常快,可能不会让您有机会看到它是如何进行的。终端以行模式工作,允许您在将输入发送到程序之前对其进行编辑。请参阅我的答案以获得更完整的解释。
  • "那么这里正在做什么" 您的终端/控制台/任何智能的东西都可以让您在将输入发送到程序之前对其进行编辑。 “在按下新行之前,这些字符在哪里存储”在终端驱动程序中。此外,标准库中还有缓冲区(您可以禁用)。

标签: c putchar


【解决方案1】:

当像这样从控制台读取时,您的程序实际上根本不会收到任何输入,直到您按下回车键,然后它会立即读取并处理整行。

在您按下回车键之前,您的程序根本不知道这些字符是否存在,此时它们会被发送到标准输入缓冲区。从这里开始,您的循环将读取并打印每个字符(包括新行),直到缓冲区为空(意味着它将等待更多输入)或到达文件末尾。

【讨论】:

    【解决方案2】:

    缓冲发生在数据的读取数据的写入中。

    stdinstdout,可以独立地被字符完全缓冲。

    在您的常见情况下,两者都是 line 缓冲的。

    直到 Enter ('\n') 被击中,getchar() 才会输入。 (您可以退格输入吗?)并且在打印'\n' 之前不会显示输出。

    【讨论】:

    • 这有点误导。退格处理等与缓冲无关。它发生在程序之外。 程序 直到输入被击中。缓冲发生在此之上。
    • @n.m. “在输入被击中之前,输入不会提供给程序。” --> 这是一个可能的,虽然不是必需的实现(它很常见)。 “你能退格输入吗?”是让 OP 尝试查看之前的键和退格键可能没有出现在 stdin 中,因此证明输入的缓冲发生在 someplace 而不仅仅是 OP 建议的输出中。
    • 几乎不需要任何东西。例如,不要求终端是“交互设备”。我说的是真实情况,而不是标准允许的任何可能情况。
    • @n.m. “在输入被击中之前,输入不会提供给程序。”现实生活中的管道输入通常不是这样。
    • 我对发明各种可以解释这种行为的场景并不感兴趣。我将选择实际发生的事实。用户正在终端中运行程序。
    【解决方案3】:

    getchar()putchar() 函数是 stdio 包的一部分,用于进行缓冲输入/输出。

    这意味着,尽管您实际请求的字符数(仅在您的情况下为一个),但缓冲机制会使字符在缓冲区中等待,直到整个缓冲区(或一行,以防 stdin 来自tty 设备)已满。

    这里发生的情况是,首先getchar() 请求一个完整的缓冲区填充,并且只用您键入的输入行填充部分。之后,所有getchar() 调用都从缓冲区中获取单个字符,直到它再次为空,此时请求另一个完整的缓冲区。

    这是为了使单字符处理成为一种有效的处理方式。这也发生在输出上。 putchar() 函数只填充缓冲区,直到它完全填满(或者,如果你的输出通道是到 tty 设备,直到你请求输出一个 \n 字符),当它填满时,输出完整的缓冲区到文件/设备。

    此外,所有风格的 unix 中的终端驱动程序都在行模式下工作,这意味着在您按下 &lt;ENTER&gt; 键之前,您不会收到任何发送到程序的信息。这将允许您通过使用擦除键(退格键)和/或终止键(Cntrl-U)来纠正键入错误。这是在 Windows 控制台应用程序中模拟的,因此您可能需要将输出设备设为原始模式,然后才能以基于字符的方式输入。

    如果你想让你的程序一次读取一个字符,你需要绕过stdio的缓冲区(通过setbuf(NULL)函数调用,或者使用read(2)系统调用读取一个字符,可以是完成:

    char c;
    int res = read(0, &c, 1);
    if (res < 0) { /* error */
        ...
    } else if (res == 0) { /* EOF in input */
        ...
    } else { /* 1, as you requested only one char */
        ... /* one character was read. */
    }
    

    )并在实际阅读之前将终端驱动程序置于原始模式(通过tcsetattr()tcgetattr() 调用,有关如何执行此操作的详细信息,请参见termios(4) 手册页)。在这种情况下,必须在终止程序之前将终端状态返回到线路模式,否则会遇到麻烦。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-08-02
      • 2014-11-23
      • 1970-01-01
      • 2017-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-26
      相关资源
      最近更新 更多