fgets在scanf之后无法工作。

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

void delspace(char *str);

int main() {
    int i, loops;
    char s1[101], s2[101];

    scanf("%d", &loops);

    while (loops--) {
        fgets(s1, 101, stdin);
        fgets(s2, 101, stdin);
        s1[strlen(s1)] = '\0';
        s2[strlen(s2)] = '\0';

        if (s1[0] == '\n' && s2[0] == '\n') {
            printf("YES\n");
            continue;
        }

        delspace(s1);
        delspace(s2);

        for (i = 0; s1[i] != '\0'; i++)
            s1[i] = tolower(s1[i]);

        for (i = 0; s2[i] != '\0'; i++)
            s2[i] = tolower(s2[i]);

        if (strcmp(s1, s2) == 0) {
            printf("YES\n");
        }
        else {
            printf("NO\n");
        }
    }

    return 0;
}

void delspace(char* str) {
    int i = 0;
    int j = 0;
    char sTmp[strlen(str)];

    while (str[i++] != '\0') {
        if (str[i] != ' ') {
            sTmp[j++] = str[i];
        }
    }
    sTmp[j] = '\0';
    strcpy(str, sTmp);
}

在我输入"loops"后,"s1"自动被赋值为空行。这是如何发生的?我确定我的键盘没有问题。


15
“我相信我的键盘没有问题。” 哈哈! - orlp
2
似乎每天都会在这里问类似的问题的变体。 - Jim Balter
函数 delspace 明显有误!这会将 abc\0 转换为 bc\0 - TrueY
1
经验法则:对于交互式输入,始终读取 - Karoly Horvath
1
相关:https://dev59.com/dG435IYBdhLWcg3wvyw5 - melpomene
7个回答

27

scanf()会精确读取你要求的内容,但是会将该行结尾后的\n保留在缓冲区中,这样fgets()会读到它。你可以对换行符进行处理,或者(我比较喜欢的解决方案)先使用fgets()读取字符串,再使用sscanf()来读取数据。


如果我想使用 scanf(),那么我该如何去掉输入缓冲区中的换行符呢?谢谢。 - Vayn
5
在格式字符串末尾使用类似 %*[^\n]%*c 的内容,可以跳过直到换行符为止的所有字符,包括换行符本身。 - geekosaur
4
在格式字符串的末尾添加'%*[^\n]%c'并不能真正起作用,如果在之前读取的内容和换行符之间没有其他字符,则'%[^\n]'将无法匹配,因此'%*c'会被跳过,而换行符仍将留在输入中。你需要在另一个scanf调用中使用'%*c'才能使其起作用。 - Chris Dodd

10

scanf 会在输入缓冲区中保留空格,包括换行符。如果要使用 fgets 读取下一行,您需要手动删除当前行的其余部分:

int c;
do{
    c = getchar();
}while(c != EOF && c != '\n');

没错,那也能行,不过我个人只用fgets然后sscanf(就像geekosaur建议的一样),因为它在处理各种数据时都能“统一”地工作……而不仅仅是单个字符。而且我喜欢“通用、多用途的解决方案”。干杯,Keith。 - corlettk
1
这个不仅适用于一个字符 - 这就是循环的作用 ;) - hugomg
while(c != EOF && c != '\n');不好。 c超出范围。 - chux - Reinstate Monica
我本来想借口说可能是因为我混淆了C和Lua的作用域规则,但显然这个答案是在我学习Lua之前的,哈哈哈。 - hugomg

7

这是一个更简单的解决方案

scanf("%d",&loops);
while ((getchar()) != '\n'); //This will consume the '\n' char
//now you're free to use fgets
fgets(string,sizeof(string),stdin);

当 ((getchar()) != '\n'); 是一个无限循环,当文件结束时。 - undefined

1

Geekosaur 很好地回答了你的问题,我只是指出你的代码中的另一个“问题”。

如果在执行该行代码 s1[strlen(s1)] = '\0'; 之前,s1 已经被正确地设置为 null 结尾,则此行代码是 no-op

但是,如果在执行此行代码之前,s1 没有被正确地设置为 null 结尾(而且你很不幸),它将导致:

  • 在 POSIX (*nix) 系统上引发 SIGSEGV
  • 在 Windows 上引发 GPF

这是因为 strlen 基本上会找到现有的 null 终止符的索引并返回它!以下是 strlen 的有效但未经优化的实现:

int strlen(char *string) {
    int i = 0;
    while(string[i] != '\0') {
        ++i;
    }
    return i;
}

所以...如果你真的担心字符串没有以 null 结尾,那么你可以这样做:
  • 对于本地自动字符串(编译器“知道”字符串大小),使用 string[sizeof(string)]='\0';
  • 对于所有其他字符串,使用 string[SIZE_OF_STRING],其中 SIZE_OF_STRING 最常见的是一个 #define 常量,或者是一个专门用于存储动态分配字符串当前大小(而不是长度)的变量。
