首页 > 解决方案 > 在 C 中创建一个读取文件但无限循环的函数

问题描述

我基本上想打印出我制作的文件中的所有行,但它只是一遍又一遍地循环回到开头,基本上是因为在我设置fseek(fp, 0, SEEK_SET);这部分的函数中,但是我不知道如何放置它以通过所有其他我基本上每次都会回到起点。

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

char *freadline(FILE *fp);

int main(){

    FILE *fp = fopen("story.txt", "r");

    if(fp == NULL){
        printf("Error!");
    }else{
        char *pInput;
        while(!feof(fp)){
            pInput = freadline(fp);
            printf("%s\n", pInput); // outpu 
        }   
    }
    
    return 0;
}

char *freadline(FILE *fp){
    int i;
    for(i = 0; !feof(fp); i++){
        getc(fp);
    }
    fseek(fp, 0, SEEK_SET); // resets to origin (?)
    char *pBuffer = (char*)malloc(sizeof(char)*i);
    pBuffer = fgets(pBuffer, i, fp);

    return pBuffer;
}

这是我到目前为止的工作

标签: c

解决方案


继续我的评论,您的思路是正确的,只是您没有按照正确的顺序将各个部分组合在一起。在main()你循环调用你的函数,分配一行,然后输出一行文本并一遍又一遍地执行它。(并且没有释放您分配的任何内存——创建每行读取的内存泄漏)

如果您正在分配存储来保存该行,您通常希望通过文件分配和存储所有行并返回指向您的行集合的指针以单次传递在您的函数中读取整个文件以打印在main()(或任何调用函数)。

您可以通过添加一个额外的间接级别并让您的函数返回来做到这一点char **。这是一个简单的两步过程,您可以:

  1. 分配一个包含指针的内存块(每行一个指针)。由于您事先不知道有多少行,因此您只需分配一些指针,然后realloc()在用完时分配更多指针;

  2. 对于您读取的每一行,您分配length + 1存储字符并将当前行复制到该内存块,并将该行的地址分配给您的下一个可用指针。

(您要么跟踪分配的指针和行数,要么在最后一个指针分配行之后提供一个设置NULLSentinel的附加指针- 由您决定,简单地跟踪计数器在概念上可能更容易)

阅读最后一行后,您只需将指针返回到分配给调用者使用的指针集合。(您也可以将 a 的地址char **作为参数传递给您的函数,导致类型为char ***,但成为三星级程序员并不总是一种恭维)。但是,这样做并没有错,在某些情况下,它是必需的,但如果你有替代方案,那通常是首选路线。

那么这将如何在实践中发挥作用呢?

只需将函数返回类型更改为char **并将附加指针传递给计数变量,这样您就可以使用从函数返回之前读取的行数更新该地址处的值。例如,你可以这样做:

char **readfile (FILE *fp, size_t *n);

这会将您的文件指针指向打开的文件流,然后从中读取每一行,为该行分配存储空间并将该分配的地址分配给您的一个指针。在函数中,您将使用一个足够大的字符数组来保存您使用 读取的每一行fgets()。从末尾修剪'\n'并获取长度,然后分配length + 1字节来保存该行。将新分配的块的地址分配给一个指针,然后从您的数组复制到新分配的块。

strdup()可以分配和复制,但它不是标准库的一部分,它是 POSIX ——尽管如果您提供正确的选项,大多数编译器都支持它作为扩展)

下面的readfile()函数将其全部放在一起,从单个指针开始,当你用完时重新分配当前数字的两倍(这在所需的分配数量和指针数量之间提供了合理的权衡。(在仅仅 20 次调用之后realloc(),你将有 1M 指针)。您可以选择任何您喜欢的重新分配和增长方案,但您要避免调用realloc()每一行 -realloc()仍然是一个相对昂贵的调用。

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTR    1       /* initial no. of pointers to allocate */

/* readfile reads all lines from fp, updating the value at the address
 * provided by 'n'. On success returns pointer to allocated block of pointers
 * with each of *n pointers holding the address of an allocated block of
 * memory containing a line from the file. On allocation failure, the number
 * of lines successfully read prior to failure is returned. Caller is
 * responsible for freeing all memory when done with it.
 */
char **readfile (FILE *fp, size_t *n)
{
    char buffer[MAXC], **lines;                 /* buffer to hold each line, pointer */
    size_t allocated = NPTR, used = 0;          /* allocated and used pointers */
    
    lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
    
    if (lines == NULL) {                        /* validate EVERY allocation */
        perror ("malloc-lines");
        return NULL;
    }
    
    while (fgets (buffer, MAXC, fp)) {          /* read each line from file */
        size_t len;                             /* variable to hold line-length */
        if (used == allocated) {                /* is pointer reallocation needed */
            /* always realloc to a temporary pointer to avoid memory leak if
             * realloc fails returning NULL.
             */
            void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
            if (!tmp) {                         /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                          /* lines before failure still good */
            }
            lines = tmp;                        /* assign reallocted block to lines */
            allocated *= 2;                     /* update no. of allocated pointers */
        }
        buffer[(len = strcspn (buffer, "\n"))] = 0;     /* trim \n, save length */
        
        lines[used] = malloc (len + 1);         /* allocate storage for line */
        if (!lines[used]) {                     /* validate EVERY allocation */
            perror ("malloc-lines[used]");
            break;
        }
        
        memcpy (lines[used], buffer, len + 1);  /* copy buffer to lines[used] */
        used++;                                 /* increment used no. of pointers */
    }
    *n = used;              /* update value at address provided by n */
    
    /* can do final realloc() here to resize exactly to used no. of pointers */
    
    return lines;           /* return pointer to allocated block of pointers */
}

main()中,您只需传递文件指针和size_t变量的地址,并在迭代指针之前检查返回值,以使用您需要的行(它们简单地打印在下面),例如

int main (int argc, char **argv) {
    
    char **lines;       /* pointer to allocated block of pointers and lines */
    size_t n;           /* number of lines read */
    /* 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;
    }
    
    lines = readfile (fp, &n);
    
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    if (!lines) {       /* validate readfile() return */
        fputs ("error: no lines read from file.\n", stderr);
        return 1;
    }
    
    for (size_t i = 0; i < n; i++) {            /* loop outputting all lines read */
        puts (lines[i]);
        free (lines[i]);                        /* don't forget to free lines */
    }
    free (lines);                               /* and free pointers */
    
    return 0;
}

