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

时间:2015-05-16 03:49:39

标签: c arrays user-input scanf dynamic-memory-allocation

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

  • 启动输入循环,每次迭代打印'> '
  • 取用户输入的任何内容(未知长度)并将其读入字符数组,必要时动态分配数组的大小。用户输入的行将以换行符结尾。
  • 在字符数组的末尾添加一个空字节'\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会给我带来同样的错误。

4 个答案:

答案 0 :(得分:2)

首先,scanf格式字符串不使用正则表达式,所以我不认为接近你想要的东西会起作用。至于您得到的错误according to my trusty manual%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读取整行输入。

答案 1 :(得分:1)

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

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

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);
}

答案 2 :(得分:1)

如果您使用的是POSIX系统(如Linux),则应该可以访问getline。可以使其行为类似于fgets,但如果以空指针和零长度开头,它将为您处理内存分配。

你可以像这样使用in:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>    // 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一行字符串。

答案 3 :(得分:1)

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

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

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

     

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

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

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

这允许您'\n'存储并再次调用realloc,如果您想动态调整存储大小,或丢弃该行的其余部分(警告用户截断是个好主意) ,也许使用像fgets这样的东西。

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

fscanf(file, "%*[^\n]");

那些设法阅读本手册并提出正确的内容(如此)的人很快就会发现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似乎是最合适的。大多数C程序员在忽略the fgetc manual时也设法错误地使用fgetc

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

UCHAR_MAX