【问题标题】:Reading an unknown number of lines with unknown length from stdin从标准输入读取未知数量的未知长度的行
【发布时间】:2017-10-09 22:57:27
【问题描述】:

我对 C 编程比较陌生,我正在尝试使用 fgets 读取来自 stdin 的输入。

一开始我考虑阅读最多 50 行,每行最多 50 个字符,并且有类似的内容:

int max_length = 50;
char lines[max_length][max_length];
char current_line[max_length];
int idx = 0;

while(fgets(current_line, max_length, stdin) != NULL) {
    strcopy(lines[idx], current_line);
    idx++;
}

上面的 sn-p 成功读取输入并将其存储到 lines 数组中,我可以在其中进行排序和打印。

我的问题是如何处理未知数量的行,每行有未知数量的字符? (请记住,我必须对这些行进行排序并打印出来)。

【问题讨论】:

  • malloc()/realloc() 与一个明智的策略一起使用(例如,当你用完时双倍大小)...检查换行符以了解该行是否完整。我觉得这个问题太笼统了……
  • 尝试首先编写其中一个目标“行数未知,每行字符数未知”。避免使这篇文章过于宽泛。
  • 对于未知数量的行,您可以使用列表来保留它们。对于未知数量的字符,您可能需要每行多次运行 fgets(如果文件中的行长于 max_length)并使用 malloc/realloc 构建您的行,就像在 Felix 的回答中一样。只需使用 'max_length' 作为增量。

标签: c io stdin fgets


【解决方案1】:

虽然已经回答了这个问题的许多不同变体,但如何解决这个问题的考虑可以使用一个段落。遇到此问题时,无论您使用哪种库或 POSIX 函数组合,方法都是相同的。

基本上,您将动态分配合理数量的字符来容纳每一行。 POSIX getline 将自动为您执行此操作,使用 fgets 您可以简单地读取一个充满字符的固定缓冲区并附加它们(根据需要重新分配存储),直到读取 '\n' 字符(或达到 EOF

如果你使用getline,那么你必须分配内存,并复制填充的缓冲区。否则,您将在读取每个新行时覆盖以前的行,并且当您尝试free 每一行时,您可能会在反复尝试释放同一块时出现 double-free or corruption 的 SegFault内存。

您可以使用strdup 简单地复制缓冲区。但是,由于strdup 分配存储空间,您应该先验证分配是否成功,然后再将指向新内存块的指针分配给您的行集合。

要访问每一行,您需要一个指向每一行开头的指针(保存每一行的内存块)。通常使用 指向 char 指针的指针。 (例如char **lines;)内存分配通常通过分配一些合理数量的指针开始处理,跟踪您使用的数字,当您达到分配的数字时,您realloc并将指针数量加倍.

与每次读取一样,您需要验证每个内存分配。 (每个malloccallocrealloc)您还需要通过内存错误检查程序(例如Linux 的valgrind)运行程序来验证您的程序使用您分配的内存的方式。它们使用简单,只需valgrind yourexename

将这些部分放在一起,您可以执行类似于以下的操作。以下代码将从作为程序的第一个参数提供的文件名中读取所有行(如果没有提供参数,则默认情况下从 stdin 读取)并将行号和行打印到 stdout(如果您在 50,000 行文件上运行它)

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

#define NPTR 8

int main (int argc, char **argv) {

    size_t ndx = 0,             /* line index */
        nptrs = NPTR,           /* initial number of pointers */
        n = 0;                  /* line alloc size (0, getline decides) */
    ssize_t nchr = 0;           /* return (no. of chars read by getline) */
    char *line = NULL,          /* buffer to read each line */
        **lines = NULL;         /* pointer to pointer to each line */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* allocate/validate initial 'nptrs' pointers */
    if (!(lines = calloc (nptrs, sizeof *lines))) {
        fprintf (stderr, "error: memory exhausted - lines.\n");
        return 1;
    }

    /* read each line with POSIX getline */
    while ((nchr = getline (&line, &n, fp)) != -1) {
        if (nchr && line[nchr - 1] == '\n') /* check trailing '\n' */
            line[--nchr] = 0;               /* overwrite with nul-char */
        char *buf = strdup (line);          /* allocate/copy line */
        if (!buf) {     /* strdup allocates, so validate */
            fprintf (stderr, "error: strdup allocation failed.\n");
            break;
        }
        lines[ndx++] = buf;     /* assign start address for buf to lines */
        if (ndx == nptrs) {     /* if pointer limit reached, realloc */
            /* always realloc to temporary pointer, to validate success */
            void *tmp = realloc (lines, sizeof *lines * nptrs * 2);
            if (!tmp) {         /* if realloc fails, bail with lines intact */
                fprintf (stderr, "read_input: memory exhausted - realloc.\n");
                break;
            }
            lines = tmp;        /* assign reallocted block to lines */
            /* zero all new memory (optional) */
            memset (lines + nptrs, 0, nptrs * sizeof *lines);
            nptrs *= 2;         /* increment number of allocated pointers */
        }
    }
    free (line);                    /* free memory allocated by getline */

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < ndx; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);            /* free memory for each line */
    }
    free (lines);                   /* free pointers */

    return 0;
}

如果您没有getlinestrdup,您可以轻松实现每一个。网站上有多个示例。如果找不到,请告诉我。如果您还有其他问题,也请告诉我。

【讨论】:

    【解决方案2】:

    查看 here 找到的 GetString 函数。

    【讨论】:

    • 完全不是答案,而是评论。
    • 他必须有 20 个代表才能发表评论,所以他通过了,但他明白当他得到 20 个代表时,cmets 作为答案而不合适。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-04
    • 2017-03-18
    相关资源
    最近更新 更多