【问题标题】:By default, reading which character in the input buffer makes scanf() stop reading a string?默认情况下,读取输入缓冲区中的哪个字符会使 scanf() 停止读取字符串?
【发布时间】:2017-01-21 18:43:12
【问题描述】:

当我不使用任何扫描集或否定扫描集时,遇到缓冲区中的哪个字符会使scanf()停止从缓冲区读取数据(假设字符数组足够大)?似乎答案是空白,因为如果我们在输出中输入一个多词句子,那么默认情况下scanf() 只存储第一个词,但是当我运行此代码时:

int main(void) {  
    char n[20];
    int status;
    do {
        status = scanf("%[^ ]", n);
        //status = scanf("%s", n);
        printf("%d %s\n", status, n);
    } while (status);
    return 0;
}

输入hello world,按回车键,输出为:

1 hello
0 hello

当我将do-while 更改为:

 do {
    //status = scanf("%[^ ]", n);
    status = scanf("%s", n);
    printf("%d %s\n", status, n);
} while (status);

我得到这个作为输出(对于相同的输入):

1 hello
1 world

程序无法自行终止

再次,对于第一个do-while,如果我输入hello 并按回车,我没有得到任何输出并且程序不会终止(显然是因为它没有读取空格)。但是第二个do-while就不是这样了。

如果有人能回答我如何更改我的代码以使其在按下返回键时终止,那将是很棒的,同时一次存储一个单词。

【问题讨论】:

  • 正如手册页告诉你的那样,它通常会忽略前导空格并在下一个空格处停止(作为第二个示例)。第一个示例专门指示scanf 将空格视为终止符:因此它保留在输入缓冲区中。
  • 通常建议不要使用scanf,它会很麻烦。使用fgets,然后使用sscanf 更易于管理。
  • 非常好的问题。 +1

标签: c string input scanf


【解决方案1】:

默认情况下,读取输入缓冲区中的哪个字符会使scanf()停止读取字符串?

没有默认值。
scanf() 何时/如何停止取决于格式。当遇到文件结尾或输入错误时,读取也会停止。

// format "%c"
// Reads 1 character, then stops, no special value stops it early.
char ch;
scan("%c", &ch);

// format "%9[^ ]"
// Reads up to 9 non-space characters, then stops.  Even reads a null character.
char buf[10];
scan("%9[^ ]", buf);

// format "%9s"
// Reads and discards leading white-space characters, 
//   then reads/saves up to 9 non-white-space characters, then stops.  
// Even reads a null character.
char buf[10];
scan("%9s", buf);

// format "xyz"
// Reads up to 3 characters that match the pattern "xyz", then stops.  
scan("xyz");

【讨论】:

  • 1.你能告诉更多关于最后一种格式“xyz”的信息吗?比如输入将存储在哪里?举一个例子来说明它的用途。 2.另外请建议如何使用scanf读取空白行,因为“%[\n]”不这样做。
  • 1) 不存储输入。如果输入为"xa",则消耗x,并将a 放回以供下次使用。 2) scanf() 很难正确用于读取输入的。为什么不用fgets()
  • 在我开始使用 fgets() 之前尝试理解 scanf()。谢谢。
  • @NityeshAgarwal 请注意,C 语言中有大约 8 页的 scanf()fscanf() 规范。 fgets() 使用 1/2 页。推荐fgets() 用于阅读线,根本不要使用scanf()。您的选择。
【解决方案2】:

使用 scanf 捕获 enter 很困难。使用fgets 很容易通过检查索引[0] 来查看是否输入了空行。使用 sscanf 解析每个单词。 %n 说明符将告诉您扫描中使用了多少个字符。可以将该值添加到指针temp 以解析每个单词。

#include <stdio.h>
#include <stdlib.h>

int main() {
    char n[256] = { '\0'};
    char word[256] = { '\0'};
    char *temp = NULL;
    int result = 0;
    int chs = 0;

    while ( 1) {
        printf("\nEnter text(enter only to exit)\n");
        fgets ( n, sizeof n, stdin);
        if ( n[0] == '\n') {
            break;
        }
        temp = n;
        while ( 1 == ( result = sscanf ( temp, "%255s%n", word, &chs))) {
            temp += chs;
            printf ( "%s\n", word);
        }
    }

    return 0;

}