如果你真的、真的、真的担心字符串没有以 null 结尾(比如你正在处理“脏”的库方法,比如 Tuxedo 的 ATMI),那么在将它们传递给可疑的库方法之前,你还需要“清除”你的“返回字符串”:
  • before:memset(string, NULL, SIZE_OF_STRING);
  • invoke: DirtyFunction(/*out*/string);
  • after: string[SIZE_OF_STRING]='\0'
SIG11是很难找到的,因为它们会导致Unix强制终止程序,除非你使用信号处理器来“挂钩”它们并说明情况,否则你无法记录任何日志(事后)以帮助确定它来自哪里...特别是考虑到在许多情况下,引发SIG11的代码行与字符串失去其空终止符的实际原因无关。

你明白我的意思吗?

PS:警告: strncpy并不总是以空字符结尾...你可能想使用strlcpy。我通过痛苦的方式学习到这一点...当一个价值6000万美元的计费运行崩溃时。


编辑:
FYI:这是一个“安全”的(未优化的)strlen版本,我将其称为strnlen(我想这应该在string.h中。叹息)。
// retuns the length of the string (capped at size-1)
int strnlen(char *string, int size) {
    int i = 0;
    while( i<size && string[i]!='\0' ) {
        ++i;
    }
    return i;
}

我是C语言的新手,而且教材并没有涉及到字符串和安全方面的内容。也许我的教材不够好 :( 感谢您的专业建议 :) - Vayn
2
不要使用string[sizeof(string)]='\0'!这会在分配的内存之后写入\0sizeof(s1)是101,s1的索引从0到100!strnlen也存在同样的问题。如果没有\0,则它将返回size,而不是评论中提到的size - 1 - TrueY

-1

忽略在变量标签循环中扫描整数后跟随的换行符(由于按下ENTER键)的另一种方法是:

scanf ("%d%*c", &loops);

根据man页面:

* 抑制赋值。随后的转换按照通常方式进行,但不使用指针;转换的结果被简单地丢弃。

这很不可能发生,但在scanf期间检查错误是一个好习惯:

errno = 0
scanf ("%d%*c", &loops);
if (errno != 0) perror ("scanf");
// act accordingly to avoid un-necessary bug in the code

例如在您的代码中,循环是一个本地未初始化的变量,包含垃圾值。如果scanf无法填充所需的值,则以下while循环可能会任意运行。

2
这不是你从scanf检查错误的方法。 - Antti Haapala -- Слава Україні

-1

scanf() 会在缓冲区中留下该行末尾的 \n,而 fgets() 将读取它。为了解决这个问题,我们必须做一些事情来消耗换行符。让我们看看这个问题。

问题

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}

Output:
Enter in scanf: Hello
Enter in fgets:

正如您所看到的,它没有显示fgets部分。让我们来看看解决方案。

在scanf和fgets()之间放置getchar()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}

Output:
Enter in scanf: Hello
Enter in fgets: Hello

如果可能,在fgets()之后使用scanf()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    fgets(input, 10, stdin);
    getchar();
    printf("Enter in scanf: ");
    scanf("%s", input);
}

Output:
Enter in fgets: Hello
Enter in scanf: Hello

fget()方法使用两次(有时此方法无效)

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    scanf("%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
    fgets(input, 10, stdin)
}

输出
Enter in scanf: Hello
Enter in fgets: Hello

使用sscanf()代替scanf()

#include<stdio.h>
void main()
{
    char input[10];
    printf("Enter in fgets: ");
    sscanf(hello, "%s", input);
    getchar();
    printf("Enter in scanf: ");
    fgets(input, 10, stdin);
}

输出
Enter in sscanf: Hello
Enter in fgets: Hello

1
您介意进一步阐述吗?例如,提供一个例子。 - Elis Byberi

-2

只需要将代码中的

scanf("%d",&loops);

替换为

scanf("%d\n",&loops);

即可。


1
虽然这个答案涵盖了原问题的一个重要现象,但它未能对问题本身提供一些深入的见解。因此,它最多只对执行原问题有所帮助,长远来看并不真正有用。请添加更多的解释,使您的答案对于寻找类似问题的后来读者有所帮助。 - rpy
2
不要在 scanf() 格式字符串的末尾添加空格 —— 这会完全破坏 UI/UX,因为输入不会停止,直到您键入除空格以外的其他内容(而换行符是空格)。这意味着用户必须预测下一个输入需要什么。请参见 [What is the effect of trailing white space in a scanf() format string?](https://dev59.com/L2Ik5IYBdhLWcg3wMLmIths 5168)。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接