【问题标题】:Why is there an extra "*" in the output of this program?为什么这个程序的输出中有一个额外的“*”?
【发布时间】:2018-12-29 12:00:01
【问题描述】:
#include <stdio.h>

int main () 
{ 
  int c; 
  while ((c = getchar ()) != EOF) 
    { 
      putchar (c); 
      printf ("*"); 
    }


  return 0; 
}

当我运行这个程序时,我得到的输出是

qwerty                                          

q * w * e * r * t * y *
* 

#我不知道最后一个“*”是如何打印的。

这与putchar()的回归有关。那么putchar() 函数实际上是如何返回的。我知道它在达到 EOF 后返回,所以在这种情况下,它不会打印任何东西,printf("*") 将被执行。但问题是,为什么最后一个* 会打印在下一行。是不是像putchar() 返回并将打印指针移到新行?

我克服这个疑问的一个理论是,如果我在输入后不按“输入”,* 将不会被打印。但它再次提出了一个问题,即我如何在不按 Enter 的情况下获得输出(在给出输入之后)?为什么会出现最后一个* 是因为按了“回车”?

【问题讨论】:

    标签: c io output getchar putchar


    【解决方案1】:

    当您输入“qwerty”并按回车键时,新行字符\n 将附加到输入。因此,程序的输入将是:

    "qwerty\n"
           ^^
    

    当循环处理最后一个(换行符)字符时,即打印的 \n 字符实际上将光标移动到下一行,最后一个星号在它之后打印。

    为了不让输入的尾随换行符,您可以在while循环条件中添加对换行符的检查:

    while ((c = getchar ()) != EOF && c != '\n')
    

    或者,您可以给出文件结尾EOF 字符,而不是在输入输入后按回车键。对于EOF,在 Unix 上按 Control+D,在 Windows 上按 Control+Z

    【讨论】:

    • 但是当我声明变量时,说 n 并用 0 初始化它。现在,我用条件 'n
    • 您必须使用 minimal reproducible example 证明该问题,作为新的单独问题的一部分。请不要从原始问题更改此问题。这里已经有答案,其中一些已被投票。
    • @aambazinga 我看不出有任何原因使它不起作用。它应该工作。显示 while 循环条件。
    • @H.S.原因与此构造完全“有效”相同:stdin 上的行缓冲。我假设他没有按回车键,并希望在 10 个字符后出现一些输出。不会发生。
    • @H.S. ctrl+d 正在工作.. 但是 putchar 是从与输入相同的行执行的。 ctrl+z 的工作方式不同.... (1)- 它没有放置所有字符,除了最后一个 *。 (2)-在输入后跳过一行,然后打印*。
    【解决方案2】:
    #include <stdio.h>
    int main () 
    { 
      int c; 
      while ((c = getchar ()) != EOF) 
        { 
          putchar (c); 
          printf ("*"); 
        }
      return 0; 
    }
    

    输出:

    QWERTY<Enter>
    
    Enter also Taking as Character 
    last '*' for <Enter> Character..
    

    【讨论】:

      【解决方案3】:

      您的代码将为每个打印的其他字母打印一个 *
      打印一个换行符,注意最后一行 * 是如何单独出现在最后一行的。
      最后一个* 用于\n,当您使用返回键输入输入时会读取它。

      您可能希望已经在该换行符处停止循环,即

      while (((c = getchar ()) != EOF) && (c!='\n'))
      

      【讨论】:

        【解决方案4】:

        如何在不按回车的情况下获得输出(在输入后)?

        您可以在“qwerty”之后按Ctrl-D,而不是按Enter

        【讨论】:

        • 不做进一步解释,这应该是注释。请注意,这也取决于平台。
        • @FelixPalmen 这是一个部分答案,回答了实际上所有其他部分答案都没有回答的问题的一部分。不是吗?
        • @Yunnosch 可能是,但它缺少解释。如果不是因为 Ctrl-D 不是从键盘提供 EOF 的通用标准,我会很高兴:o - AFAIK,这已经不适用于 Windows,Ctrl-Z 应该而是。
        • @FelixPalmen 这是真的。但它仍然是一个答案,而不是评论。假设这是一个糟糕的答案,但它是一个答案。
        • @Yunnosch 一个定义问题......是的,我不会标记这个 NAA,但如果它是一个答案,它是部分 部分错误,我会说将这种小提示作为 cmets 给出是一种常见的做法。
        【解决方案5】:

        这与putchar()的返回有关。那么putchar() 函数实际上是如何返回的。我知道它会在到达 EOF 后返回,所以在这种情况下,它不会打印任何内容,printf("*") 将被执行。

        这很混乱,您在这里完全走错了路。 putchar() 只输出一个字符,并在此输出完成后立即返回。事实上,你可以像这样编写相同的代码:

        while ((c = getchar()) != EOF) 
        { 
           putchar(c); 
           putchar('*'); 
        }
        

        这意味着只要getchar()成功(没有文件结尾),输出你收到的字符,然后立即输出一个星号。

        事实上,当您按下键qwerty 时应该会看到如下内容:

        qq*ww*ee*rr*tt*yy*
        

        这是假设您的终端执行“本地回显”,这意味着它会显示您输入的任何内容。

        在按 Enter 之前看不到任何输出的原因(或 EOF 的平台相关控制键,例如在 *nix 系统 Ctrl-D 上)仅仅是stdin,您在 C 中的输入流,是由默认行缓冲。这意味着在上面的循环中,getchar() 将永远不会返回,除非您有一整行输入或到达 EOF。

        只要getchar() 返回,您的循环就会运行。如果您按下回车,输入缓冲区中将有一个换行符,getchar() 也会读取它,因此它也将由循环内的putchar() 输出,后跟一个星号。

        但是请注意,可以更改缓冲模式...如果您在 stdin 上禁用缓冲,您可能会看到我上面描述的内容。


        可靠地实现您想要的一种明智的方法是读取代码中的整行并遍历您读取的字符。例如,这可能如下所示:

        char buffer[1024];
        if (fgets(buffer, 1024, stdin))
        {
            for (char *c = buffer; *c; ++c)
            {
                putchar(*c);
                if (*c != '\n') putchar('*');
            }
        }
        

        使用这样的代码,您拥有自己的缓冲区,并且不依赖于输入流的缓冲模式。

        【讨论】:

        • 好的,所以你是说每当遇到新字符时,都会调用 putchar()。如果我做对了,那么有什么可以检查字符的出现?
        • 好吧,getchar()?正如我所说,您的输入流是行缓冲的,所以getchar() 等待输入,直到一行完成....
        【解决方案6】:

        欢迎访问该网站。 HS已经回答了你的主要问题。尽管如此,我还是想添加更多解释并直接解决您的其他问题之一。

        终端属性

        但它再次提出了一个问题,即如何在不按 Enter 的情况下获得输出(在给出输入之后)?

        您需要按 Enter 键的原因是您的终端默认处于“规范”模式。来自tcsetattr 的 UNIX 手册:

        规范模式下:

        输入是逐行提供的。当键入行分隔符之一(NL、EOL、EOL2;或在行首的 EOF)时,输入行可用。除了在 EOF 的情况下,该行 分隔符包含在 read(2) 返回的缓冲区中。

        请注意“包含行分隔符”这一事实也间接回答了您的问题

        为什么最后一个 * 是因为按了“enter”?

        (因为按 Enter 会插入 NL 行分隔符 - 程序只是将其与其余字符一起放入输入缓冲区)。

        根据您的平台,您可以禁用规范模式

        禁用规范模式

        注意:以下信息可能因平台而异。 tcsetattr 函数等。 al 是 POSIX 标准的一部分 - 但我不知道它们是否可以在 Windows 上运行。

        例如,在 Linux 上,以下代码禁用规范模式,并指示程序在用户键入字符后立即读取输入。

        #include <stdio.h>
        #include <string.h>
        #include <termios.h>
        
        int main(void) {
            char c = '\0';
            struct termios orig_attr, new_attr;
        
            tcgetattr(fileno(stdin), &orig_attr);
            memcpy(&new_attr, &orig_attr, sizeof(struct termios));
            new_attr.c_lflag &= ~(ICANON | ECHO);  // Disable canonical mode
            tcsetattr(fileno(stdin), TCSANOW, &new_attr);
        
            while ((c = getchar()) != '\n') {
                printf("%c * ", c);
            }
            puts("");
        
            // Restore original terminal attributes
            tcsetattr(fileno(stdin), TCSANOW, &orig_attr);
        
            return 0;
        }
        

        注意事项:

        • 上述代码还禁用了终端“echo”(否则您会看到每个字符都打印了两次)。
        • 您可能还想设置new_attr.c_cc[VTIME]new_attr.c_cc[VMIN] 以更好地控制读取缓冲区。 (详情请参阅man page。)
        • 最终 - 取决于您的最终目标 - 使用 fgetsgetline 可能更容易?

        【讨论】:

        • 这真是一件有趣的事情..我已经尝试过了,输入没有被缓冲,直接输出被打印出来。
        • 我这里有一个问题,所以如果我们禁用规范模式,那么必须在处理器中禁用上下文切换。因为如果不是这样,那么由于缺少某些输入必须丢失缓冲区..我说的对吗?
        • 没有。不要试图不尊重这个答案,但我建议你暂时保持在 C 定义的范围内。引入特定于操作系统的内容只会增加混乱。
        • @aambazinga:我不是 100% 确定您所说的“处理器中的上下文切换......”是什么意思。你的意思是操作系统中的kernel(用于切换进程)?如果是这样,那么不需要——你当然不需要这种激进的措施。我想不出输入可能会“丢失”的情况(尽管其他读者可能知道)。
        • @aambazinga:这种技术在 Linux/UNIX 操作系统上并不少见。尽管如此,如果您需要与操作系统无关的东西,也许可以使用更简单的选项,例如fgets(正如 Felix Palmen 建议的那样)。无论如何......以上希望至少可以帮助您理解这个问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-18
        • 2022-11-16
        • 1970-01-01
        • 2015-10-13
        • 1970-01-01
        相关资源
        最近更新 更多