来自 stdin 的 fgets 具有不可预测的输入大小

fgets from stdin with unpredictable input size

我正在尝试从 stdin 中读取一行,但我不知道如何正确处理输入大小至少等于限制的情况。示例代码:

void myfun() {
    char buf[5];
    char somethingElse;

    printf("\nInsert string (max 4 characters): ");
    fgets (buf, 5, stdin);

    ...

    printf("\nInsert char: ");
    somethingElse = getchar();
}

现在,用户可以做三件事:

  1. Input less than 4 characters (plus newline): 在这种情况下,stdin 中没有任何内容,随后的 getchar() 正确地等待用户输入;
  2. 恰好输入 4 个字符(加上换行符):在这种情况下,stdin 中还剩下一个换行符,随后的 getchar() 会读取它;
  3. 输入超过4个字符(加上换行符):在这种情况下stdin中至少还剩下一个字符,随后的getchar()读取它,至少留下
  4. 中的换行符

案例 23 需要使用 while(getchar() != '\n') 之类的东西清空 stdin,而案例 1 不需要任何额外的操作。据我阅读类似问题的答案和 c-faq 了解到,没有 standard/portable 方法可以知道实际情况是否是 1 中描述的情况。

我搞定了吗?或者实际上 一种可移植的方法?或者可能是一种完全不同的方法?

如果有空间,fgets 函数会将换行符存储在缓冲区中。因此,如果字符串中的最后一个字符不是换行符,您就知道需要刷新缓冲区。

fgets (buf, 5, stdin);
if (strrchr(buf, '\n') == NULL) {
    // flush buffer
    int c;
    while ((c = getchar()) != '\n') && (c != EOF));
}

如果假设 空字符 '[=12=]' 永远不会被读取,那么 答案将起作用。

如果读取了 空字符,则 strrchr(buf, '\n') 不会找到任何可能已读取的 '\n'

代码可以预先设置缓冲区以查看最后是否读取了 '\n'

buf[sizeof buf - 2] = '\n';
if (fgets (buf, sizeof buf, stdin)) {
  if (strrchr(buf, '\n') == NULL) {
    // incomplete line read.  (the usual detection)
  } else if (buf[sizeof buf - 2] != '\n') {
    // incomplete line read with prior null character  (see below note).
  }
}

然而,C 标准并未指定在 buf[] 中读取的数据之后的数据保持不变,用某种模式预填充缓冲区不足以检测 空字符 '[=12=]' 已阅读。

is a portable way to do it?

最便携的方法是使用重复调用 fgetc() 或类似的方法而不是 fgets()

maybe a totally different approach?

我推荐fgetc()或者通用但不是C标准的getline()


另一种选择:使用scanf("%4[^\n]%n", buf, &n):它非常麻烦,但是一种可移植的方式是可能的。它会跟踪 '\n' 之前读取的字符数,即使有些是 空字符

  int n = 0;
  cnt = scanf("%4[^\n]%n", buf, &n);  // Cumbersome to get that 4 here.
  // Lots of TBD code now needed:
  // Handle if cnt != 1 (\n to be read or EOF condition)
  // Handle if n == sizeof buf - 1, (0 or more to read)
  // Handle if n < sizeof buf - 1, (done, now get \n)
  // A \n may still need to be consumed
  // Handle EOF conditions