【问题标题】:Reading newline from previous input when reading from keyboard with scanf()使用 scanf() 从键盘读取时从先前的输入读取换行符
【发布时间】:2015-07-06 10:52:34
【问题描述】:

这本来应该很简单,但我无法从键盘读取连续的输入。

代码如下:

#include <string.h>
#include <stdio.h>

int main()
{
    char string[200];
    char character;
    printf ("write something: ");
    scanf ("%s", string);
    printf ("%s", string);
    printf ("\nwrite a character: ");
    scanf ("%c", &character);
    printf ("\nCharacter %c  Correspondent number: %d\n", character, character);

    return 0;
}

发生了什么

当我输入一个字符串(例如:计算机)时,程序会读取换行符('\n')并将其放入character。下面是显示的样子:

 write something: computer
 computer
 Character:
    Correspondent number: 10

此外,该程序不适用于包含多个单词的字符串。 我怎样才能克服这些问题?

【问题讨论】:

标签: c string char


【解决方案1】:

首先scanf 读取输入的字符串并将\n 留在输入缓冲区中。下次调用scanf 读取\n 并将其存储到character
试试这个

scanf (" %c", &characte);   
     // ^A space before %c in scanf can skip any number of white space characters. 

程序对超过一个字符的字符串不起作用,因为scanf 一旦找到一个空白字符就会停止读取。你可以改用fgets

 fgets(string, 200, stdin);

【讨论】:

  • OP 对printf() 的使用是正确的。像char character 这样的可变参数使用与int(或很少unsigned)的通常比例提升,因此不需要演员intprintf ("%c %d",... 接受两个 int 参数,因此 OP 使用了正确的说明符。 "d,i int 参数被转换..." "c 如果不存在 l 长度修饰符,int 参数..." C11 §7.21.6.1 8
  • 我同意使用 fgets() 而不是 scanf() 来读取一行文本。但是,第二个输入应该是输入'newline',而'newline'是空格,所以建议:scanf("c", &character);将不起作用,因为 scanf 将跳过新行。也许这就是我们想要的,这个细节上的问题还不清楚。
  • @user3629249;在scanf 中使用" %c" 始终是安全的。如果用户按两次 Enter/Return 会怎样?
【解决方案2】:

OP 的第一个问题通常通过在格式前面添加一个空格来解决。这将消耗空格,包括前一行的'\n'

// scanf("%c", &character);
scanf(" %c", &character);

此外,该程序不适用于包含多个单词的字符串。我该如何克服这些问题?

对于第二期,让我们更准确地理解“字符串”以及"%s" 的作用。

string 是一个连续的字符序列,以第一个空字符终止并包括第一个空字符。 7.1.1 1

即使报告“我输入了一个字符串(例如:计算机)”,OP 也没有输入一个字符串。 OP 正在输入一行文本。 8 个字符“计算机”,后跟 Enter。这里没有“空字符”。而是 9 char "computer\n"

"%s" in scanf("%s", string); 做了 3 件事:

1) 扫描,但不保存任何前导空格。

2) 扫描并保存到string 任意数量的非空白。

3) 到达空白或 EOF 时停止扫描。那个char 只是回到stdin'\0' 附加到 string 使 char 数组成为 C 字符串

