c-为什么下面的代码不允许我使用fgets获得用户输入,但却可以使用scanf



这是一个更大程序的简短摘录,但程序的其余部分无关紧要,因为我认为我能够隔离这个问题。我怀疑这与我使用fgets的方式有关。我读到过使用fgets比使用scanf更可取,但我似乎无法使它在这里正常工作。当我使用以下代码时,程序不会给我输入数字的机会(只是简单地跳到while循环,检查输入的数字是否在正确的范围内):

#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
int main(void)
{
// ask user for how many items to store
printf("how many words would you like to enter? (1-%i): ", SIZE);
// save number of words user would like to store
char *input = malloc(sizeof(char));
fgets(input, 1, stdin);
// scanf("%c", input);
int words = atoi(input);
printf("the number of words is: %in", words);
while (words < 1 || words > SIZE)
{
printf("please enter a number between 1 and %i: ", SIZE);
scanf("%i", &words);
}
}

这是我得到的输出:

~/workspace/extra_stuff/hash_tables/ $ ./test2
how many words would you like to enter? (1-10): the number of words is: 0
please enter a number between 1 and 10: 

正如你所看到的,它从不让我输入数字,而是简单地进入下一步,似乎假设我什么都没输入。

如果我按如下方式更改代码,一切都会按计划进行:

#include <stdlib.h>
#define SIZE 10
int main(void)
{
// ask user for how many items to store
printf("how many words would you like to enter? (1-%i): ", SIZE);
// save number of words user would like to store
char *input = malloc(sizeof(char));
// fgets(input, 1, stdin);
scanf("%c", input);
int words = atoi(input);
printf("the number of words is: %in", words);
while (words < 1 || words > SIZE)
{
printf("please enter a number between 1 and %i: ", SIZE);
scanf("%i", &words);
}
}

附言:我确实意识到,如果使用scanf,我可以立即将输入存储到int变量中,使用atoi将char转换为int;然而,fgets似乎需要一个char*,所以这就是我选择这条路线的原因。此外,我意识到我应该稍后free(input)

有人能解释这种行为吗?谢谢

编辑:

感谢到目前为止所有回复的人!这里有一些有用的建议,但看起来我在我的项目中遇到了同样的问题。以下是代码摘录:

// ask for strings
for (int j = 0; j < words; j++)
{
char buffer[4096];
// fgets(buffer, 40, stdin);
// name=calloc(NAME_SIZE, sizeof(char));
// fgets(name, NAME_SIZE, stdin);
// printf("size of (array[j]->next)->text is: %lun", sizeof((array[j]->next)->text));
printf("please enter string #%i: ", j);
fgets(buffer, 4096, stdin);
printf("you've entered: %s", buffer);
int length = strlen(buffer);
printf("word length: %in", length); 
}

当我运行程序时,它再次没有给我机会输入我的输入,而它应该是:

please enter string #0: you've entered: 
word length: 1

编辑#2:

在完成David的回答并参考其他人的评论和其他SO线程后,我提出了以下版本的代码,该代码首先询问用户想要输入的单词数(并验证输入),然后要求用户输入这些单词(再次验证输入)。它似乎正在编译,没有错误和警告,并且运行正常,尽管我不能100%确定我已经测试了用户输入中可能出现的所有问题,还有一些代码我仍然不完全理解(我将在下面列出)——如果有人有时间/欲望/耐心仔细查看它,告诉我是否还能改进,请告诉我。我的目标是在另一个程序中使用这段代码,该程序将要求用户输入并将条目存储在哈希表中。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUF_SIZE_WORDS 4096
#define BUF_SIZE_NUMBERS 256
#define MAX_WORDS 10
int word_input(int num_words);
void empty_stdin();
int main(void)
{
int num_words = 0,       /* number of words to enter */
word_count_check = 0;          /* word count */
char buffer[BUF_SIZE_NUMBERS] = "";    /* buffer of sufficient size for input */
for (;;) /* loop continually until valid input of NUMBER OF WORDS USER WANTS TO ENTER or user cancels */
{
printf ("how many words would you like to enter? [1-%d]: ", MAX_WORDS);
// check for cancellation of input
if (!fgets (buffer, BUF_SIZE_NUMBERS, stdin))
{
fputs ("user canceled inputn", stderr);
return 1;
}
// check if user simply hit enter w/o typing anything
if(buffer[0] == 'n')
{
printf("please enter a valuen");
continue;
}

size_t inlength = strlen(buffer);
// validate length < BUF_SIZE_NUMBERS - 1
if (inlength >= BUF_SIZE_NUMBERS - 1)
{
fputs ("input exceeds allocated buffer sizen", stderr);
return 2;
}
if (inlength && buffer[inlength - 1] == 'n')
{
// printf("hurray!n");
buffer[--inlength] = 0;
}
else if (inlength == BUF_SIZE_NUMBERS - 1) /* the line was too long */
{
printf("you've entered too many characters... please stick to a maximum of %in", BUF_SIZE_NUMBERS);
empty_stdin();
continue;
}
// make sure user actually entered a proper int
if (sscanf (buffer, "%d", &num_words) != 1) /* sscanf is used for conversion */
{
fputs ("invalid conversion to int; please provide valid inputn", stderr);
continue;
}
// check if the number entered is out of range
if (num_words < 1 || num_words > MAX_WORDS)
fprintf (stderr, "%2d out of valid range.n", num_words);
else
break; /*if the input has been validated, we can now break out of the for loop */
}
// call the word_input function and store its return value in word_count_check
word_count_check = word_input(num_words);
// check if the number of words processed equals to the number requested by the user
if(word_count_check == num_words)
{
printf("success!n");
}
else
{
printf("something went wrong, since word_count_check != num_words...n");
}
}
int word_input(int num_words)
{
int word_count = 0;
for(;;) /* loop until word_count == num_words is achieved */
{
// declare an array for storing input string
char buffer[BUF_SIZE_WORDS];
char valid_input[BUF_SIZE_WORDS];
// prompt user for input
printf("please enter a string: ");
// get input and check for CTRL+D
if (!fgets(buffer, BUF_SIZE_WORDS, stdin))
{
fputs ("user canceled inputn", stderr);
exit(1);
}
// check if user simply hit enter w/o typing anything
if(buffer[0] == 'n')
{
printf("please enter a word that's more than 0 charactersn");
// empty_stdin();
continue;
}
size_t inlength = strlen(buffer);
// check if user input exceed buffer size
if (inlength >= BUF_SIZE_WORDS - 1)
{
empty_stdin();
fputs ("input exceeds allocated buffer size, please try againn", stderr);
continue;
}
// check if the user entered too many characters
if (inlength == BUF_SIZE_WORDS - 1) /* the line was too long */
{
printf("you've entered too many characters... please stick to a maximum of %in", BUF_SIZE_WORDS);
empty_stdin();
continue;
}
if (inlength && buffer[inlength - 1] == 'n')
{
buffer[--inlength] = 0;
// get rid of trailing spaces using sscanf
sscanf(buffer, "%s", valid_input);
// figure out the length of the word the user entered
int word_length = ((int) strlen(valid_input));
printf("string length: %in", word_length);
// print out the word entered by the user one character at a time
printf("you've entered: ");
for (int i = 0; i < word_length; i++)
{
printf("%c", valid_input[i]);
}
printf("n");
// increment word count
word_count++;
printf("word_count = %in", word_count);
if (word_count == num_words)
{
return word_count;
}
}
}
}
/* helper function to remove any chars left in input buffer */
void empty_stdin()
{
int c = getchar();
while (c != 'n' && c != EOF)
c = getchar();
}

我还不完全理解的事情:

1)

if (!fgets (buf, MAXC, stdin)) { /* validate ALL user input */ 
fputs ("(user canceled input)n", stderr); 
return 1; 
}

---这是简单地检查用户是否手动输入EOF(使用ctrl+d),还是也检查其他内容?

2) 调用下面的empty_stdin()函数似乎会导致某种奇怪的挂起,看起来程序正在等待我的进一步输入,而不是继续下一步,尤其是当我经常使用它时(我想为什么不在每次用户输入奇怪的内容时清除输入流?)和/或当我将缓冲区减少到很小的值,然后故意输入太多字符时。。

void empty_stdin()
{
int c = getchar();
while (c != 'n' && c != EOF)
c = getchar();
}

3) 最终,我想使用其中的一些代码从文本文件(而不是用户输入)加载字典,并将其存储在哈希表中,在另一个版本中,存储在trie中。除了使用isalpha()来确保我们只存储包含字母的单词外,在处理输入时,除了上面的检查/验证之外,还有其他检查/验证需要进行吗?是否应跳过上述任何检查?

在C中处理字符串没有任何魔力,但你确实需要戴上你的会计帽子…为什么?在处理输入时,您不仅必须考虑放入缓冲区(或存储输入的位置)的字符数,而且还必须考虑保留在输入流中的字符

