【问题标题】:odd behaviour of getchar() functiongetchar() 函数的奇怪行为
【发布时间】:2015-12-23 23:35:10
【问题描述】:

我正在尝试使用fopen("filename","w") 编写一个将数据存储在文本文件中的函数,其中“文件名”是用户输入的字符串。我选择通过getchar() 函数来执行此操作,并且我需要一个计数器变量,每次击键都会递增。这就是事情变得复杂和混乱的地方。

   char *p;
   int count = 0; 
   p = (char*)malloc(32*sizeof(char));
   do 
   {
       *p = getchar();
       count++;
   }
   while(getchar() != '\n');

在输入 3 个字符之前,只需按 1 次 Enter 键,计数器准确,直到 2 个字符后的 2 个字符遵循奇数模式。

--------------------Input------------------------Count--------------------
-------------------- t --------------------------  1  --------------------
-------------------- te -------------------------  2  --------------------
-------------------- tes  -----------------------  2  --------------------
-------------------- test ------------------------ 3 ---------------------
-------------------- test1 ----------------------- 3 ---------------------
-------------------- test12 ---------------------- 4 ---------------------
-------------------- test123 --------------------- 4 ---------------------
-------------------- test1234 -------------------- 5 ---------------------

基本上,每增加 2 个字符,计数就会增加 1。

这个函数在这种情况下是如何工作的,为什么它需要 2 次击键?

【问题讨论】:

  • 不要将malloc 和朋友的结果投射到 C 中!并且sizeof(char) 被定义为产生1。这是多余的。

标签: c getchar


【解决方案1】:

您在循环中每次调用getchar 两次。您在字符末尾按回车键(换行符)。而您在第二个getchar 之前 增加计数。所以计数将向上取整为(n + 1) / 2,其中n 是字符数。

加一个换行符。除以二,因为每次增量读取两个字符。并向上取整,因为增量发生在第二次读取之前。

【讨论】:

    【解决方案2】:

    Dave Schwartz 关于调用 getchar() 两次是对的。您的代码片段中还有一两个粗糙的边缘;让我们谈谈这些,同时我对 Dave 的回答进行一些扩展。

    C 语言可以非常密集;在一行代码中可以进行多件事情。对于刚起步的人,我鼓励你写长篇文章,即使你认为你不需要……然后随着你对 C 正在做什么的心智模型变得更丰富而巩固。

    例如...让我们修改您的代码片段,使其看起来像这样:

    int count = 0; 
    p = (char*)malloc(32*sizeof(char));
    char c = getchar(); /* first call to getchar() */
    while( c != '\n') {
       p[count] = c; /* easier to read than *p=getchar() */
       count++; /* could be combined...  see below for a more C-like version. */
       /* question: what would happen if we increment count BEFORE we store in p's memory? */
    
       /* Also... your initial code was this: */
       /*   *p = getchar(); */
       /* which is always assigning getchar to p[0]. */
       /* see below for more "idiomatic" way to do that. */
       /* see below for more "idiomatic" way to do that. */
    
       /* Get the next char, then let while() condition decide if */
       /* we come back into the loop body or proceed after it. */
       /* It is a common tactic to put an input value in a scratch variable */
       /* like 'c' and use it at different points in your loop. */
    
       c = getchar(); /* all additional calls to getchar. */
       /* note that we already declared c above the while-loop, so ok to re-use. */
    }
    /* dont want to call getchar() again here... this is the problem dave called out. */
    /* while( getchar() != '\n' ); */
    /* ...do some stuff here */
    free(p); /* your logic does free the malloc at some point, yes? :-) */
    

    现在您可以尝试将尽可能多的代码折叠成一个表达式。 但是不要那样做——至少在你能够轻松编写1)易于阅读的代码,2)易于预测它会做什么之前不要这样做。

    您最终会发现编写代码并不是最难的部分。

    阅读 2 周(或 2 多年前)编写的代码很难。

    接下来让我们谈谈循环:使用 while()、for() 和 do/while()。 while() 和 for() 为您提供跳过循环体的选项... do/while() 将始终执行循环体,这可能不是您在您的情况下想要的,因为他们用户可以直接按 Enter给你第一个字符'\n'。

    for 循环版本 考虑一下你的逻辑在使用 for() 循环时的样子:

    int count = 0; 
    p = (char*)malloc(32*sizeof(char));
    for( char c = getchar(); c != '\n'; c = getchar() ) {
       p[count] = c;
       count++;
    }
    

    do/while 版本 我发现用 do/while() 循环写这个更困难: 并不是说代码很难编写,而是我发现很难让代码可读,所以意图很清楚。

    int count = 0; 
    p = (char*)malloc(32*sizeof(char));
    char c;
    do {
       if( count >= 1  ) {
          /* ugly... check count >= 1 so we don't save an uninitialized 'c' */
          p[count] = c;
       }
       count++;
    } while( (c = getchar() );
    count--; /* adjust because we counted our '\n'. */
    

    再次添加一些额外的 C 习语 这个很难读,因为要理解它,你必须能够 解开 while() 表达式,它执行以下操作:

    1) 调用 getchar() 函数并将结果分配给我们的 temp var 'c'

    2) 将该赋值的结果与 '\n' 进行比较

    3) while() 计算比较的结果,并且只有在 temp char var 'c' 中除了 '\n' 之外还有其他内容时才进入循环体。

    在您问“这是真的吗?”之前,您需要考虑很多事情。 仅供参考 - 这是我实际尝试运行的唯一示例...

    #include <stdio.h>
    #include <malloc.h>
    
    void main( char **argv ) {
       printf("Please type some stuff:\n");
    
        int count = 0; 
        char *p = (char*)malloc(32*sizeof(char));
        char c;
        while( (c = getchar() ) != '\n') {
           p[count++] = c;
           /* So... could we just saying the following instead?
            *     *p++ = c;
            */
        }
        p[count] = '\0'; /* this would turn our malloced buffer into a C-style string */
        printf("found %d chars = '%s'\n", count, p);
        free(p);
    }
    

    最后一点要思考,请考虑以下几点:

    1) 我们的用户在按下回车键之前可以输入多少个字符?

    2) 假设 #1 确实有一个限制,它是什么以及为什么

    3) 您可以做些什么来添加检查以确保我们不会让我们的用户尝试输入太多字符?

    【讨论】:

    • char c; 应该是 int c; ,你也应该检查它不是EOF
    • 哇,伙计,您的回复让我大吃一惊。谢谢,感谢您的时间和努力。看起来这个网站有一个很棒的社区,我很喜欢它。再次感谢您的帮助,祝您节日快乐:)。
    • @M.M 同意...最好使用 'int c' 与 'char c'。最好检查 getchar() 返回 -1 以指示 eof。也更好地检查 malloc 失败。 :-) 在 Paragon 进一步学习之后,他们可以阅读以下内容:stackoverflow.com/questions/3676796/…
    • Paragon - 非常感谢。编程可以很有趣。 C 有一些令人惊讶的复杂性,我鼓励您尝试运行程序并输入 100 个随机字符(或多或少)。对我来说,核心转储了,这对您解决原因很有用。 M.M 也提出了一个很好的观点,但对于最初的课堂/研究代码,我不会太担心这样的事情。玩得开心:-)
    • 不要将malloc 和朋友的结果投射到 C 中!并且sizeof(char) 被定义为产生1。这是多余的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-03
    • 2011-06-14
    • 2020-01-31
    相关资源
    最近更新 更多