如何在不询问用户字符串大小的情况下仅使用 getchar 将可变大小的字符串作为输入?

How do I take a variable sized string as input using only getchar without asking user for string size before?

我只需要使用 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] = '[=10=]'; // terminate string with '[=10=]'
            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!

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

我认为这是你需要做的:

char *get_string( )
{
    char* buffer = (char*)malloc(sizeof(char));
    char c;
    int size = 0;

    c = getc(stdin);
    buffer[size++] = c;

    while( c != '\n')
    {
        c = getc(stdin);
        buffer = (char*)realloc(buffer, (size+1)*sizeof(char));

        if(buffer != NULL) // Check if space was re allocated
            buffer[size++] = c;
        else // If re allocation failed
            return NULL;
    }
    return buffer;
}

您首先创建一个大小为 1 的缓冲区并从标准输入读取第一个字符。 然后,白色下一个字符不是 \n:

  1. 读取下一个字符。

  2. 为下一个字符重新分配space(注意如果重新分配失败,realloc可以returnNULL,你必须检查) .

  3. 将当前字符添加到缓冲区。

你会想要使用 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