将文本文件的每一行存储到数组中

Storing each line of a text file into an array

我正在尝试将文本文件的每一行保存到一个数组中。 他们的方式我正在这样做并且到目前为止工作正常是这样的:

char *lines[40];
char line[50];
int i = 0 ;
char* eof ;
while( (eof = fgets(line, 50, in)) != NULL )
{
    lines[i] = strdup(eof); /*Fills the array with line of the txt file one by one*/
    i++;
}

我的文本文件有 40 行,我正在使用 for 循环访问它

for( j = 0; j <= 39 ; j++)
{ /*Do something to each line*/}.

到目前为止一切顺利。我的问题是我定义了数组的大小 lines 对于具有 40 行的文本文件。我试图计算行数然后定义大小,但我得到 分段错误

我的做法:

int count=1 ; char c ;
for (c = getc(in); c != EOF; c = getc(in))
    if (c == '\n') // Increment count if this character is newline
        count = count + 1;
printf("\nNUMBER OF LINES = %d \n",count); 

char* lines[count];

有什么想法吗?

顺便说一句,我测试了你上面显示的确切代码以获得行数(通过计算换行符),在一个包含超过 1000 行的文件中,有些行长 4000 个字符。问题不在那里。 因此,seg fault 可能是由于您为每个行缓冲区分配内存的方式所致。您可能试图将长行写入短缓冲区。 (也许我在您的 post 中遗漏了它,但找不到您解决行长度的位置?)

在为文件中的字符串分配内存时,有两个有用的东西是行数和文件中的最大行长度。这些可用于创建 char 数组的数组。

您可以通过循环 fgets(...) 来获得行数和最长行:(您的主题的变体,本质上是让 fgets 找到换行符)

int countLines(FILE *fp, int *longest)
{
    int i=0;
    int max = 0;
    char line[4095];  // max for C99 strings
    *longest = max;
    while(fgets(line, 4095, fp))
    {
        max = strlen(line); 
        if(max > *longest) *longest = max;//record longest
        i++;//track line count
    }
    return i;
}
int main(void)
{
    int longest;
    char **strArr = {0};
    FILE *fp = fopen("C:\dev\play\text.txt", "r");
    if(fp)
    {
        int count = countLines(fp, &longest);
        printf("%d", count);
        GetKey();
    }
    // use count and longest to create memory
    strArr = create2D(strArr, count, longest);
    if(strArr)
    {
       //use strArr ...
       //free strArr
       free2D(strArr, lines);
    }
    ......and so on
    return 0;   
}

char ** create2D(char **a, int lines, int longest)
{
    int i;
    a = malloc(lines*sizeof(char *));
    if(!a) return NULL;
    {
        for(i=0;i<lines;i++)
        {
            a[i] = malloc(longest+1);
            if(!a[i]) return NULL;
        }
    }
    return a;
}

void free2D(char **a, int lines)
{
    int i;
    for(i=0;i<lines;i++)
    {
        if(a[i]) free(a[i]);
    }
    if(a) free(a);
}

有很多方法可以解决这个问题。要么声明一个静态二维数组或 char(例如 char lines[40][50] = {{""}};),要么声明一个指向 char [50] 类型数组的 指针,这可能是动态分配最简单的方法。使用这种方法,您只需要一次分配。对于常量 MAXL = 40MAXC = 50,您只需要:

char (*lines)[MAXC] = NULL;
...
lines = malloc (MAXL * sizeof *lines);

fgets读取每一行是一项简单的任务:

