动态分配用户输入的字符串
我正在尝试编写一个执行以下操作的函数:
- 启动输入循环,每次迭代打印
'> '
。 - 取用户输入的任何内容(未知长度)并将其读入字符数组,必要时动态分配数组的大小。 用户输入的行将以换行符结束。
- 在字符数组的末尾添加一个空字节
'\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) == 1
到scanf("%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个不同值中的一个,介于0
和UCHAR_MAX
之间(包括0
和UCHAR_MAX
)。 否则它可以返回EOF
,这意味着fgetc
(或因此, getchar
)通常可以返回257个不同的值 。 尝试将这些值存储到char
或unsigned 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; }