【问题标题】:Use and explanation of getchar() functiongetchar()函数的使用和解释
【发布时间】:2019-09-14 07:18:23
【问题描述】:

我正在编写一个程序来读取用户输入语句并从输入中提取所有整数。例如,如果我输入“h3ll0”,程序将输出“30”。我已经使用 fgets 函数来读取用户输入。

但是,我目前正在阅读有关 getchar() 的内容,并且想知道在我的程序中使用 getchar() 来读取用户输入而不是 fget 的最佳方法是什么。我不太清楚 getchar() 的工作原理以及它在什么情况下有用。

这个问题与一个项目有关,该项目专门要求 getchar() 作为读取用户输入的方法。由于我不清楚 getchar() 是如何工作的,所以我使用 fgets 构建了程序的其余部分以确保它正常工作。

#include <stdio.h>

int main()
{
    char user_input[100];
    int i;
    int j = 0;

    printf("Please enter your string: ");
    fgets(user_input ,100, stdin);

    for(i = 0; user_input[i] ; i++)
    {

        if(user_input[i] >= '0' && user_input[i] <= '9')
        {
            user_input[j] = user_input[i];
            j++;
        }
    }


    user_input[j] = '\0';

    printf("Your output of only integers is: ");
    printf("%s\n", user_input);

    return 0;
}

【问题讨论】:

  • 你的意思是“使用 getchar()”而不是“实现 getchar()”,不是吗?
  • 留在fgets(),免去很多麻烦和问题。
  • 我认为你应该只将 99 个字符读入你的 100 个字符数组中,以便在结尾处为 '\0' 留出空间。
  • 你知道你可以edit你的问题来添加信息和解决潜在的误解,不是吗?
  • 如果您有工作代码时必须使用getchar(),那么请查看meta.stackoverflow.com/questions/334822/…,因为这似乎是典型的教师要求。

标签: c


【解决方案1】:

OP:不清楚getchar() 的工作原理

int fgetc(FILE *stream) 通常返回 257 个不同值中的 1 个。

“如果 ... 存在下一个字符,fgetc 函数会将该字符作为 unsigned char 转换为 int C11 §7.21.7.1 2

在文件结束或输入错误(罕见)时,返回EOF


OP:在我的程序中使用 getchar() 而不是 fgets 来读取用户输入。

使用与fgets() 相同的函数签名和函数创建您自己的my_fgets(),然后替换。

char *fgets(char * restrict s, int n, FILE * restrict stream);

fgets 函数从 stream 指向的流中读取最多比n 指定的字符数少一个到s 指向的数组中。在换行符(保留)之后或文件结尾之后不会读取其他字符。在读入数组的最后一个字符之后立即写入一个空字符。 C11 §7.21.7.2 2

返回相同的值

如果成功,fgets 函数将返回 s。如果遇到文件结尾并且没有字符被读入数组,则数组的内容保持不变并返回一个空指针。如果在操作过程中发生读取错误,则数组内容是不确定的,并返回一个空指针。 §7.21.7.2 3


未经测试的代码示例

#include <stdbool.h>
#include <stdio.h>

char *my_fgets(char * restrict s, int n, FILE * restrict stream) {
  bool something_read = false;
  int ch = 0;
  char *dest = s;

  // Room ("reads at most one less") and EOF not returned?
  while (n > 1 && (ch = fgetc(stream)) != EOF) {
    n--;
    something_read = true;
    *dest++ = (char) ch;
    if (ch == '\n') {
      break;  // "No additional characters are read after a new-line character"
    }
  }

  // Did code end the while loop due to EOF?
  if (ch == EOF) {
    // Was EOF due to end-of-file or rare input error?
    if (feof(stream)) {
      // "If end-of-file is encountered and no characters ... read into the array ..."
      if (!something_read) {
        return NULL;
      }
    } else {
      // "If a read error ..."
      return NULL;  // ** Note 1
    }
  }

  // room for \0?
  if (n > 0) {
    *dest = '\0';  //" A null character is written immediately after the last character"
  }

  return s;
}

也许改进fgets() 并将size_t 用于n

char *my_fgets(char * restrict s, size_t n, FILE * restrict stream);

fgets()n &lt;= 0not clearly defined。使用size_t,一个unsigned类型,至少可以消除n &lt; 0的顾虑。

注意 1:或使用 s = NULL; 代替 return NULL; 并让剩余的代码 null 终止缓冲区。我们有这个选项,因为“数组内容是不确定的”。

