将文本文件读入C中的2D数组

时间:2018-10-20 00:39:12

标签: c

我正在尝试将整个文本文件读取为2D数组,因此我可以限制可以存储的文本量并知道何时进行换行(如果有人有更好的主意,我愿意建议)。

这是我到目前为止所拥有的:

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

    char texto[15][45];
    char ch;
    int count = 0;
    FILE *f = fopen("texto.txt", "r");

    if(f == NULL)
        printf("ERRO ao abrir o ficheiro para leitura");

    while((ch = fgetc(f) != EOF))
        count++;

    rewind(f);

    int tamanho = count;

    texto = malloc(tamanho *sizeof(char));

    fscanf(f, "%s", texto);

    fclose(f);

    printf("%s", texto);

    return (EXIT_SUCCESS);
}

文本文件就是这样

lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip
lorem ipsum lorem ipsum lorem ipsum lorem ip

但是我得到这个错误

  

错误:分配给具有数组类型的表达式

这里

  

texto = malloc(tamanho * sizeof(char));

2 个答案:

答案 0 :(得分:2)

您要解决的问题之一是迫使您了解面向字符的输入格式化输入 line-之间的区别和局限性定向输入。您将数组限制设置为:

char texto[15][45];

上面声明了一个15-1D数组,每个数组包含45个字符,这些字符在内存中将是连续的(array的定义)。这意味着,在每个索引texto[0] - texto[14]处,您最多可以存储45个字符(或 string 44个字符,后跟无符号终止字符)。

然后将为您提供一个文件,每个文件包含七行45个字符。 但是每行中只有44个字符吗? –错误。由于(假定给定"texto.txt")信息被保存在文本文件中,因此每行末尾将有一个额外的'\n'(换行符)字符。您在读取文件时必须考虑它的存在。文件中的每一行将类似于以下内容:

        10        20        30        40
123456789012345678901234567890123456789012345
lorem ipsum lorem ipsum lorem ipsum lorem ip\n

(数字只是代表显示每行中有多少个字符的比例尺)

ASCII '\n'字符是单个字符。

格式化输入法

您可以使用fscanf 转化说明符使用"%s"读取输入吗? (回答:否)为什么? "%s"转换说明符在读取非空白字符后遇到第一个空白字符时停止读取。这意味着使用fscanf (fp, "%s", ...)进行阅读将在第5个字符之后停止阅读。

尽管您可以使用[...]形式的字符类转换说明符对此进行补救,其中括号包含要包含的字符(如果类中的第一个字符是'^'),您将'\n'字符未读留在输入流中。

尽管您可以通过使用'*'分配抑制字符来读取并丢弃带有"%*c"的下一个字符(换行符)来进行补救,但是如果该行中还有其他字符,它们也可以将保留在输入缓冲区(输入流,例如您的文件)中未读

您是否开始了解使用scanf系列函数进行文件输入时固有的脆弱性? (你会是对的)

使用fscanf的幼稚实现可能是:

#include <stdio.h>

#define NROWS 15    /* if you need a constant, #define one (or more) */
#define NCOLS 45

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

    char texto[NROWS][NCOLS] = {""};
    size_t n = 0;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    /* read up to NROWS lines of 44 char each with at most 1 trailing char */
    while (n < NROWS && fscanf (fp, "%44[^\n]%*c", texto[n]) == 1)
        n++;    /* increment line count */

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

    for (size_t i = 0; i < n; i++)  /* output lines stored */
        printf ("texto[%2lu]: '%s'\n", i, texto[i]);

    return 0;
}

注意:,如果您可以保证输入文件的格式是固定的,并且永远不变,那么这可能是一种适当的方法。但是,文件中的一个额外的杂散字符可能会破坏这种方法。 )

使用/输出示例

$ ./bin/texto2dfscanf <dat/texto.txt
texto[ 0]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 1]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 2]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 3]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 4]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 5]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'
texto[ 6]: 'lorem ipsum lorem ipsum lorem ipsum lorem ip'

行输入

更好的方法始终是面向行的方法。为什么?它使您可以分别验证从文件(或用户)读取的一行数据,然后验证从该行分析必要的信息。

但是texto的大小上有一个故意的问题,使简单的面向行方法变得复杂。尽管您可能会试图将文本的每一行简单地读入texto[0-14],但是您只会将文本读入texto而未读'\n'。 (什么?我认为面向行的输入可以解决此问题?-如果您在要填充的缓冲区中提供足够的空间,它会这样做...)

