如何使用 C 逐行读取 txt 文件(int,未知长度)?

How to read the txt file line by line.(int, unknown length) using C?

我想对文本文件中的数据进行合并排序。(逐行)

为了让我的合并排序工作,我必须逐行读入数据,并将它们放入一个数组中以便对它们进行排序。 (我们只知道每行最多10000个整数) 所以我做了研究并尝试了这些方法:

1。使用 fgets 和 strtok/strtol.

问题:我不知道字符数组的最大长度。此外,声明一个巨大的数组可能会导致缓冲区溢出。

来源:How many chars can be in a char array?

2。使用 fscanf 输入整数数组。

问题:同上。我不知道一行中有多少个整数。所以我不会接受“%d”部分。(不知道应该有多少)

3。使用fscanf以char数组形式输入,使用strtok/strtol.

问题:同上。因为我不知道长度,所以我不能做类似

的事情
char *data;
data = malloc(sizeof(char) * datacount);

因为“数据计数”未知。

有什么办法吗?

更新

示例输入:

-16342 2084 -10049 10117 2786
3335 3512 -10936 5343 -1612 -4845 -14514

示例输出:

-16342 -10049 2084 2786 10117
-14514 -10936 -4845 -1612 3335 3512 5343

您确实可以使用 fscanf 来读取单个整数。除此之外,您需要了解的不仅是指针和 malloc but also about realloc.

你只需做类似

的事情
int temporary_int;
int *array = NULL;
size_t array_size = 0;

while (fscanf(your_file, "%d", &temporary_int) == 1)
{
    int *temporary_array = realloc(array, (array_size + 1) * sizeof(int));
    if (temporary_array != NULL)
    {
        array = temporary_array;
        array[array_size++] = temporary_int;
    }
}

在该循环之后,如果 array 不是空指针,那么它将包含文件中的所有整数,无论​​有多少。大小(元素数量)在 array_size 变量中。


看到更新后,更容易理解需要什么。

在 pseudo-code 中很简单:

while(getline(line))
{
    array_of_ints = create_array_of_ints();

    for_each(token in line)
    {
        number = convert_to_integer(token);
        add_number_to_array(array_of_ints, number);
    }

    sort_array(array_of_ints);
    display_array(array_of_ints);
}

实际上实现这个要困难得多,并且在某种程度上取决于您的环境(比如您是否可以访问 the POSIX getline function)。

如果你有例如 getline(或类似的函数),那么 pseudo-code 中的外部循环就很简单,并且看起来就像它已经做的一样。否则你基本上必须一个字符一个字符地读入你动态扩展的缓冲区(使用realloc)以适应整行。

这将我们带到外循环的内容:将输入拆分为一组值。您在此答案中的第一个 code-snippet 已经拥有的基本解决方案,我在其中根据需要在循环中重新分配数组。拆分值然后 strtok is probably the simplest one to use. And converting to an integer can be done with strtol (if you want validation) of atoi 如果您不关心验证您的输入。

请注意,您实际上不需要动态分配数组。 10000 int 值将在当前系统中 sizeof(int) == 4 为 "only" 40000 字节。它足够小,甚至可以放在大多数 non-embedded 系统的堆栈上。

你说:

We only know that there are at most 10000 integer per line

那就去吧。 standard-compliant 编译器(和环境)应该能够定义最多 65,535 个对象的数组。 10,000 远远不够,所以只需定义一个静态数组:

int a[10001], n;

int main() {
    // Open file
    n = 0;
    while (fscanf(fp, " %d", &a[n]) == 1) {
        // Process a[n]
        n++;
    }
}

如果您了解平台规范(如 sizeof(int) == 4),您可以假定整数长度。例如,32 位整数的最大长度为 11 个字符(-2147483648)左右。然后您可以定义一个具有计算长度的字符数组。

您没有考虑的一种方法可能是最便携的方法:编写您自己的 "read one token from a FILE stream, and convert it to a long; but never cross a newline boundary" 函数:

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

