getchar() 函数的奇怪行为

odd behaviour of getchar() function

我正在尝试使用 fopen("filename","w") 编写一个将数据存储在文本文件中的函数,其中 "filename" 是用户输入的字符串。我选择通过 getchar() 函数这样做,我需要一个随着每次击键而递增的计数器变量。这就是事情变得复杂和混乱的地方。

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

直到输入 3 个字符,它只需要按 1 次回车键,并且计数器是准确的,直到 2 个字符之后的 2 个字符遵循一个奇怪的模式。

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

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

该函数在此上下文中如何工作以及为什么需要 2 次击键?

您在循环中每次调用 getchar 两次。您在字符末尾按回车键(换行符)。然后你在 第二个 getchar 之前增加计数 。因此计数将 (n + 1) / 2 四舍五入,其中 n 是字符数。

换行加一。除以二是因为每次递增读取两个字符。并四舍五入,因为增量发生在第二次读取之前。

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() 将始终执行循环体,这在您的情况下可能不是您想要的,因为他们用户可以按回车键,第一个字符为“\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() 循环写这个更难: 并不是说 编写 代码很难,但我发现很难使代码可读,因此 intent 很清楚。

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() 函数并将结果分配给我们的临时变量 'c'

2) 将该赋值的结果与 '\n'

进行比较

3) while() 计算比较的结果,只有当我们在 temp char var 'c'.

中有除 '\n' 之外的内容时才进入循环体

在您提问 "was it true?" 之前,您需要思考很多问题。 fyi - 这是我实际尝试过的唯一示例 运行...

#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] = '[=13=]'; /* 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) 你能做些什么来添加检查以确保我们不会让我们的用户尝试输入太多字符?