另一种方法是使用getchar 读取每个字符。检查换行符以退出,检查空格以打印每个单词并检查可打印字符以累积到每个单词中。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define LIMIT 79

int main( void) {
    char n[LIMIT + 1] = { '\0'};
    int at = 0;
    int ch = 0;

    printf ( "type the text and press enter\n");
    while ( ( ch = getchar ( )) != EOF) {
        if ( ch == '\n') {
            if ( at) {
                fputs ( n, stdout);
                fputc ( '\n', stdout);
            }
            at = 0;
            break;
        }
        if ( at >= LIMIT || isspace ( ch)) {
            if ( at) {
                fputs ( n, stdout);
                fputc ( '\n', stdout);
            }
            at = 0;
        }
        if ( isgraph ( ch)) {
            n[at] = ch;
            at++;
            n[at] = '\0';
        }
    }
    if ( at) {
        fputs ( n, stdout);
        fputc ( '\n', stdout);
    }

    return 0;
}

【讨论】:

    【解决方案3】:

    对于取反的扫描集,scanf() 在从 stdin 获取与扫描集匹配的字节(%[^ ] 格式的空格)或到达文件末尾时停止读取。

    请注意,如果它立即读取匹配字节或文件结尾,则转换失败,scanf() 分别返回 0EOF

    如果转换失败,您应该更改代码以避免将n 数组传递给printf(),因为它将未初始化并且printf() 中的行为将未定义。

    您还应该通过在空终止符之前传递要存储到n 的最大字符数来防止潜在的缓冲区溢出。

    这是一个更安全的版本:

    #include <stdio.h>
    
    int main(void) {  
        char n[20];
        int status;
        while ((status = scanf("%19[^ ]", n)) == 1) {
            printf("%d %s\n", status, n);
        }
        printf("stop with status=%d\n", status);
        return 0;
    }
    

    上述循环的问题是第二次迭代将失败,因为stdin中的空间仍在等待中。

    • 您可以通过在格式字符串中添加初始“”来解决此问题,以在转换前跳过初始空格:scanf(" %19[^ ]", n),但这会跳过其他空格,例如换行符。
    • 您也应该在换行符上停止 scanf
    • 更严格的解决方案是在每次成功转换后跳过待处理的空格:

      #include <stdio.h>
      
      int main(void) {  
          char n[20];
          int status;
          while ((status = scanf("%19[^ \n]", n)) == 1) {
              printf("%d %s\n", status, n);
              scanf("%*[ \n]"); // skip spaces
          }
          printf("stop with status=%d\n", status);
          return 0;
      }
      

    如果你想在用户在空行上按下回车键时退出循环,你可以先测试一下,如果它不是换行符,则取消获取字节:

    #include <stdio.h>
    
    int main(void) {  
        char n[20];
        int c, status;
        for (;;) {
            if ((c = getchar()) == EOF || c == '\n')
                break;
            ungetc(c, stdin);
            if ((status = scanf("%19[^ \n]", n)) == 1) {
                printf("%d %s\n", status, n);
                scanf("%*c"); // skip the space or newline that stopped the scanf
            } else {
                printf("stop with status=%d\n", status);
                break;
            }
        }
        return 0;
    }
    

    【讨论】:

    • 此代码按预期工作,除了输入中的最后一个单词。最后一个词没有被打印出来。如果我在此之后输入更多输入,则会打印出一些意想不到的东西。否则,如果我一直按回车键(在我的机器上按 14 次),就会显示所需的单词。此外,这条线 printf("stop with status=%d\n", status); 似乎永远不会被执行。
    • 问题出在scanf() 调用中:scanf() 读取输入,直到它得到一个空格、文件末尾或已读取 19 个字符。如果您希望换行符停止 scanf,则必须将其包含在格式字符串中。为此,我更新了答案。
    • “更严格的解决方案”受益于在 while 循环之前使用 scanf("%*[ ]"); 以消耗前导空格。否则 " abc def ghi " 不会报告任何内容。
    • @chux:没错,用scanf() 解析输入非常棘手。
    猜你喜欢
    • 2019-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-26
    • 2021-12-10
    • 2019-08-31
    • 2011-07-21
    • 1970-01-01
    相关资源
    最近更新 更多