/* Reads one decimal integer (long) from 'input', saving it to 'to'.
   Returns: 0    if success,
            EOF  if end of input,
            '\n' if newline (end of line),
            '!'  if the number is too long, and
            '?'  if the input is not a number.
*/
int read_long(FILE *input, long *to)
{
    char    token[128], *end;
    size_t  n = 0;
    long    value;
    int     c;

    /* Consume leading whitespace, excluding newlines. */
    do {
        c = fgetc(input);
    } while (c == '\t' || c == '\v' || c == '\f' || c == ' ');

    /* End of input? */
    if (c == EOF)
        return EOF;

    /* Newline? */
    if (c == '\n' || c == '\r') {
        /* Do not consume the newline character! */
        ungetc(c, input);
        return '\n';
    }

    /* Accept a single '+' or '-'. */
    if (c == '+' || c == '-') {
        token[n++] = c;
        c = fgetc(input);
    }

    /* Accept a zero, followed by 'x' or 'X'. */
    if (c == '0') {
        token[n++] = c;
        c = fgetc(input);
        if (c == 'x' || c == 'X') {
            token[n++] = c;
            c = fgetc(input);
        }
    }

    /* Accept digits. */
    while (c >= '0' && c <= '9') {
        if (n < sizeof token - 1)
            token[n] = c;
        n++;
        c = fgetc(input);
    }

    /* Do not consume the separator. */
    if (c != EOF)
        ungetc(c, input);

    /* No token? */
    if (!n)
        return '?';

    /* Too long? */
    if (n >= sizeof token)
        return '!';

    /* Terminate token, making it a string. */
    token[n] = '[=10=]';

    /* Parse token. */
    end = token;
    errno = 0;
    value = strtol(token, &end, 0);
    if (end != token + n || errno != 0)
        return '?';

    /* Save value. */
    if (to)
        *to = value;

    return 0;
}

要前进到下一行,您可以使用例如

/* Skips the rest of the current line,
   to the beginning of the next line.
   Returns: 0 if success (next line exists, although might be empty)
          EOF if end of input.
*/
int next_line(FILE *input)
{
    int  c;

    /* Skip the rest of the current line, if any. */
    do {
        c = fgetc(input);
    } while (c != EOF && c != '\n' && c != '\r');

    /* End of input? */
    if (c == EOF)
        return EOF;

    /* Universal newline support. */
    if (c == '\n') {
        c = fgetc(input);
        if (c == EOF)
            return EOF;
        else
        if (c == '\r') {
            c = fgetc(input);
            if (c == EOF)
                return EOF;
        }
    } else
    if (c == '\r') {
        c = fgetc(input);
        if (c == EOF)
            return EOF;
        else
        if (c == '\n') {
            c = fgetc(input);
            if (c == EOF)
                return EOF;
        }
    }

    ungetc(c, input);
    return 0;
}

要读取每行的 long,您可以使用跨行共享的动态调整大小的缓冲区:

int main(void)
{
    long   *field_val = NULL;
    size_t  field_num = 0;
    size_t  field_max = 0;

    int     result;

    do {
        /* Process the fields in one line. */
        field_num = 0;

        do {

            /* Make sure the array has enough room. */
            if (field_num >= field_max) {
                void *temp;

                /* Growth policy; this one is linear (not optimal). */
                field_max = field_num + 5000;

                temp = realloc(field_val, field_max * sizeof field_val[0]);
                if (!temp) {
                    fprintf(stderr, "Out of memory.\n");
                    return EXIT_FAILURE;
                }

                field_val = temp;
            }

            result = read_long(stdin, field_val + field_num);
            if (result == 0)
                field_num++;

        } while (result == 0);

        if (result == '!' || result == '?') {
            fprintf(stderr, "Invalid input!\n");
            return EXIT_FAILURE;
        }

        /*
         * You now have 'field_num' longs in 'field_val' array.
        */

        /* Proceed to the next line. */
    } while (!next_line(stdin));

    free(field_val);
    field_val = NULL;
    field_max = 0;

    return EXIT_SUCCESS;
}

虽然逐字符读取输入并不是最有效的方式(它往往比逐行读取稍慢),但它的多功能性弥补了这一不足。

例如,上面的代码适用于任何换行约定(CRLF 或 \r\n、LFCR 或 \n\r、CR \r 和 LF \n)(但是在 Windows 中,您需要为 fopen() 指定 "b" 标志,以阻止它进行自己的换行符处理。

read-field-by-field 方法也很容易扩展到例如CSV 格式,包括其特有的引用规则,甚至嵌入换行符。