【问题标题】:Dynamically allocate user inputted string动态分配用户输入的字符串
【发布时间】:2015-07-28 02:15:26
【问题描述】:

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

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

【问题讨论】:

  • 您不能使用strlen 来获取最后一个索引,假设您的字符串末尾没有空值。如果它最后确实有一个空值,那你为什么要重新添加它?
  • input 错字为str

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


【解决方案1】:

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

但是你有一个更大的问题。 scanf 期望您传递给它一个先前分配的空缓冲区,以便它用读取的输入填充。它不会为您分配 sctring,因此您尝试将 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 读取整行输入。

【讨论】:

  • 我不太确定如何使用fgets。对我来说,这似乎是一种混乱之王。你能像我五岁一样向我解释吗?编辑:另外,我怎样才能将它合并到我的输入循环中?
  • 您只需根据需要预先分配一个具有足够空间的字符串。对于输入,根据您的需要选择一个足够大的数字,例如 100、256、512 等。 fgets 采用 3 个参数:放置字符串的位置、字符串的最大长度 + 它自动附加的 null 以及从中读取输入的位置。对于输入,您可以指定stdin 从控制台或文件句柄(如果有)读取。例如,如果你有一个char str[512];,你会打电话给fgets(str, 512 - 1, stdin);。大小上的 -1 表示空值。 fgets 失败时返回 null。
  • @ozdrgnaDiies fgets 是否在字符串末尾添加空字节?
  • @PythonNewb 是的。你可以在这里阅读更多信息:cplusplus.com/reference/cstdio/fgets
  • @ozdrgnaDiies:次要观点:您不必为空字节腾出空间; fgets 会为您解决这个问题。 fgets(str, sizeof(str), stdin); 在你的例子中应该很好。
【解决方案2】:

没有错误检查,完成后不要忘记释放指针。如果您使用此代码阅读大量行,那么您应该承受它给您带来的所有痛苦。

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

【讨论】:

  • 要使用这个的人要注意,如果realloc失败,buf会丢失,内存会泄露。您应该将realloc 的结果分配给一个临时指针以进行错误检查,然后重新分配它。
  • @ozdrgnaDiies:这取决于你想在失败时做什么。您是否返回截断的行?如果是这样,调用代码将如何知道?通常,人们只是退出并退出程序。这里不需要额外的工作。
  • 一个可能的调整是使用l *= SOME_CONSTANT_FACTOR 而不是l += 256。如果输入行很长,这样可以避免二次运行时间。
  • getchar 将在成功时返回一个介于 0 和 UCHAR_MAX(含)之间的值(通常是 256 个值之一),或 EOF(使总数达到 257 个值之一,通常)当它指示失败时。如果 ch 通常无法存储 257 个不同值之一,那么您将面临无法识别何时 EOF 或错误已被标记的风险......这并不重要,因为您的循环不会努力检查反正。我强烈建议首先将 ch 更改为 int(正如手册中所建议的那样)。
【解决方案3】:

如果您在 Linux 等 POSIX 系统上,您应该可以访问getline。它可以表现得像fgets,但如果你以一个空指针和零长度开始,它会为你处理内存分配。

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

#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 该行字符串只有一次,当你完成阅读时。

【讨论】:

    【解决方案4】:

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

    • 当线条过大时会发生什么,以及
    • EOF 或遇到错误时会发生什么。

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

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

    我觉得不需要过多强调检查返回值的重要性,所以不再赘述。可以这么说,如果您的程序不检查返回值,您的程序将不知道何时EOF 或发生错误;您的程序可能会陷入无限循环。

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

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

    hugomg 提到在动态调整大小代码中使用乘法来避免二次运行时问题。沿着这条线,最好避免在每次迭代中解析相同的数据(从而引入更多的二次运行时问题)。这可以通过在某处存储您已读取(和解析)的字节数来实现。例如:

    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 程序员在忽略 the fgetc manual 时也会错误地使用 fgetc

    最重要的细节是要意识到fgetc 返回的是int,而不是char。它可能返回通常介于 0UCHAR_MAX(含)之间的 256 个不同值之一。它可能返回EOF,这意味着通常有257个不同的值fgetc(或因此,getchar)可能返回。尝试将这些值存储到 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-17
      • 2021-07-03
      • 2020-08-23
      • 2018-01-04
      • 2023-03-10
      • 2018-03-25
      • 2017-08-15
      • 1970-01-01
      相关资源
      最近更新 更多