【讨论】:

    【解决方案2】:

    这样的东西应该可以作为fgets的笨拙替代品,只使用getchar。我不保证错误处理的准确性。

    我认为您永远不会希望在应用程序中使用 getchar 而不是 fgetsGetchar 更受限制且更不安全。

    #include <stdint.h>
    
    void    your_fgets(char *buffer, size_t buffer_size)
    {
        int     i;
        size_t  j;
    
        if (buffer_size == 0)
            return ;
        else if (buffer_size == 1)
        {
            buffer[0] = '\0';
            return ;
        }
        j = 0;
        while ((i = getchar()) != EOF)
        {
            buffer[j++] = i;
            if (j == buffer_size - 1 || i == '\n')
            {
                buffer[j] = '\0';
                return ;
            }
        }
        buffer[j] = '\0';
    }
    

    【讨论】:

    • 好点,我应该确保循环在 EOF 上退出。我编辑了答案。
    • 注意:buffer[] 在返回 EOF 后可能缺少 null 字符 - 有时这很好,有时则不然。
    • 我认为坚持 fgets 手册是正确的选择。
    【解决方案3】:

    我对这篇文章中的 cmets 表示 fgets 更易于使用感到困惑。使用 fgets 不必要地使问题复杂化。做吧:

    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    int
    main(int argc, char **argv)
    {
            int c;
            while( ( c = getchar() ) != EOF ) {
                    if(isdigit(c) && (putchar(c) == EOF)) {
                            perror("stdout");
                            return EXIT_FAILURE;
                    }
            }
            return ferror(stdin);
    }
    

    绝对没有理由使用任何额外的缓冲,或者一次读取一行输入。也许您希望在换行符进入时输出它们,但这将是问题中未指定的实现细节。无论哪种方式,这都是微不足道的 (if(( c == '\n' || isdigit(c)) &amp;&amp; (putchar(c) == EOF)))。只需读取一个字符并决定是否要输出它。如果您不认为输入比实际更复杂,那么逻辑会容易得多。 (它不是面向行的......它只是一个字节流。)

    如果出于某种未知原因,您想让该工具仅在交互式设置中可用,并以过度冗长的方式加载输出,您可以轻松地执行以下操作:

    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    
    int
    main(int argc, char **argv)
    {
            int c;
            do {
                    int want_header = 1;
                    printf("Please enter your string: ");
    
                    while( ( c = getchar() ) != EOF && c != '\n' ) {
                            if(! isdigit(c)) {
                                    continue;
                            }
                            if(want_header) {
                                    want_header=0;
                                    printf("Your output of only integers is: ");
                            }
                            if(putchar(c) == EOF) {
                                    perror("stdout");
                                    return EXIT_FAILURE;
                            }
                    }
                    if( c == '\n')
                            putchar(c);
                    want_header = 0;
            } while(c == '\n');
            return ferror(stdin);
    }
    

    但是,请不要那样做。 (想象一下,如果grep 开始时发出一个提示“请输入您要搜索的正则表达式”!)

    【讨论】:

    • 解除困惑:直接使用getchar() 不带缓冲区是个好主意,因为编码目标确实没有缓冲要求,尤其是我们假设“提取所有整数”实际上是“提取所有数字”。 OTOH,这是一个有趣的案例,据称 OP 有工作代码并希望用 getchar() 替换 fgets() -不知何故。正如您所建议的那样,重构整体代码在这里很好,但在较大的代码中,限制更改和局部性更安全。使用fgets() 替换,风险更小。
    • 请注意,完全不必要和不受欢迎的冗长的附加逻辑引入了一个错误:如果尝试编写换行符失败,我们没有捕捉到错误。复杂的逻辑是不好的。避免它。
    • 如果您只想输出结果,这很有效,这是一个很好的例子,其中 getcharfgets 更适合。我们不知道为什么 OP 想要使用 getchar 而不是 fgets,并且丢失缓冲区可能是进一步处理的问题。
    • 而且我们没有检查 any 的 printfs 是否有错误。伊克。
    • 注意:看起来似乎不需要检查 printf 是否有错误,但它不是一个深奥的错误。通常的工作流程是代码不检查错误,进程将数据写入关闭的管道,进程收到 SIGPIPE 并终止,一切都很好。但是,如果该进程在已传递忽略 SIGPIPE 的处置的 python 子进程中运行,则该进程不会终止。始终检查写入错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多