从txt文件(csv)填充2d数组时出现问题

时间:2020-04-18 04:17:59

标签: c arrays

我正在通过从文本文件中读取来填充2d数组,各项之间用逗号分隔。 我尝试了两种方法,但遇到了一些问题。

第一种方法:

使用strtok(我已经读过,我应该避免这样做,所以我strcpy会将读取的原始字符串复制到另一个字符串中)我使用逗号作为分隔符。第一个问题是程序崩溃,除非我在正在读的单词之间添加额外的空格。所以我添加了空格并且它可以正常工作,它可以读取所有内容,并且我可以打印以检查其是否已添加到2d数组中。完成数组填充后,我嵌套了for循环以进行打印,并且由于某种原因,二维数组中的所有内容都已替换为从txt文件读取的最后内容。所以我的问题是如何使strtok不需要多余的空间,以及数组由于某种原因而被覆盖,当我第一次填充并打印它时,似乎填充正确了。

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

int main(int argc, char *argv[])
{
    FILE *fp;
    char text[20], *token;
    char word[20];
    const char delimiters[] = ",";
    char *table[8][8];
    int i = 0;
    int j = 0;

    fp = fopen("board.txt", "r");
    if (fp == NULL)
    {
        printf("Error opening");
    }
    printf("\n\n");
    while (fscanf(fp, "%15s", text) != EOF)
    {
        strcpy(word, text);
        token = strtok(word, delimiters);

        table[i][j] = token;
        //pritn table values as they get added
        printf("table[%d][%d] = %s ", i, j, table[i][j]);

        //ghetto nested for loop
        j++;
        if (j >= 8)
        {
            i++;
            j = 0;
            printf("\n");
        }
    }

    printf("\n\n\ntable[0][3] = %s|", table[0][3]);
    printf("\n");

    for (i = 0; i < 8; i++)
    {
        //printf("\n");
        for (j = 0; j < 8; j++)
        {
            printf("table[%d][%d] = %s|", i, j, table[i][j]);
        }
        printf("\n");
    }
    return 0;
}

这是我从文本文件中读取的数据

-4,-2,-3,-5,-6,-3,-2,-4
-1,-1,-1,-1,-1,-1,-1,-1
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
+1,+1,+1,+1,+1,+1,+1,+1
+4,+2,+3,+5,+6,+3,+2,+100

但是如果我不添加这样的空格,它将崩溃

-4, -2, -3, -5, -6, -3, -2, -4
-1, -1, -1, -1, -1, -1, -1, -1
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
+1, +1, +1, +1, +1, +1, +1, +1
+4, +2, +3, +5, +6, +3, +2, +100

第二种方法:

我正在一次从txt文件读取每个字符,如果它检测到逗号,则会将所有先前的字符添加为字符串,移至下一个字符,并一直重复直到EOF。使用这种方法,我没有需要多余空间的问题,但是代码的问题是,每当行到达行尾时,它都会添加2项而不是1项,因此现在所有内容都从此转移了。这发生在每一行的末尾,所以当完成所有操作时,我会丢失nRows项。

通过这种方法,我还遇到了与第一种方法相同的问题,即似乎用从文本文件读取的最后一个值覆盖了所有内容。与此相关的一个小问题是,由于其工作方式是通过检测逗号,因此它在知道一个单词之前就知道了所有内容,当我到达文件中的最后一个值时,除非添加逗号,否则不会将其写入数组。我正在通过添加逗号来解决它,但它不是文件的一部分,所以我不应该使用它。

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

int main()
{
    FILE *fp;
    char text[20];
    char *table[8][8] = {0};
    char word[30];
    //char *table[8][8];
    int i = 0;
    int j = 0;

    fp = fopen("board.txt", "r");
    if (fp == NULL)
    {
        printf("Error opening");
    }

    int word_i = 0;
    int c;
    while ((c = fgetc(fp)) != EOF)
    {
        if (c == ',')
        {
            //separate words with commas
            if (word_i > 0)
            {                                        
                text[word_i] = '\0';

                // strcpy(word, text);
                // table[i][j] = word;

                table[i][j] = text;
                printf("table[%d][%d] = %s |\t", i, j, table[i][j]);
                j++;

                if (j >= 8)
                {
                    i++;
                    j = 0;
                }
            }
            word_i = 0;
        }
        else
        {
            text[word_i] = c;
            ++word_i;
        }
    }

    printf("\n\n");
    //want to check that i manually modified table[0][0]=124
    for (i = 0; i < 8; i++)
    {
        //printf("\n");
        for (j = 0; j < 8; j++)
        {
            printf("table[%d][%d] = %s|", i, j, table[i][j]);
        }
        printf("\n");
    }
    return 0;
}

使用此代码,我必须在文本文件的末尾添加一个逗号,以便它读取最后一个值

-4,-2,-3,-5,-6,-3,-2,-4
-1,-1,-1,-1,-1,-1,-1,-1
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
+1,+1,+1,+1,+1,+1,+1,+1
+4,+2,+3,+5,+6,+3,+2,+100,

如果需要,我可以发布获得的输出。

非常感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

继续@JohathanLeffler的评论,使用面向 line 的输入函数一次读取一行数据,例如fgets()或POSIX getline()确保每次读取文件时都消耗一行输入。然后,您只需从保存文件中数据行的缓冲区中解析逗号分隔的值即可。

