【问题标题】:fgets from stdin problems [C]来自标准输入问题的 fgets [C]
【发布时间】:2010-03-02 00:40:42
【问题描述】:

我正在编写一个处理文件的程序。 我需要能够将数据作为结构输入,并最终将其读出。 我目前遇到的问题是这段代码:

typedef struct {
    char* name;
    .....
}employeeRecord;
employeeRecord record;

char name[50];

if(choice == 1)
    {
        /*Name*/
        printf("\nEnter the name:");
        fgets(name,50,stdin);
        record.nameLength = strlen(name) -1;
        record.name = malloc(sizeof(char)*record.nameLength);
        strcpy(record.name,name);
        /*Other data, similar format...*/

如果我想要例如姓名地址和电话号码,并连续询问每一个(所以地址与上面几乎相同,除了用地址替换“姓名”),我发现它会跳过输入。我的意思是,我没有机会输入它。输出实际上是 输入名称: 输入地址:(这里提示我输入)

【问题讨论】:

  • 您应该使用实际显示问题的代码更新问题(顺便说一下,应该是strlen(name) + 1,而不是- 1)。
  • 我这里没有看到任何问题,你有什么具体问题?

标签: c file file-io fgets


【解决方案1】:

换行符仍在stdin 中,来自先前调用未从输入中读取换行符的函数。清除stdin,直到你读出换行符——不是,像其他人建议的那样刷新stdin

编辑:感谢 Alok 的更正!

【讨论】:

  • 就像 Bernard 说的那样,如果打算进行面向行的输入,应该小心不要留下未读的尾随空格(就像 scanf 所做的那样)。一次读取所有内容(例如通过 fgets),然后在读取的行上使用 sscanf 以一个合适的选项。
  • 如果fgets 读取整行,则stdin 缓冲区中的换行符是not。 OP 很可能正在通过getchar()getc()fgetc() 或等效项之一读取choice。如果fgets 没有读取整行,则输入缓冲区需要更大。 (即使您回答中的引号也表示已读取换行符。)
  • 通过:“while (getc(stdin) != '\n');” -- fflush(stdin) 不应该被使用,因为它是未定义的行为。
  • 我通过 scanf("%d",&choice);我通过重复 fgets 调用半解决了它。所以我仍然不清楚该怎么做......
【解决方案2】:

我尝试了您的代码,但无法重现该问题。下面的代码按照您的预期工作,它会提示输入名称,等待您输入名称,然后提示输入地址等。

我想知道您是否不需要在提示输入更多输入之前读取标准输入并将其清空?

typedef struct {
    char* name;
    char* address;
}employeeRecord;

int readrecord(employeeRecord &record)
{
   char name[50];
   char address[100];

   printf("\nenter the name:");
   fgets(name, sizeof(name), stdin);
   record.nameLength = strlen(name) + 1;
   record.name = malloc(sizeof(char)*record.nameLength);
   strcpy(record.name,name);

   printf("\nenter the address:");
   fgets(address, sizeof(address), stdin);

   ...    
}

顺便说一句,您想将 1 加到 strlen(name),而不是减 1。或者,如果您希望 name 存储在您的记录中而没有终止 null,那么您需要使用 memcpy 将字符串复制到您的记录中,而不是strcpy。

编辑:

我从 cmets 看到您正在使用 scanf 来读取选择值,这会在输入缓冲区中留下一个 \n ,然后您的第一个 fgets 调用会拾取该值。您应该做的是使用 fgets 读取选择行,然后使用 sscanf 从输入中解析值。像这样

int choice;
char temp[50];
fgets(temp, sizeof(temp), stdin);
sscanf(temp, "%d", &choice);

这应该会使刷新标准输入的整个问题变得毫无意义。

【讨论】:

  • StackOverflow 上肯定有无数的 cmets 关于 fflush(stdin) 是如何不受支持的任何东西,如果它有效,那只是偶然。
  • @John:这不是“错误”的事情,C 标准将行为保留为“未定义”。甚至您的链接也这么说:'// 输入流上的 fflush 是对 C 标准的扩展'。现在,您可能会说这是它应该的行为,但标准没有定义任何东西(并且作为 fsync() 系统调用对 *nix 系统上的标准输入描述符没有意义('同步挂起的写入标准输入到disk'), FILE* stdin 上的 fflush 同样没有什么意义在那种环境中。而且由于它是不可移植的,因此应尽可能避免使用它(就像使用 GNU 扩展时一样)。
  • 我将 flush 理解为“刷新到目的地”。由于要从中读取标准输入,因此对我来说没有任何意义。现在我是一名 Unix 程序员,正如我之前所说,从语义上讲,我将 fflush 读作 FILE 指针的 fsync。我从您的个人资料中得知您主要是一名 Windows 程序员。当您使用不同的环境时,您对 fflush 的期望是不同的。因此,Windows 可以满足您的期望。但并不是到处都是这样,因为不同环境的用户有不同的期望。看到 C 和 Unix 一起成长,你就会明白为什么 C 标准没有定义它。
  • @Bernard:很公平。知道它不可移植是很有用的。
  • 而且,似乎有一个单独的函数用于此目的:linux.about.com/library/cmd/blcmdl3_fpurge.htm 不幸的是,它似乎无法移植到 MSVC!你就是赢不了。 =p(当然,在 MSVC 上 fflush 做同样的事情,一个简单的包装函数,嗯,很简单!)
【解决方案3】:

在调用fgets 读取名称之前,您可能使用scanf 读取choicescanf 可能在stdin 中留下了一个换行符,您的代码将其误认为是空名称输入。如果确实如此,请尽量不要使用scanf(使用fgets 检索choice 并使用atoi 转换为intstrcmp 以与“1\n”等进行比较) .否则代码应该可以工作,并进行以下修改以说明fgets 还将终止换行符读入缓冲区(您可能想要剥离):

  #define MY_LENOF(x) (sizeof(x)/sizeof((x)[0])) 

  char choice[3] = { 0 }; /* example of how to initialize to all NULs */
  if (!fgets(choice, MY_LENOF(choice), stdin)) {
    fprintf(stderr, "Premature end of input\n");
    exit(1);
  }

  if (strcmp(choice, "1\n") == 0) {  
    /*Name*/
    printf("\nEnter the name:");
    if (!fgets(name, MY_LENOF(name), stdin)) {
      /* if fgets fails it leaves name unchanged, so we reset it to "" */
      name[0] = '\0';
    }
    /* good practice to use srtnlen in order not to overrun fixed buffer */
    /*  not necessarily a problem with fgets which guarantees the trailing NUL */
    size_t nameLength = strnlen(name, MY_LENOF(name));
    assert(name[nameLength] == '\0');
    if (nameLength - 1 > 0 && name[nameLength - 1] == '\n') {
      /* strip trailing newline */
      name[--nameLength] = '\0';
    } else if (nameLength >= MY_LENOF(name) - 1) {
      fprintf(stderr, "Name is too long\n");
      exit(1);
    } else {
      fprintf(stderr, "Premature end of input\n");
      exit(1);
    }

    record.nameLength = nameLength;
    record.name = malloc(sizeof(char)*(record.nameLength + 1));
    strcpy(record.name, name);

【讨论】:

  • 可能不想使用atoi,因为它不执行错误检查(sscanf 并检查返回值应该可以工作,但我的 C 已经生锈了)。很好的答案。
  • 正确。因此,在他的情况下(我想他不执行算术,只是检查)他应该使用strcmp
  • @Bernard: sscanf 如果你不小心的话,对于错误检查来说也很糟糕(例如,"%d" 将匹配 "123foo")。使用strtod 等检查格式错误的输入要简单得多。
  • 就像我说的,我的 C 生锈了! =D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多