scanf和fgets的问题

3

这是一个作业任务,需要对给定的字符串进行排序。我要求用户使用scanf输入他们想要排序的字符串数量,根据该数字分配一个数组,然后使用fgets获取字符串本身。

如果字符串的数量是硬编码的,则一切都正常,但是添加了scanf以让用户决定后,问题就出现了。以下是代码:

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

#define LENGTH  20 // Maximum string length.

int main(void)
{
    int index, numStrings = 0;
    char **stringArray;
    printf("Input the number of strings that you'd like to sort: ");
    assert(scanf("%d", &numStrings) == 1);
    stringArray = (char **)malloc(numStrings * sizeof(char *));

    for (index = 0; index < numStrings; index++)
    {
        stringArray[index] = (char *)malloc(LENGTH * sizeof(char));
        assert(stringArray[index] != NULL);
        printf("Input string: ");
        assert(fgets(stringArray[index], LENGTH, stdin) != NULL);
    }

    // Sort strings, free allocated memory.

    return 0;
}

以下是控制台的样子:

输入您想要排序的字符串数量: 3
输入字符串: foo
输入字符串: bar

它跳过了循环的第一次迭代,导致数组开头为空字符串。我的问题是,为什么会这样,我该如何解决?


当将格式字符串"%d\n"传递给scanf时,控制台显示如下:

输入您想要排序的字符串数量: 3
foo
输入字符串: bar
输入字符串: baz

因此,我可以输入所有字符串,但是第一个字符串的提示在错误的位置。


4
不要在那些可能会失败的函数上使用assert(),比如读取用户输入的输入函数。这比忽略错误要好,但不如合理地处理错误。 - Jonathan Leffler
2个回答

6

您需要在scanf中加入\n来清除输入缓存中的\n:

scanf("%d\n", &numStrings)

如果不这样做,scanf将会把残留的换行符[当你按下回车键时产生的]作为循环中的第一行读入。


1
添加到Foo Bah答案的另外一些评论:你代码中的一个问题是,不应该在assert内部执行任何具有副作用的操作;在发布模式下,assert内部的内容不会被执行。您还可以在printf之后添加fflush(stdout);以确保在要求用户输入之前打印提示信息。 - Jeremiah Willcock
注意,如果您的字符串不太长,fgets将包括终止的换行符。 - Foo Bah
如果字符串太长,fgets()函数将会把该行剩余的字符留给下一次调用标准I/O函数时读取。 - Jonathan Leffler
添加\n可以让我输入所有的字符串,但第一个Input string:提示仍然在错误的位置。在我输入字符串之后出现,在下一个提示的同一行上。请参见我问题中的编辑。 - gdejohn

3
我的真正建议(在我谦虚但非常正确的意见中:P)是不要使用scanf。使用fgets读取第一行(即数字),然后使用sscanfstrtoul解析该字符串。这样,您就可以处理输入数据格式不正确时产生的错误,并且无需绕过scanf缺乏健壮的空格处理能力。
此外,永远不要使用int存储大小,除非您希望有很多长度为-4的数组。标准指定了无符号类型size_t作为足够大以存储对象大小和数组索引的无符号类型。使用其他任何类型都不能保证有效。

当你说不要使用int来存储大小时,你指的是我的代码的哪一部分? - gdejohn
@Charlatan - numberOfStrings 具体来说,任何你传递给 malloc、从 strlen 接收或用于索引数组的东西都应该是 size_t - Chris Lutz
那么,您建议我如何从stdin获取一个size_t类型的内容以传递给malloc?解析一个整数并进行强制转换吗?顺便说一下,我采用了您的答案(使用了atoi),现在它可以正常工作了。 - gdejohn
@Charlatan - 你可能有点想多了。;) size_t 是一个无符号整数类型的 typedef。只需在需要的地方使用它,就像 size_t len = 0; 中一样(有用的提示:打印 size_t 的格式是 "%zu"。你可以使用另一个接受无符号类型(如 "x")而不是 "u" 的格式。) - Chris Lutz
通常情况下,您应该像您建议的那样使用size_t来存储大小,但请注意,您推荐他使用的函数fgets需要一个int类型的大小,而不是size_t(而fputs返回一个int类型,而不是size_t):http://www.cplusplus.com/reference/clibrary/cstdio/fgets/ - Zach Burlingame
@Zach - fputs是可以理解的,因为它在失败时返回EOFfgets接受一个int可能是出于对称性(在我看来这是个坏主意)或遗留问题(这也可能是个坏主意)。有趣的是,标准(以及我找到的所有文档)都没有提到fgets可能会被赋予负参数,所以我认为这可能是一个错误。我想在大多数系统上,它最终会被转换为无符号整数类型。 - Chris Lutz

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