当使用scanf函数族中的任何一个进行输入时,这一点尤其正确。为什么?因为在匹配输入失败时,从输入缓冲区(此处为stdin)中处理(读取和删除)字符将停止,因此不会读取更多字符,并且导致匹配故障的任何字符在输入流中仍保持未读状态,等待下次尝试读取时再次咬到您。

对于新的C程序员来说,更令人困惑的是,一些转换说明符使用前导空格(例如space, tab, newline,...),而另一些则不使用。数字转换说明符(以及"%s")使用前导空格,而"%c""%[...]"则不使用前导空格。

所有这些都是鼓励新的C程序员使用面向行的输入函数(如fgets或POSIXgetline)来处理用户输入的主要原因(因为他们一次读取整行,包括试用'n'),从而使新程序员在匹配的情况下不必考虑结尾空白或未转换的违规字符失败

使用fgetssscanf提供了额外的好处,即允许对(1)输入的读取进行单独的验证;以及(2)将输入解析并转换为所需的值。

(注意:面向行的输入函数唯一需要注意的是,它们读取并在填充的缓冲区中包含尾部的'n',因此您需要根据需要"修剪"尾部的空白。您不希望在存储的字符串末尾挂起零散的'n'字符。)

也就是说,有时使用scanf函数族读取输入是有意义的。这样做没有错,只要您每次验证返回并处理所有三种可能的条件

  1. 用户在Linux上按ctrl+d生成手动EOF(windoze上的ctrl+z
  2. 您处理匹配输入失败的情况,包括在下次尝试读取之前从输入缓冲区中删除任何有问题的字符;最后
  3. 您有很好的输入(返回值表示预期的、发生的所有转换)

其中任何一个都没有魔力,但它确实需要了解可能的错误条件,并在每次输入时处理每一个错误条件。

在您的案例中,让我们看看您从用户那里获取要输入的字数的任务。在这里,您试图使用fgets进行读取(这很好!),但未能提供足够的存储空间来容纳输入。当从用户那里读取少量文本时,只需要一个具有自动存储类型的简单数组。但是,您需要相应地调整缓冲区的大小(不要在缓冲区大小上吝啬)。

没有黄金法则,但如果我让用户输入文本以转换为单个数字,那么我会对256字符缓冲区感到满意(它提供了足够的容量来容纳任何有效数字的输入,再加上另外230个奇数字符来处理猫踩键盘的时间,等等。)

例如,从用户那里获取输入并获得要输入的单词数量可以用类似于以下的方式完成:

#include <stdio.h>
#define SIZE  10    /* good form defining a constant! */
#define MAXC 256    /* max characters for buffer */
int main (void) {
int nwords = 0,         /* number of words to enter */
words = 0,          /* each word */
wc = 0;             /* word count */
char buf[MAXC] = "";    /* buffer of sufficient size for input */
for (;;) {  /* loop continually until valid input or user cancels */
printf ("number of words to enter? [1-%d]: ", SIZE);
if (!fgets (buf, MAXC, stdin)) {    /* validate ALL user input */
fputs ("(user canceled input)n", stderr);
return 1;
}
/* validate length < MAXC - 1 and buf[length-1] == 'n' here */
if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
fputs ("  error: invalid conversion to int.n", stderr);
continue;
}
if (nwords < 1 || SIZE < nwords)  /* validate nwords in range */
fprintf (stderr, " %2d out of valid range.n", nwords);
else  /* good input received, break loop */
break;
}

(注意:您的while循环已转换为一个循环,该循环将持续循环,直到输入1 < value < SIZE之间的有效输入。该条件只会导致控制break;,即接收到良好输入时的循环)

该循环呈现了对来自用户输入的输入行的信息的经典fgets/sscanf读取和解析。您可以以任何方式解析该行中的数字(但不要使用atoi()——它提供了绝对的转换错误检查)。您可以使用strtol(经过适当的验证),也可以简单地使用指针在缓冲区中遍历,挑选数字,将其从ASCII转换为数值,然后乘以10,然后边加边加。只要验证、验证、验证操作的每个部分,任何方法都可以。

现在转向阅读用户应该输入的每个单词,我们将忽略传统观点,使用scanf来完成任务,但我们每次都会处理返回的所有三种可能情况。我们还将添加一个计数器来跟踪用户提供的有效输入,并且只有当我们提供了该数量的有效整数时才退出循环(或者用户通过生成手动EOF来取消)。

printf ("nnumber of words entered: %dn", nwords);
for (; wc < nwords;) {  /* loop continually  */
int rtn = 0;    /* scanf return */
printf ("please enter a number between 1 and %d: ", SIZE);
rtn = scanf ("%d", &words);     /* valdate ALL user input */
if (rtn == EOF) {           /* handle EOF (manual) */
fputs ("(user canceled input)n", stderr);
break;
}
else if (rtn == 0) {    /* handle "matching failure" */
int c = getchar();  /* remove offending chars from stdin */
while (c != 'n' && c != EOF)
c = getchar();
fputs ("  error: invalid integer inputn", stderr);
continue;
}
else {  /* valid integer received */
int c = getchar();      /* remove any extra chars from stdin */
while (c != 'n' && c != EOF)
c = getchar();
if (words < 1 || SIZE < words)  /* validate in-range */
fprintf (stderr, " %2d - invalid! (1 < valid < %d)n", 
words, SIZE);
else    /* good input, increment word count */
printf (" word[%2d]: %3dn", ++wc, words);
}
}

注意:清空stdin中任何有问题的字符可以变成一个方便的功能,这样您就不必在输入例程期间每次需要清除stdin时重复循环。你可以用一个简单的功能来代替它,例如

/* helper function to remove any chars left in input buffer */
void empty_stdin()
{
int c = getchar();
while (c != 'n' && c != EOF)
c = getchar();
}

这将有助于保持代码整洁。我让你把上面的内容结合起来。

总之,你可以做以下事情:

#include <stdio.h>
#define SIZE  10    /* good form defining a constant! */
#define MAXC 256    /* max characters for buffer */
int main (void) {
int nwords = 0,         /* number of words to enter */
words = 0,          /* each word */
wc = 0;             /* word count */
char buf[MAXC] = "";    /* buffer of sufficient size for input */
for (;;) {  /* loop continually until valid input or user cancels */
printf ("number of words to enter? [1-%d]: ", SIZE);
if (!fgets (buf, MAXC, stdin)) {    /* validate ALL user input */
fputs ("(user canceled input)n", stderr);
return 1;
}
/* validate length < MAXC - 1 and buf[length-1] == 'n' here */
if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
fputs ("  error: invalid conversion to int.n", stderr);
continue;
}
if (nwords < 1 || SIZE < nwords)
fprintf (stderr, " %2d out of valid range.n", nwords);
else 
break;
}
printf ("nnumber of words entered: %dn", nwords);
for (; wc < nwords;) {  /* loop continually  */
int rtn = 0;    /* scanf return */
printf ("please enter a number between 1 and %d: ", SIZE);
rtn = scanf ("%d", &words);     /* valdate ALL user input */
if (rtn == EOF) {           /* handle EOF (manual) */
fputs ("(user canceled input)n", stderr);
break;
}
else if (rtn == 0) {    /* handle "matching failure" */
int c = getchar();  /* remove offending chars from stdin */
while (c != 'n' && c != EOF)
c = getchar();
fputs ("  error: invalid integer inputn", stderr);
continue;
}
else {  /* valid integer received */
int c = getchar();      /* remove any extra chars from stdin */
while (c != 'n' && c != EOF)
c = getchar();
if (words < 1 || SIZE < words)  /* validate in-range */
fprintf (stderr, " %2d - invalid! (1 < valid < %d)n", 
words, SIZE);
else    /* good input, increment word count */
printf (" word[%2d]: %3dn", ++wc, words);
}
}
}

示例使用/输出

$ ./bin/getintstdin
number of words to enter? [1-10]: five, maybe six?
error: invalid conversion to int.
number of words to enter? [1-10]: -2
-2 out of valid range.
number of words to enter? [1-10]: 3
number of words entered: 3
please enter a number between 1 and 10: two? three?
error: invalid integer input
please enter a number between 1 and 10: 2
word[ 1]:   2
please enter a number between 1 and 10: -2
-2 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 11
11 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 3
word[ 2]:   3
please enter a number between 1 and 10: 4
word[ 3]:   4

请注意上面所有的无效输入,以及代码如何处理每个输入。只要fgets的输入不超过255个字符,代码就会正常地响应非有效整数的输入(无论给定多少),并且会响应超出范围的整数输入。

该代码并不比您发布的代码长多少,但它解决了可能出现的错误条件,然后处理了错误。当你把这一切归结起来时,这就是编码的意义所在。仔细看看,如果你还有问题,请告诉我。

相关内容

  • 没有找到相关文章

最新更新