要读取包含空格的行,请勿使用scanf("%s",...。考虑fgets()

fgets(string, sizeof string, stdin);
// remove potential trailing \r\n as needed
string[strcspn(string, "\n")] = 0;

混合scanf()fgets() 是一个问题,因为像scanf("%s", string); fgets(...) 这样的调用将'\n' 留在stdin 中,以将fgets() 读取为仅包含"\n" 的行。建议改为使用fgets()(或*nix 系统上的getline())读取所有用户输入。 然后解析读取的行。

fgets(string, sizeof string, stdin);
scanf(string, "%c", &character);

如果代码必须用户scanf()读取用户输入,包括空格:

scanf("%*[\n]"); // read any number of \n and not save.
// Read up to 199 `char`, none of which are \n
if (scanf("%199[^\n]", string) != 1) Handle_EOF();

最后,代码应该使用错误检查和输入宽度限制。测试所有输入函数的返回值。

【讨论】:

    【解决方案3】:

    您看到的是您调用的函数的正确行为:

    • scanf 将从输入中读取 一个 字,并在它读取的字之后立即留下输入指针。如果您键入computer&lt;RETURN&gt;,则要读取的下一个字符是换行符。

    • 要读取整行,包括最后的换行符,请使用fgets。仔细阅读文档:fgets 返回一个字符串,其中包含它读取的最后一个换行符。 (gets,由于多种原因,无论如何都不应该使用它,它会读取并丢弃最后的换行符。)

    我应该补充一点,虽然scanf 有它的用途,但我认为您已经发现,以交互方式使用它会导致非常 令人困惑的行为。即使您想逐字阅读,如果预期用途是交互式的,请使用另一种方法。

    【讨论】:

      【解决方案4】:

      您可以使用%*c:

      #include <string.h>
      #include <stdio.h>
      
      int main()
      {
          char string[200];
          char character;
          printf ("write something: ");
          scanf ("%s%*c", string);
          printf ("%s", string);
          printf ("\nwrite a character: ");
          scanf ("%c%*c", &character);
          printf ("\nCharacter %c  Correspondent number: %d\n", character, character);
      
          return 0;
      }
      

      %*c 将接受并忽略换行符或任何空格

      【讨论】:

      • 注意:%*c 将接受并忽略后面的任何 char,这通常是换行符,但也可以是任何空格,例如 ' '
      • @chux,是的,没错。我会将您的注释准确地添加到我的答案中。
      • 这有缓冲区溢出;你需要限制输入
      【解决方案5】:

      您还可以将getchar() 放在scanf 行之后。它会完成这项工作:)

      【讨论】:

        【解决方案6】:

        需要刷新流。在执行连续输入时,标准输入流 stdin 会缓冲键盘上的每次按键操作。因此,当您键入“computer”并按下回车键时,输入流也吸收了换行符,即使只有字符串“computer”被分配给string。因此,当您稍后扫描一个字符时,已经加载的换行符是扫描并分配给 character 的那个。

        还需要刷新标准输出流。考虑一下:

        ...
        printf("foo");
        while(1)
        {}
        ...
        

        如果尝试执行这样的操作,那么控制台上不会显示任何内容。系统缓冲了stdout 流,标准输出流,没有意识到接下来会遇到无限循环,一旦发生这种情况,它就永远没有机会将流卸载到控制台。

        显然,以类似的方式,每当scanf 阻塞程序并等待标准输入流stdin 时,它都会影响其他正在缓冲的流。无论如何,如果事情开始混乱,最好正确刷新流。

        对您的代码进行以下修改似乎会产生所需的输出

        #include <string.h>
        #include <stdio.h>
        
        int main()
        {
        char string[200];
        char character;
        
        printf ("write something: ");
        
        fflush(stdout);
        
        scanf ("%s", string);
        
        fflush(stdin);
        
        printf ("%s", string);
        printf ("\nwrite a character: ");
        
        fflush(stdout);
        
        scanf ("%c", &character);
        printf ("\nCharacter %c  Correspondent number: %d\n", character, character);
        
        return 0;
        } 
        

        输出
        写点东西:电脑
        电脑
        写一个字符:a

        通讯员号码:97

        【讨论】:

        • 您的代码有缓冲区溢出,fflush(stdin) 导致未定义的行为
        • @M.M 有任何证据支持 fflush 产生未定义行为这一事实!而且我不太了解缓冲区溢出部分,请您重新措辞/解释一下。谢谢。
        • 如果他们输入 200 个字母,那么您将溢出 string。对于fflush,请参阅当前 C 标准的第 7.21.5.2/2 节,“如果流指向未输入最新操作的输出流或更新流,则 fflush 函数会导致该流的任何未写入数据被传递到主机环境以写入文件;否则,行为未定义”。 stdin 不是输出或更新流。
        • @M.M 就缓冲区溢出而言,这不是我想要说明的重点。显然,如果您尝试存储超出其容量的数据,则数组会溢出。我既没有解决这个问题,也没有试图在我的代码中适应这个问题。
        • @MM 是的,规范是这么说的,但 fflush 的行为仍然完全符合人们的预期,即使使用输入流也应该如此,因此我认为可以安全地假设stdin 可以以这种方式刷新而不会产生任何影响,除非您有一个反例,其中在连续输入之间刷新标准输入会导致未定义的行为。另外我不太了解更新流这个术语,你能告诉我它到底是什么意思吗,输入流和更新流之间到底有什么区别。谢谢。
        猜你喜欢
        • 2018-02-15
        • 2017-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多