首页 > 解决方案 > 如何仅使用 getchar 将可变大小的字符串作为输入而不询问用户之前的字符串大小?

问题描述

我只需要收集用户输入getchar()并将malloc()其存储在一个字符串中(其大小未知)。我以前做过,但忘记了我是怎么做到的,现在我的字符串有问题,只打印第一个字母,这意味着我的 get_string 函数没有从标准输入收集所有字符,或者指针没有指向或者它只是没有用 printf 正确打印。

char *get_string(void);

int main(void)
{
    printf("Input string: ");
    char *p = get_string();
    printf("Output string: %s\n", p);
}

char *get_string(void)
{
    int c = 0;
    char *str = NULL;
    char *buff = NULL;

    for(int i = 0; c != '\n'; i++)
    {
        if(i > 0) // skips on first iteration (no char collected yet)
        {
            if(i > 1) // skips on second iteration (1st char collected)
            {
                buff = malloc(i + 1);
                for(int j = 0; j < i - 1; j++)
                    buff[j] = str[j];
                free(str);
            }
            str = malloc(i + 1); // allocate space for string
            if(i > 1) // no need to copy string from buffer
            {
                for(int j = 0; j < i - 1; j++)
                    str[j] = buff[j];
                free(buff);
            }
            str[i - 1] = c; // place char into string
            str[i] = '\0'; // terminate string with '\0'
            printf("%s\n", str); // print contents on each iteration
        }
        c = getchar();
    }
    return (str);
}

如果我使用返回的字符串在 main 中运行 printf,则不会打印任何内容。如果我在循环内运行 printf,它只会在第一次迭代(第一个字母)时打印。

我得到什么:

$ > gcc get_string.c -o get_string
$ > ./get_string
Input string: Hello World!
H
Output string:

我的期望:

$ > gcc get_string.c -o get_string
$ > ./get_string
Input string: Hello World!
H
He
Hel
Hell
Hello
...
Output string: Hello World!

此外,如果您知道更好(和更短)的方法来解决这个问题,请分享。

标签: carraysstringinputgetchar

解决方案


您将希望使用realloc扩展输入缓冲区,尽管您不想为每个单独的字符都这样做(这是一个相对昂贵的操作,并且可能导致字符串在内存中移动)。一个常见的技巧是在到达缓冲区末尾时将缓冲区的大小加倍,以便在读取字符时缓冲区大小从 16 变为 32 再到 64 等,从而最大限度地减少realloc调用次数。权衡是一点内部碎片 - 您最终可能会在 128 个字符的缓冲区中存储 65 个字符。但平均而言,这应该不是太大的问题。这是一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define START_SIZE 16 // some size that should handle most cases

/**
 * Get the next line from the specified input stream.  Return characters up to
 * (but not including) the next newline character or EOF.  Return the size of the
 * allocated buffer as well.  
 */
char *getline( FILE *stream, size_t *size )
{
  *size = START_SIZE;
  size_t i = 0;

  /**
   * Initial allocation, buf can store a string up to START_SIZE - 1 characters.
   * If initial allocation fails, return NULL.
   */
  char *buf = malloc( sizeof *buf * *size );
  if ( !buf )
  {
    fprintf( stderr, "Failure to allocate initial buffer\n" );
    return NULL;
  }

  /**
   * Read from the input stream until we see a newline or EOF.  Newline will
   * *not* be stored in the returned string.
   */
  for ( int c = fgetc( stream ); c != '\n' && c != EOF; c = fgetc( stream ))
  {
    /**
     * Have we hit the end of the input buffer yet (allowing for the terminator)?
     */
    if ( i + 1 == *size )
    {
      /**
       * Yes.  Double the size of the buffer using realloc.  
       * If realloc cannot satisfy the request, it will return 
       * NULL and leave the contents of buf unchanged.  Therefore,
       * we want to make sure we assign the result to
       * a temporary variable and check it, otherwise we
       * could potentially lose our reference to the
       * previously allocated memory, leading to a memory leak.
       */
      char *tmp = realloc( buf, sizeof *buf * (*size * 2));
      if ( tmp )
      {
        buf = tmp;
        *size *= 2;
      }
      else
      {
        fprintf( stderr, "Unable to extend buf, returning what we have so far\n");
        return buf;
      }
    }
    buf[i++] = c;
    buf[i] = 0; // zero terminate the string as we go
  }
  return buf;
}

int main( void )
{
  size_t bufsize;
  printf( "Gimme a string: ");
  char *str = getline( stdin, &bufsize );
  printf( "You entered: \"%s\"\n", str );
  printf( "length = %zu, buffer size = %zu\n", strlen( str ), bufsize);
  free( str );
  return 0;
}

还有一些示例运行:

john@marvin:~/Development/getline$ gcc -o getline -std=c11 -pedantic -Wall -Werror getline.c

john@marvin:~/Development/getline$ ./getline
Gimme a string: this
You entered: "this"
length = 4, buffer size = 16

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test
You entered: "this is a test"
length = 14, buffer size = 16

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of
You entered: "this is a test of"
length = 17, buffer size = 32

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of the emergency broadcast system.
You entered: "this is a test of the emergency broadcast system."
length = 49, buffer size = 64

john@marvin:~/Development/getline$ ./getline
Gimme a string: this is a test of the emergency broadcast system.  in the event of an actual emergency, you would be dead by now.  
You entered: "this is a test of the emergency broadcast system.  in the event of an actual emergency, you would be dead by now.  "
length = 115, buffer size = 128

推荐阅读