注意:完成后不要忘记释放分配的内存。当您调用在其他函数中分配的函数时,这变得至关重要。在main()退出时,内存将自动释放,但要养成良好的习惯。)

示例使用/输出

$ ./bin/readfile_allocate dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

该程序将读取任何文件,无论它有 4 行还是 400,000 行,直到系统内存的物理限制(MAXC如果您的行长于 1023 个字符,请进行调整)。

内存使用/错误检查

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

您必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化值的条件跳转,最后确认释放所有分配的内存。

对于 Linuxvalgrind是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/readfile_allocate dat/captnjack.txt
==4801== Memcheck, a memory error detector
==4801== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==4801== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==4801== Command: ./bin/readfile_allocate dat/captnjack.txt
==4801==
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.
==4801==
==4801== HEAP SUMMARY:
==4801==     in use at exit: 0 bytes in 0 blocks
==4801==   total heap usage: 10 allocs, 10 frees, 5,804 bytes allocated
==4801==
==4801== All heap blocks were freed -- no leaks are possible
==4801==
==4801== For counts of detected and suppressed errors, rerun with: -v
==4801== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

该示例使用的完整代码仅包含标头,但为了完整起见,将其包含在下面:

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define NPTR    1       /* initial no. of pointers to allocate */

/* readfile reads all lines from fp, updating the value at the address
 * provided by 'n'. On success returns pointer to allocated block of pointers
 * with each of *n pointers holding the address of an allocated block of
 * memory containing a line from the file. On allocation failure, the number
 * of lines successfully read prior to failure is returned. Caller is
 * responsible for freeing all memory when done with it.
 */
char **readfile (FILE *fp, size_t *n)
{
    char buffer[MAXC], **lines;                 /* buffer to hold each line, pointer */
    size_t allocated = NPTR, used = 0;          /* allocated and used pointers */
    
    lines = malloc (allocated * sizeof *lines); /* allocate initial pointer(s) */
    
    if (lines == NULL) {                        /* validate EVERY allocation */
        perror ("malloc-lines");
        return NULL;
    }
    
    while (fgets (buffer, MAXC, fp)) {          /* read each line from file */
        size_t len;                             /* variable to hold line-length */
        if (used == allocated) {                /* is pointer reallocation needed */
            /* always realloc to a temporary pointer to avoid memory leak if
             * realloc fails returning NULL.
             */
            void *tmp = realloc (lines, 2 * allocated * sizeof *lines);
            if (!tmp) {                         /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                          /* lines before failure still good */
            }
            lines = tmp;                        /* assign reallocted block to lines */
            allocated *= 2;                     /* update no. of allocated pointers */
        }
        buffer[(len = strcspn (buffer, "\n"))] = 0;     /* trim \n, save length */
        
        lines[used] = malloc (len + 1);         /* allocate storage for line */
        if (!lines[used]) {                     /* validate EVERY allocation */
            perror ("malloc-lines[used]");
            break;
        }
        
        memcpy (lines[used], buffer, len + 1);  /* copy buffer to lines[used] */
        used++;                                 /* increment used no. of pointers */
    }
    *n = used;              /* update value at address provided by n */
    
    /* can do final realloc() here to resize exactly to used no. of pointers */
    
    return lines;           /* return pointer to allocated block of pointers */
}

int main (int argc, char **argv) {
    
    char **lines;       /* pointer to allocated block of pointers and lines */
    size_t n;           /* number of lines read */
    /* 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;
    }
    
    lines = readfile (fp, &n);
    
    if (fp != stdin)    /* close file if not stdin */
        fclose (fp);

    if (!lines) {       /* validate readfile() return */
        fputs ("error: no lines read from file.\n", stderr);
        return 1;
    }
    
    for (size_t i = 0; i < n; i++) {            /* loop outputting all lines read */
        puts (lines[i]);
        free (lines[i]);                        /* don't forget to free lines */
    }
    free (lines);                               /* and free pointers */
    
    return 0;
}

如果您还有其他问题,请告诉我。


推荐阅读