有多种方法可以分隔每个逗号分隔的值(每个值都有变体,具体取决于您要保留还是丢弃字段周围的空白)。您始终可以使用 start_pointer end-pointer 移动end_pointer来定位下一个',',然后复制字符( token )从start_pointerend_pointer,然后设置start_pointer = ++end_pointer并重复直到到达缓冲区末尾。

如果您没有空字段(意味着您的数据没有相邻的','分隔符,例如-4,-2,,-5,...),则使用strtok()是将缓冲区拆分为< em>令牌。如果您有空字段,那么如果您的编译器提供了BSD strsep(),它将处理空字段,或者简单地使用strcspn()strspn()的组合(或者在单个字段的情况下)使用','代替strchr()分隔符)将使您自动通过缓冲区自动完成行走一对指针。

使用strtok()来将每行分成令牌(从stdin读取文件)的非常简单的实现是:

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

#define MAXC 1024

int main (void) {

    char buf[MAXC];                         /* buffer to hold each line */

    while (fgets (buf, MAXC, stdin)) {      /* read each line into buf */
        /* split buf into tokens using strtok */
        for (char *tok = strtok (buf, ","); tok; tok = strtok (NULL, ",")) {
            tok[strcspn (tok, "\n")] = 0;   /* trim '\n' from end tok */
            /* output board (space before if not 1st) */
            printf (tok != buf ? " %s" : "%s", tok);
        }
        putchar ('\n');
    }
}

注意:printf一起使用简单的三元运算符可在除第一个字段之外的所有字段之前放置一个空格-您可以更改输出格式还可以注意,请注意,有意省略了检查strlen(buf) + 1 == MAXC && buf[MAXC-2] != '\n'以验证整个行是否适合buf并留给您实施)

上面的for循环的使用只是一种简化的方法,可以合并调用以获取第一个令牌,其中strtok的第一个参数是字符串本身,然后获取后续令牌其中strtok的第一个参数是NULL,同时检查tok != NULL以验证对strtok的调用会返回有效令牌。如果它更易于阅读,例如,也可以用while()循环编写

        /* split buf into tokens using strtok */
        char *tok = strtok (buf, ",");      /* separate 1st token */
        while (tok) {                       /* validate tok != NULL */
            tok[strcspn (tok, "\n")] = 0;   /* trim '\n' from end tok */
            /* output board (space before if not 1st) */
            printf (tok != buf ? " %s" : "%s", tok);
            tok = strtok (NULL, ",");       /* get next token */
        }

(都是等效的循环,用于将逗号分隔的令牌与buf分开)

示例输入文件

$ cat dat/board-8x8.txt
-4,-2,-3,-5,-6,-3,-2,-4
-1,-1,-1,-1,-1,-1,-1,-1
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
 0, 0, 0, 0, 0, 0, 0, 0
+1,+1,+1,+1,+1,+1,+1,+1
+4,+2,+3,+5,+6,+3,+2,+100

使用/输出示例

仅将每个令牌用空格分隔即可输出数据:

$ ./bin/strtok_board_csv < dat/board-8x8.txt
-4 -2 -3 -5 -6 -3 -2 -4
-1 -1 -1 -1 -1 -1 -1 -1
 0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
+1 +1 +1 +1 +1 +1 +1 +1
+4 +2 +3 +5 +6 +3 +2 +100

table中的每个指针分配存储空间

声明char *table[ROW][COL];时,已声明指针的二维数组 char。为了使用指针,您必须为每个指针分配一个有效的现有内存块的地址,或者必须分配一个足以容纳tok的新内存块,并为每个这样的块分配起始地址依次指向每个指针。您不能简单地分配例如table[i][j] = tok;由于tok指向buf中的地址,每次读取新行时,该地址都会被新内容覆盖。

相反,您需要分配足够的内存来容纳tok的内容(例如strlen(tok) + 1字节),将生成的新内存块分配给table[i][j]指针,然后复制{{1 }}到新的内存块。您可以执行以下操作:

tok

(示例输入和输出相同)

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不尝试访问内存或不在分配的块的边界之外/之外写,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,#include <stdio.h> #include <stdlib.h> #include <string.h> #define ROW 8 /* if you need a constant, #define one (or more) */ #define COL ROW #define MAXC 1024 int main (void) { char buf[MAXC], /* buffer to hold each line */ *table[ROW][COL] = {{NULL}}; /* 2D array of pointers */ size_t row = 0; while (fgets(buf,MAXC,stdin)) { /* read each line into buf */ size_t col = 0; /* split buf into tokens using strtok */ for (char *tok = strtok (buf, ","); tok; tok = strtok (NULL, ",")) { size_t len; tok[strcspn (tok, "\n")] = 0; /* trim '\n' from end tok */ len = strlen (tok); if (!(table[row][col] = malloc (len + 1))) { /* allocate/validate */ perror ("malloc-table[row][col]"); exit (EXIT_FAILURE); } memcpy (table[row][col++], tok, len + 1); /* copy tok to table */ } if (col != COL) { /* validate COL tokens read from buf */ fprintf (stderr, "error: insufficient columns, row %zu\n", row); exit (EXIT_FAILURE); } row++; /* increment row counter */ } for (size_t i = 0; i < row; i++) { /* loop rows */ for (size_t j = 0; j < COL; j++) { /* loop COLS */ /* output board from table (space before if not 1st) */ printf (j > 0 ? " %s" : "%s", table[i][j]); free (table[i][j]); /* free allocated memory */ } putchar ('\n'); } } 是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

valgrind

始终确认已释放已分配的所有内存,并且没有内存错误。

让我知道您是否还有其他问题。

相关问题