动态分配用户输入的字符串

我正在尝试编写一个执行以下操作的函数:

  • 启动输入循环,每次迭代打印'> '
  • 取用户输入的任何内容(未知长度)并将其读入字符数组,必要时动态分配数组的大小。 用户输入的行将以换行符结束。
  • 在字符数组的末尾添加一个空字节'\0'
  • 当用户输入一个空行时,循环终止: '\n'

这是我目前写的:

 void input_loop(){ char *str = NULL; printf("> "); while(printf("> ") && scanf("%a[^\n]%*c",&input) == 1){ /*Add null byte to the end of str*/ /*Do stuff to input, including traversing until the null byte is reached*/ free(str); str = NULL; } free(str); str = NULL; } 

现在,我不太清楚如何将空字节添加到字符串的末尾。 我在想这样的事情:

 last_index = strlen(str); str[last_index] = '\0'; 

但我不太确定这是否会奏效。 我无法测试它是否可行,因为我在尝试编译代码时遇到此错误:

 warning: ISO C does not support the 'a' scanf flag [-Wformat=] 

那么我该怎么做才能让我的代码工作呢?

编辑:更改scanf("%a[^\n]%*c",&input) == 1scanf("%as[^\n]%*c",&input) == 1给出了同样的错误。

首先,scanf格式字符串不使用正则表达式,所以我不认为接近你想要的东西会起作用。 至于你得到的错误, 根据我可靠的手册 , %a转换标志是浮点数,但它只适用于C99(你的编译器可能配置为C90)

但是你有一个更大的问题。 scanf期望您传递一个先前分配的空缓冲区,以便用读取输入填充它。 它不会为你的sctring malloc,所以你尝试初始化str为NULL和相应的frees将无法使用scanf。

你能做的最简单的事就是放弃n个行长字符串。 创建一个大缓冲区并禁止长于此的输入。

然后,您可以使用fgets函数填充缓冲区。 要检查它是否设法读取整行,请检查字符串是否以“\ n”结尾。

 char str[256+1]; while(true){ printf("> "); if(!fgets(str, sizeof str, stdin)){ //error or end of file break; } size_t len = strlen(str); if(len + 1 == sizeof str){ //user typed something too long exit(1); } printf("user typed %s", str); } 

另一种选择是您可以使用非标准库函数。 例如,在Linux中有getline函数,它在后台使用malloc读取整行输入。

没有错误检查,不要忘记在完成指针后释放指针。 如果你使用这段代码阅读巨大的线条,你应该得到它带给你的所有痛苦。

 #include  #include  char *readInfiniteString() { int l = 256; char *buf = malloc(l); int p = 0; char ch; ch = getchar(); while(ch != '\n') { buf[p++] = ch; if (p == l) { l += 256; buf = realloc(buf, l); } ch = getchar(); } buf[p] = '\0'; return buf; } int main(int argc, char *argv[]) { printf("> "); char *buf = readInfiniteString(); printf("%s\n", buf); free(buf); } 

如果您使用的是POSIX系统(如Linux),则应该可以访问getline 。 它可以像fgets一样运行,但是如果你以空指针和零长度开始,它将为你处理内存分配。

你可以在这样的循环中使用:

 #include  #include  #include  // for strcmp int main(void) { char *line = NULL; size_t nline = 0; for (;;) { ptrdiff_t n; printf("> "); // read line, allocating as necessary n = getline(&line, &nline, stdin); if (n < 0) break; // remove trailing newline if (n && line[n - 1] == '\n') line[n - 1] = '\0'; // do stuff printf("'%s'\n", line); if (strcmp("quit", line) == 0) break; } free(line); printf("\nBye\n"); return 0; } 

传递的指针和长度值必须一致,以便getline可以根据需要重新分配内存。 (这意味着你不应该改变循环中的nline或指针line 。)如果行适合,则每次循环使用相同的缓冲区,这样你只需要free一次行字符串,当你'读完了。

有人提到scanf可能不适用于此目的。 我也不建议使用fgets 。 虽然它稍微适合一些,但至少在开始时有些问题似乎难以避免。 很少有C程序员能够在没有完全阅读fgets手册的情况fgets一次正确使用fgets 。 大多数人完全忽视的部分是:

  • 当线太大时会发生什么
  • 遇到EOF或错误时会发生什么。

fgets()函数必须从stream读取字节到s指向的数组,直到读取n-1个字节,或者读取a并将其传送到s ,或者遇到文件结束条件。 然后以空字节终止该字符串。

成功完成后, fgets()将返回s 。 如果流位于文件结尾,则应设置流的文件结束指示符,并且fgets()应返回空指针。 如果发生读错误,则应设置流的错误指示符, fgets()应返回空指针…

我觉得我不需要强调太多检查返回值的重要性,所以我再也不提了。 可以说,如果你的程序没有检查返回值,你的程序在EOF或错误发生时就不会知道; 你的程序可能会陷入无限循环。

当没有'\n' ,该行的剩余字节尚未被读取。 因此, fgets将始终在内部至少解析一次该行。 当您引入额外的逻辑时,要检查'\n' ,那么您将再次解析数据。

这允许您重新分配存储并再次调用fgets ,如果您想动态调整存储大小,或丢弃该行的其余部分(警告用户截断是一个好主意),可能使用类似fscanf(file, "%*[^\n]");

hugomg提到在动态resize代码中使用乘法来避免二次运行时问题。 沿着这条线,避免在每次迭代中反复解析相同的数据(因此引入进一步的二次运行时问题)是一个好主意。 这可以通过存储您在某处读取(和解析)的字节数来实现。 例如:

 char *get_dynamic_line(FILE *f) { size_t bytes_read = 0; char *bytes = NULL, *temp; do { size_t alloc_size = bytes_read * 2 + 1; temp = realloc(bytes, alloc_size); if (temp == NULL) { free(bytes); return NULL; } bytes = temp; temp = fgets(bytes + bytes_read, alloc_size - bytes_read, f); /* Parsing data the first time */ bytes_read += strcspn(bytes + bytes_read, "\n"); /* Parsing data the second time */ } while (temp && bytes[bytes_read] != '\n'); bytes[bytes_read] = '\0'; return bytes; } 

那些设法阅读手册并提出正确的东西(如此)的人很快就会意识到fgets解决方案的复杂性至少是使用fgetc的同一解决方案的两倍。 我们可以避免使用fgetc第二次解析数据,因此使用fgetc似乎是最合适的。 唉大多数C程序员在忽略fgetc手册时也设法错误地使用fgetc

最重要的细节是要意识到fgetc返回一个int ,而不是一个char 。 它通常可以返回256个不同值中的一个,介于0UCHAR_MAX之间(包括0UCHAR_MAX )。 否则可以返回EOF ,这意味着fgetc (或因此, getchar )通常可以返回257个不同的值 。 尝试将这些值存储到charunsigned char中会导致信息丢失,尤其是错误模式。 (当然,如果CHAR_BIT大于8,则此典型值257将更改,因此UCHAR_MAX大于255)

 char *get_dynamic_line(FILE *f) { size_t bytes_read = 0; char *bytes = NULL; do { if ((bytes_read & (bytes_read + 1)) == 0) { void *temp = realloc(bytes, bytes_read * 2 + 1); if (temp == NULL) { free(bytes); return NULL; } bytes = temp; } int c = fgetc(f); bytes[bytes_read] = c >= 0 && c != '\n' ? c : '\0'; } while (bytes[bytes_read++]); return bytes; }