在同一个程序中使用scanf和fgets吗?

7

我需要做类似以下的事情:

int main(void) {
char a,b,cstring;

printf("please enter something");
scanf("%c %c",&a,&b);
prinf("thanks, now some more");
fgets(cstring,35,stdin);

}

问题是每当我输入scanf的第一个变量时,它就会跳到程序的末尾。我如何进行多次输入?

4个回答

9
第一个问题是 scanf() 读取了两个字符,但没有读取换行符。
这意味着你的 fgets() 读取了换行符并结束了。
你很幸运程序没有崩溃。你告诉 fgets() 它要获取一个长度为35的字符数组,但你传递了一个字符(甚至不是字符的地址)。这也告诉我们你没有包含 #include <stdio.h>(如果没有在作用域中定义scanf()的原型,则不能可靠地使用scanf()),并且你没有启用足够的编译器警告,或者没有足够注意编译器的提示。
你应该得到编译器警告。例如,GCC 4.6.0说:
/usr/bin/gcc -g -I/Users/jleffler/inc -std=c99 -Wall -Wextra -Wmissing-prototypes \
     -Wstrict-prototypes -Wold-style-definition xxx.c
xxx.c: In function ‘main’:
xxx.c:7: warning: implicit declaration of function ‘prinf’
xxx.c:8: warning: passing argument 1 of ‘fgets’ makes pointer from integer without a cast

而且,我甚至没有注意到prinf()的拼写错误,直到我尝试链接并且编译器通过说“不知道prinf()是什么!”来提醒我。

如果你的编译器没有至少给出第二个警告,请获取一个更好的编译器。


注释:

如何阻止它在读取换行符后继续读取?

问题不是停止scanf()读取换行符;实际上问题是如何使它读取它,以便fgets()可以清晰地运行下一行输入。这实际上有点棘手 - 这就是为什么我很少使用scanf()(但我经常使用sscanf())的原因之一。这对于简单的输入可以完成任务:

#include <stdio.h>
int main(void)
{
    char a,b,cstring[35];

    printf("please enter something");
    scanf("%c %c%*c", &a, &b);
    printf("thanks, now some more");
    fgets(cstring, 35, stdin);
    printf("OK: I got %c and %c and <<%s>>\n", a, b, cstring);
    return 0;
}
%*c 是读入一个额外的字符(即一个换行符),但不会对该字符进行赋值(这是由于 * 的原因)。但如果第二个非空格字符后面还有多个字符,则无效。我可能会编写一个循环,直到读取到换行符为止。
#include <stdio.h>
int main(void)
{
    char a,b,cstring[35];
    int c;

    printf("please enter something");
    scanf("%c %c", &a, &b);
    while ((c = getchar()) != EOF && c != '\n')
        ;
    printf("thanks, now some more");
    fgets(cstring, 35, stdin);
    printf("OK: I got %c and %c and <<%s>>\n", a, b, cstring);
    return 0;
}

请注意,getchar()返回的是一个int而不是char。这可靠地丢弃了输入的第一行所有内容。它还显示了如何回显所读取的内容-调试的重要部分。

抱歉,我正在使用stdio进行编译。如何阻止它在读取换行符后继续执行? - wesbos
哦,我明白了。非常感谢您抽出时间来解释那个。 - wesbos
@Wes:请注意,我的代码没有检查scanf()是否成功读取了两个字符;它应该这样做。它还应该检查fgets()是否也有输入。然而,那会稍微使答案变得混乱一些,让你更难以看到问题的本质。 - Jonathan Leffler

2
那是未定义的行为。你的cstring变量,尽管它的名字是这样,但实际上它只是一个字符,而你正试图读取多达35个字符到它当前值所指向的位置。换句话说,cstring中的当前字符将被转换为指针,并尝试将你的输入写入其中。这将是一个内存位置(最有可能)在0到255之间。
这就是导致段错误的原因。相反,请从类似以下的内容开始:
#include <stdio.h>
int main(void) {
    char a,b,cstring[99];

    printf("please enter something");
    scanf("%c %c",&a,&b);
    printf("thanks, now some more");
    fgets(cstring,35,stdin);

    return 0;
}

或者,可能更好的选择:

#include <stdio.h>
int main(void) {
    char a,b,cstring[99];

    printf("please enter something");
    fgets(cstring,35,stdin);
    sscanf(cstring,"%c %c",&a,&b);
    printf("thanks, now some more");
    fgets(cstring,35,stdin);

    return 0;
}

或者,如果您想要一个更强大的用户输入解决方案,请查看这里。这是一种经过验证和测试的方法,具有非常好的错误检查功能。


啊,那应该是字符串类型吧? - wesbos
还是应该定义为char cstring[35]? - wesbos
@Wes:是的,请看更新,你基本上需要创建一个足够大的数组来容纳你想要输入的数据。 - paxdiablo
@Wes,C语言中没有字符串类型,一个C字符串只是一个以空字符结尾的字符数组。 - tobyodavies
1
这是因为您的scanf获取了两个字符,但换行符仍然在缓冲区中。这意味着fgets会获取它。您可以在scanf之后使用getchar将其删除,但您仍然会被边缘情况所困扰。认真地,跟随链接并使用一个体面的输入函数。 - paxdiablo
显示剩余2条评论

0
问题出在当流程到达fgets()时,这个API首先查找stdin输入,并发现那里有一个杂散的'\n'。至于fgets(),它说这个API在遇到EOF或'\n'时返回,因为fgets()从stdin得到了'\n',所以它满足了API的返回条件,而不需要等待用户输入任何内容。
解决问题的方法是在scanf之后使用getchar();并忽略scanf留下的额外字符。 例如:
scanf("%lf",&e);
getchar();
fgets(t,200,stdin);

或者

另一个解决方案是结合fgets使用sscanf. 例如:

#include <stdio.h>
int main() {
int j;char t[200];
fgets(t,200,stdin);
sscanf(t,"%d",&j);
}

-1
你可以在使用scanf后使用fflush(stdin)来解决这个问题。

fflush 是一个输出操作(它将缓冲数据写入底层流)。在像 stdin 这样的输入句柄上使用它会产生未定义的行为。 - melpomene
请参考使用 fflush(stdin)以深入讨论使用fflush(stdin)的优点和缺点。 - Jonathan Leffler

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