簡體   English   中英

將文本文件讀入C中的2D數組

[英]Read a text file into a 2D array in 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));

您面臨的問題是迫使您了解面向字符的輸入格式化輸入面向行的輸入之間的區別和局限性之一。 您將數組限制設置為:

char texto[15][45];

上面聲明了一個由15-1D數組組成的數組,每個數組包含45個字符,它們在內存中將是順序的( array的定義)。 這意味着在每個索引texto[0] - texto[14]您最多可以存儲45字符(或44字符的字符串 ,后跟一個以零結尾的字符)。

然后,您將得到一個由7行組成的文件,每行45字符。 但是每行只有44字符嗎? -錯誤。 由於(假定給定為"texto.txt" ),信息被保存在文本文件中 ,因此每行末尾將有一個附加的'\\n' (換行符)字符。 您在讀取文件時必須考慮它的存在。 文件中的每一行將類似於以下內容:

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

(其中數字僅代表顯示每行中存在多少個字符的比例)

ASCII '\\n'字符是一個單字符。

格式化輸入法

您可以使用"%s" 轉換說明符通過fscanf讀取輸入嗎? (回答:否)為什么? 讀取非空白字符后,遇到第一個空白字符時, "%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' getline如果有足夠的空間。 如果使用fgets ,則fgets讀取的字符數將不超過緩沖區中指定的字符數(緩沖區保護數組邊界)。 您在這里的任務被設計為要求讀取46字符,並且具有面向行的功能才能讀取:

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

(文本加上換行符再加上終止符)

這迫使您正確進行面向行的輸入。 將信息讀取到足夠大的緩沖區中,以處理最大的預期輸入線(不要跳過緩沖區大小)。 驗證您的讀取成功。 然后使用您選擇的任何方式從行中解析您需要的信息(在這種情況下, sscanf很好)。 通過分兩步進行操作,您可以讀取行,確定讀取的行的原始長度(包括'\\n' )並驗證其是否都適合您的緩沖區。 然后,您可以解析44字符(加上以字符結尾的空間 )。

此外,如果還有其他字符未讀,則您可以預先知道該字符,然后可以連續讀取並丟棄其余字符,為下一次讀取做准備。

合理的面向行的方法可能類似於以下內容:

#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語言中任何輸入任務中的鍵都將正確的工具集與作業匹配。 如果可以確保輸入文件具有不會偏離的固定格式,那么格式化輸入可能是有效的。 對於所有其他輸入(包括用戶輸入),通常建議使用面向行的輸入,因為它能夠讀取整行而不會在輸入緩沖區中留下未懸掛的'\\n'懸掛狀態-前提是您使用了足夠大小的緩沖區。 始終可以使用面向字符的輸入,但是您面臨着另一個挑戰,即逐個字符地跟蹤索引。 使用這三種方法是唯一了解哪種工具是工作的最佳工具的唯一方法。

仔細檢查一下,如果您還有其他問題,請告訴我。

您將在固定數組上使用malloc進行分配,這是不可能的,因為它已經具有固定大小。 您應該將texto定義為char*以便使用malloc malloc的目的是分配內存,在固定陣列上分配內存-不可能。

這是如何以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,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,orem,

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM