如何在C语言中读取scanf直到EOF?

32

我有这段代码,但是一旦它到达了预定的EOF(文件结束符),它就会重复循环并再次使用scanf。

int main(void)
{
        char words[16];

        while(scanf("%15s", words) == 1)
           printf("%s\n", words);

        return 0;
}

3
您在使用什么输入方式?您是将文件重定向到stdin还是在控制台键入?如果是后者,您使用的操作系统是什么,如何键入EOF? - David Thornley
7个回答

27

尝试:

while(scanf("%15s", words) != EOF)
你需要将scanf的输出结果与EOF进行比较。
由于你在格式字符串中指定了宽度为15,因此你最多只会读取15个字符。所以这个字符数组应该有16个大小(15 +1 用于null字符)。请将其声明为:
char words[16];

3
@user411313 不可以,scanf有可能返回EOF。由于他在使用!=进行比较,所以第一次不会中断。然而,在一般情况下,你需要获得已分配的项目数量,这样你才能进行一些验证操作。 - nos
4
@user411313说:man scanf和C99 §7.19.6.2/16都表明scanf函数可以返回EOF。 - Roger Pate
这将产生相同的输出, [我是一个句子] [我] [是] [一个] [句子] 然后 scanf 重新开始... 我希望它在这里中断。 - JJRhythm
2
这个循环以字符串输入终止,因为所有字符(甚至是空字节)都被接受。如果转换使用数字类型(例如 int d; while (scanf("%d", &d) != EOF) { … }),那么如果输入中有非数字、非空格字符(例如字母或标点符号),则会得到一个无限循环。总的来说,问题中的风格——while (scanf("…", &variable) == 1) 是最好的。我想知道为什么这对 OP 不起作用。(也许是 为什么分叉我的进程会导致我的文件无限读取? - Jonathan Leffler

5

scanf通常会带来很多麻烦,以下是两种更好的替代方案。第一种方案基本上是你代码的直接翻译。虽然它更长,但你可以清晰地看到它的功能,不像scanf那样难以理解。

#include <stdio.h>
#include <ctype.h>
int main(void)
{
    char buf[1024], *p, *q;
    while (fgets(buf, 1024, stdin))
    {
        p = buf;
        while (*p)
        {
            while (*p && isspace(*p)) p++;
            q = p;
            while (*q && !isspace(*q)) q++;
            *q = '\0';
            if (p != q)
                puts(p);
            p = q;
        }
    }
    return 0;
}

这里有另一个版本。通过检查很难看出它的作用,但是如果一行超过1024个字符,它不会崩溃,因此这是我在生产中使用的代码。(好吧,实际上我会使用tr -s '[:space:]' '\n',但这是如何实现类似功能的方法。)

#include <stdio.h>
#include <ctype.h>
int main(void)
{
    int ch, lastch = '\0';
    while ((ch = getchar()) != EOF)
    {
        if (!isspace(ch))
            putchar(ch);
        if (!isspace(lastch))
            putchar('\n');
        lastch = ch;
    }
    if (lastch != '\0' && !isspace(lastch))
        putchar('\n');
    return 0;
}

1
是的,这比 for (char buf[1024]; scanf("%1023s", buf) == 1;) printf("%s\n", buf); 简单多了,只是在某些前导、尾随和相邻空格的情况下改变了行为。 - Roger Pate
在现实生活中,我会编写一个循环来处理每个字符,并确保其具有我想要的空格行为(这可能与 OP 想要的不同,他们没有说明),但这对于示例来说太过繁琐。无论如何,如果你正在使用 scanf,你肯定不关心边缘情况的行为或错误恢复。 - zwol
你知道吗,这并不是太繁琐。我重写了这个例子。 - zwol
你的新示例更好,但现在它不是与我上面那一行代码完全等价了吗?如果是这样,为什么要用15行代码替换一行(或两行,更可能)呢? - Roger Pate
1
主要是因为我“知道”我的15行代码确切地实现了我想要的功能,而我永远不信任scanf能够做到我想要的。scanf的行为有太多奇怪的边界情况,特别是涉及错误处理时。 - zwol
我已经做过了 - 如果对于scanf来说太复杂,你最好使用实际的解析器,如flex + bison或antlr。 - Chris Dodd

5

你的代码会循环读取一个单词,然后退出。所以如果你输入多个单词,它只会读取第一个单词并退出;而如果你输入空内容,它将永远循环下去。无论哪种情况,它都只会打印来自未初始化内存的随机垃圾数据。这显然不是你想要的结果,那么你想要什么呢?如果你只想读取和打印第一个单词(如果存在),请使用 if 语句:

if (scanf("%15s", word) == 1)
    printf("%s\n", word);

如果你想一直循环读取单词,可以使用while语句:
while (scanf("%15s", word) == 1)
    printf("%s\n", word);

此外,正如其他人所指出的那样,您需要为scanf分配足够大的数组大小:

char word[16];

有人建议测试EOF而不是检查scanf匹配了多少项。这对于此情况是可以的,因为scanf只有在遇到EOF时才无法匹配,但在其他情况下(比如尝试读取整数),这种方法就不太好了,因为scanf可能会匹配不到任何内容而没有达到EOF(如果输入不是数字),然后返回0。
编辑
看起来您更改了问题以匹配我的代码,当我运行它时,它可以正常工作--循环读取单词直到达到EOF然后退出。所以你的代码出现了其他问题,也许与David建议的输入方式有关。

嗨,谢谢您的回复,但您尝试过这个吗?我意识到我在原始帖子中放了“!=”,但我想要的是"==",并且这正好给我我想要的结果:我在调用scanf时输入一句话,然后循环并打印出单词,接着再次调用scanf,但我不想要这样。它调用了scanf,没有让我有任何方法跳出循环。也许是我的编译器?但唯一的问题是,在打印出所有单词后它没有停止。 - JJRhythm
2
@user315903:这就是为什么最好的想法是发布那些无法工作的代码,而不是尝试输入类似的内容。如果您的代码由于您不理解的原因而无法工作,那么您就不理解问题出在哪里,也不能相信自己能够保留问题的本质。 - David Thornley
不会在空输入上无限循环。如果没有剩余数据可读,scanf()将返回EOF,它不是1,因此循环应该停止。 - Jonathan Leffler

2

如果您使用的是Windows系统,按下回车键并不会触发EOF,而是需要在控制台中按下Crtl+Z,这将打印出"^Z",表示EOF。以下是读取EOFCrtl+Z时函数的行为:

函数 输出
scanf(...) EOF
gets(<variable>) NULL
feof(stdin) 1
getchar() EOF

0
你需要检查返回值与 EOF 而不是 1 进行比较。
请注意,在你的例子中,你也使用了两个不同的变量名,wordsword,只声明了 words,并没有声明它的长度,它应该是 16,以适应读入的 15 个字符加上一个 NUL 字符。

-1

我想最好的方法是...

int main()
{
    char str[100];
    scanf("[^EOF]",str);
    printf("%s",str);
    return 0;     
}

-6

对于C语言用户,这也是适用的

while ( gets(str) != NULL )

2
要明确的是,gets存在的问题在于它可以写入超出分配缓冲区末尾的内容。 - Ben Voigt

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