How can I read a known number of strings of unknown size from a .txt file and store each line in a line of a matrix (in C)?

有些话、适合烂在心里 提交于 2021-01-28 01:02:22

问题


Title is pretty self explanatory. I'm almost sure that the end result wouldn't be a matrix as each line would have a different number of columns, so it's more like a array of arrays of variable sizes. It would also be interesting to sort the fragments by size, biggest first. This is what I've tried so far:

int main() {
  char str[MAXLEN], **fragmentsList;
  int number_of_strings, i, max, k;
  printf("Enter .txt file name: ");
  scanf("%s", str);
  printf("How many strings does the file has? ");
  scanf("%d", &number_of_strings);
  FILE *arq;
  arq = fopen(str, "r");
  for (i = 0, max = 0; !feof(arq); i++) {
    while (fscanf("%c") != '\n') {
      max++;
    }
    if (max > k) {
      k = max;
    }
  }
  fclose(arq);
  fragmentsList = malloc(k * sizeof(char));
  *fragmentsList = malloc(number_of_strings * sizeof(char));
  arq = fopen(str, "r");
  for (i = 0; !feof(arq); i++) {
    fscanf(arq, "%s", fragmentList[i]);
  }
  for (i = 0; i < number_of_strings; i++) {
    printf("%s", fragmentList[i]);
  }
  return 0;
}

回答1:


The reading an unknown number of lines from a file into memory in C is a basic necessity. There are a couple of way to approach it, but the standard practice is to:

  • declare a pointer to pointer to type (char** for lines in a file) to allow you to collect and reference each line after read into memory;

  • allocate some reasonably anticipated number of pointers to begin with to avoid repeated calls to realloc allocating pointers for each line individually (initially allocating 8, 16, 32, .. all work fine);

  • declare a variable to track the number of lines read, and increment for each line;

  • read each line of the file into a buffer (POSIX getline works particularly well because it itself will dynamically allocate sufficient storage to handle any line length -- freeing you from reading with a fixed buffer and having to allocate for and accumulate partial-lines until the end of the line is reached)

  • allocate storage for each line, copy the line to the new storage, and assign the beginning address to your next pointer, strdup does both for you, but since it allocates, make sure you validate it succeeds;

  • when your index reaches your current number of allocated pointers, realloc more pointers (generally by doubling the number, or increasing the number by 3/2 -- the rate if increase isn't particularly important -- what is important is insuring you always have a valid pointer to assign the new block of memory holding your line to); and

  • repeat until the file is completely read.

There are a few subtleties to be aware of when reallocating memory. First never realloc directly to to pointer being reallocated, e.g. do not do:

mypointer = realloc (mypointer, current_size * 2);

if realloc fails, it returns NULL and if you are assigning the return to your original pointer, you overwrite the address to your current data with NULL creating a memory leak. Instead, always use a temporary pointer and validate realloc succeeds before assigning the new block of memory to your original pointer, e.g.

    if (filled_pointers == allocated pointers) {
        void *tmp = realloc (mypointer, current_size * 2);

        if (tmp == NULL) {
            perror ("realloc-mypointer");
            break;      /* or use goto to jump out of your read loop,
                         * preserving access to your current data in
                         * the original pointer.
                         */
        }
        mypointer = tmp;
        current_size *= 2;
    }

Putting the pieces altogether in an example using getline, you can do something like the following. (note: the code expects the filename to read from as the 1st argument to your program, if no argument is given the program will read from stdin by default)

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

#define NPTR 8      /* initial number of pointers (must be > 0) */

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

    size_t ndx = 0,             /* line index */
        nptrs = NPTR,           /* initial number of pointers */
        n = 0;                  /* line alloc size (0, getline decides) */
    ssize_t nchr = 0;           /* return (no. of chars read by getline) */
    char *line = NULL,          /* buffer to read each line */
        **lines = NULL;         /* pointer to pointer to each line */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

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

    /* allocate/validate initial 'nptrs' pointers */
    if (!(lines = calloc (nptrs, sizeof *lines))) {
        perror ("calloc - lines");
        return 1;
    }

    /* read each line with POSIX getline */
    while ((nchr = getline (&line, &n, fp)) != -1) {
        if (nchr && line[nchr - 1] == '\n') /* check trailing '\n' */
            line[--nchr] = 0;               /* overwrite with nul-char */
        char *buf = strdup (line);          /* allocate/copy line */
        if (!buf) {             /* strdup allocates, so validate */
            perror ("strdup-line");
            break;
        }
        lines[ndx++] = buf;     /* assign start address for buf to lines */
        if (ndx == nptrs) {     /* if pointer limit reached, realloc */
            /* always realloc to temporary pointer, to validate success */
            void *tmp = realloc (lines, sizeof *lines * nptrs * 2);
            if (!tmp) {         /* if realloc fails, bail with lines intact */
                perror ("realloc - lines");
                break;          /* don't exit, lines holds current lines */
            }
            lines = tmp;        /* assign reallocted block to lines */
            /* zero all new memory (optional) */
            memset (lines + nptrs, 0, nptrs * sizeof *lines);
            nptrs *= 2;         /* increment number of allocated pointers */
        }
    }
    free (line);                    /* free memory allocated by getline */

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

    for (size_t i = 0; i < ndx; i++) {
        printf ("line[%3zu] : %s\n", i, lines[i]);
        free (lines[i]);            /* free memory for each line */
    }
    free (lines);                   /* free pointers */

    return 0;
}

Look things over and let me know if you have further questions. If you do not have getline or strdup available, let me know and I'm happy to help further with an implementation that will provide their behavior.



来源:https://stackoverflow.com/questions/50778328/how-can-i-read-a-known-number-of-strings-of-unknown-size-from-a-txt-file-and-st

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!