while (i < MAXL && fgets (lines[i], MAXC, fp)) {...

完成后,您需要做的就是free (lines);将各个部分放在一起,您可以执行以下操作:

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

enum { MAXL = 40, MAXC = 50 };

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

    char (*lines)[MAXC] = NULL; /* pointer to array of type char [MAXC] */
    int i, n = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

    if (!(lines = malloc (MAXL * sizeof *lines))) { /* allocate MAXL arrays */
        fprintf (stderr, "error: virtual memory exhausted 'lines'.\n");
        return 1;
    }

    while (n < MAXL && fgets (lines[n], MAXC, fp)) { /* read each line */
        char *p = lines[n];                  /* assign pointer */
        for (; *p && *p != '\n'; p++) {}     /* find 1st '\n'  */
        *p = 0, n++;                         /* nul-termiante  */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    /* print lines */
    for (i = 0; i < n; i++) printf (" line[%2d] : '%s'\n", i + 1, lines[i]);

    free (lines);   /* free allocated memory */

    return 0;
}

注意: 您还需要检查 fgets 是否每次都读取了整行。 (假设文件中有一长行超过 38 个字符)。您可以通过在用 nul-terminating 字符覆盖之前检查 *p 是否为 '\n' 来执行此操作。 (例如 if (*p != '\n') { int c; while ((c = getchar()) != '\n' && c != EOF) {} })。这确保使用 fgets 的下一次读取将从下一行开始,而不是当前行中的其余字符。

要包括检查,您可以执行类似于以下的操作(注意: 我将读取循环计数器从 i 更改为 n 以消除需要在读取循环后分配 n = i;)。

    while (n < MAXL && fgets (lines[n], MAXC, fp)) { /* read each line */
        char *p = lines[n];                 /* assign pointer  */
        for (; *p && *p != '\n'; p++) {}    /* find 1st '\n'   */
        if (*p != '\n') {                   /* check line read */
            int c;  /* discard remainder of line with getchar  */
            while ((c = fgetc (fp)) != '\n' && c != EOF) {}
        }
        *p = 0, n++;                        /* nul-termiante   */
    }

由您决定是丢弃还是保留超出数组长度的剩余行。但是,始终检查是个好主意。 (下面我的示例输入中的文本行限制为 17 个字符,因此不可能出现长行,但您通常不能保证行的长度。

示例输入

$ cat dat/40lines.txt
line of text -  1
line of text -  2
line of text -  3
line of text -  4
line of text -  5
line of text -  6
...
line of text - 38
line of text - 39
line of text - 40

例子Use/Output

$ ./bin/fgets_ptr2array <dat/40lines.txt
 line[ 1] : 'line of text -  1'
 line[ 2] : 'line of text -  2'
 line[ 3] : 'line of text -  3'
 line[ 4] : 'line of text -  4'
 line[ 5] : 'line of text -  5'
 line[ 6] : 'line of text -  6'
...
 line[38] : 'line of text - 38'
 line[39] : 'line of text - 39'
 line[40] : 'line of text - 40'

现在在代码中包含一个长度检查并在输入中添加一个长行,例如:

$ cat dat/40lines+long.txt
line of text -  1
line of text -  2
line of text -  3 + 123456789 123456789 123456789 123456789 65->|
line of text -  4
...

重新运行 程序,您可以确认您现在已经防止文件中的长行破坏您从文件中连续读取的行。


动态重新分配 lines

如果您的文件中的行数未知,并且您在 lines 中达到了 40 的初始分配,那么继续阅读其他行所需要做的就是 realloc lines 的存储空间。例如:

    int i, n = 0, maxl = MAXL;
    ...
    while (fgets (lines[n], MAXC, fp)) {     /* read each line */
        char *p = lines[n];                  /* assign pointer */
        for (; *p && *p != '\n'; p++) {}     /* find 1st '\n'  */
        *p = 0;                              /* nul-termiante  */
        if (++n == maxl) { /* if limit reached, realloc lines  */
            void *tmp = realloc (lines, 2 * maxl * sizeof *lines);
            if (!tmp) {     /* validate realloc succeeded */
                fprintf (stderr, "error: realloc - virtual memory exhausted.\n");
                break;      /* on failure, exit with existing data */
            }
            lines = tmp;    /* assign reallocated block to lines */
            maxl *= 2;      /* update maxl to reflect new size */
        }
    }

现在文件中有多少行并不重要,您只需继续重新分配 lines 直到读取整个文件,或者 运行 内存不足。 (注意:目前,代码在每次重新分配时为 lines 重新分配当前内存的两倍。您可以随意添加任意数量的内存。例如,您可以分配 maxl + 40 来简单地分配 40每次多行。

编辑以回应评论查询

如果您确实想使用 lines 数量的固定增加而不是按某种因素缩放,则必须分配固定数量的额外 lines(增加次数 sizeof *lines),你不能简单地添加 40 字节,例如

        void *tmp = realloc (lines, (maxl + 40) * sizeof *lines);
            if (!tmp) {     /* validate realloc succeeded */
                fprintf (stderr, "error: realloc - virtual memory exhausted.\n");
                break;      /* on failure, exit with existing data */
            }
            lines = tmp;    /* assign reallocated block to lines */
            maxl += 40;     /* update maxl to reflect new size */
        }

回想一下,lineschar[50] 数组指针 ,因此对于您要分配的每一行,您必须分配存储空间对于 50 个字符(例如 sizeof *lines),因此固定增加 40 行将是 realloc (lines, (maxl + 40) * sizeof *lines);,那么您必须准确更新分配的最大行数(maxl)以反映增加 40 行,例如maxl += 40;.

示例输入

$ cat dat/80lines.txt
line of text -  1
line of text -  2
...
line of text - 79
line of text - 80

例子Use/Output

$ ./bin/fgets_ptr2array_realloc <dat/80lines.txt
 line[ 1] : 'line of text -  1'
 line[ 2] : 'line of text -  2'
...
 line[79] : 'line of text - 79'
 line[80] : 'line of text - 80'

仔细阅读,如果您有任何问题,请告诉我。