面向行的输入函数(fgets和POSIX getline)读取并将尾随'\n'包含到要填充的缓冲区中-只要足够空间。如果使用fgets,则fgets将读取的字符数不超过在缓冲区中指定的字符数(这可以保护数组边界)。您在此处的任务被设计为要求使用面向行的功能读取46个字符才能读取:

the text + '\n' + '\0'

(文本加上换行符加上可终止字符的字符)

这将迫使您正确地进行面向行的输入。将信息读取到足够大的缓冲区中,以处理最大的预期输入线(不要跳过缓冲区大小)。验证您的读取成功。然后使用您选择的任何方式从行中解析所需的信息(在这种情况下,sscanf很好)。通过分两步进行操作,您可以读取行,确定读取的行的原始长度(包括'\n')并验证其是否都适合您的缓冲区。然后,您可以解析44字符(加上 nul-终止字符的空间)。

此外,如果还有其他字符未读,那么您会先知道该信息,然后可以连续读取并丢弃其余字符,为您的下一次阅读做准备。

一种合理的面向行的方法可能类似于以下内容:

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

#define NROWS 15    /* if you need a constant, #define one (or more) */
#define NCOLS 45
#define MAXC  1024

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

    char texto[NROWS][NCOLS] = {""},
        buffer[MAXC] = "";
    size_t n = 0;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (n < NROWS && fgets (buffer, MAXC, fp)) {
        size_t len = strlen (buffer);
        if (len && buffer[len-1] == '\n')
            buffer[--len] = 0;
        else
            if (len == MAXC-1) {
                fprintf (stderr, "error: line %zu too long.\n", ++n);
                /* remove remaining chars in line before next read */
                while (fgets (buffer, MAXC, fp)) {}
            }
        if (sscanf (buffer, "%44[^\n]", texto[n]) == 1)
            n++;
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < n; i++)  /* output lines stored */
        printf ("texto[%2zu]: '%s'\n", i, texto[i]);

    return 0;
}

(输出相同)

面向字符的输入

剩下的唯一方法是面向字符的方法(这是一种逐字符读取文件的非常有效的方法)。面向字符的方法的唯一挑战是逐个字符地跟踪索引。这里的方法很简单。只需反复调用fgetc来填充texto中的可用字符,然后丢弃该行中的所有其他字符,直到到达'\n'EOF。与在适当情况下面向行的方法相比,它实际上可以提供一种更简单但同样可靠的解决方案。我将继续研究这种方法。

C中任何输入任务中的键都将正确的工具与作业匹配。如果确保输入文件具有不会偏离的固定格式,则 formatted-input 可能有效。对于所有其他输入(包括用户输入),通常建议使用面向行的输入,因为它可以读取整行,而不会使输入缓冲区中的'\n'悬挂在未读状态—如果您使用足够大小的缓冲区。始终可以使用面向字符的输入,但是您面临着另一个挑战,即逐个字符地跟踪索引。结合使用这三种方法,才能加深对哪种工具的理解,这才是工作的最佳工具。

仔细检查一下,如果还有其他问题,请告诉我。

答案 1 :(得分:0)

您要在固定数组上使用malloc进行分配,这是不可能的,因为它已经具有固定大小。您应该将texto定义为char*才能使用mallocmalloc的目的是分配内存,在固定数组上分配内存-不可能。

以下是如何以2D数组读取文本文件的示例:

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

int main(int argc, char** argv) {
    char texto[256][256]; // 256 - Big enough array, or use malloc for dynamic array size
    char ch;
    int count = 0;
    FILE *f = fopen("texto.txt", "r");

    if(f == NULL)
        printf("ERRO ao abrir o ficheiro para leitura");

    while((ch = fgetc(f) != EOF)) {
        count++;
        // rewind(f);
        int tamanho = count;
        // texto[count] = malloc(tamanho *sizeof(char));
        fscanf(f, "%s", &texto[count]);
    }
    // Now lets print all in reverse way.
    for (int i = count; i != 0; i--) {
        printf("%s, ", texto[i]);
    }
    return (0);
}

输出:

  

ip,lorem,ipsum,lorem,ipsum,lorem,ipsum,lorem,ip,lorem,ipem,lorem,ipsum,lorem,ipsum,lorem,ip,lorem,ipsum,lorem,ipsum,lorem,ipsum,lorem ip,lorem,ipsum,lorem,ipsum,lorem,ipsum,lorem,ip,lorem,ipsum,lorem,ipsum,lorem,ipsum,lorem,ip,lorem,ipsum,lorem,ipsum,lorem,ipsum,lorem,ipem ,lorem,ipsum,lorem,ipsum,lorem,ipsum,orem,