简体   繁体   中英

reading a formatted text file into an array

I have a text file with the given format:

 2078.62        5.69982       -0.17815       -0.04732
 5234.95        8.40361        0.04028        0.10852
 2143.66        5.35245        0.10747       -0.11584
 7216.99        2.93732       -0.18327       -0.20545
 1687.24        3.37211        0.14195       -0.14865
 2065.23        34.0188         0.1828        0.21199
 2664.57        2.91035        0.19513        0.35112
 7815.15        9.48227       -0.11522        0.19523
 5166.16        5.12382       -0.29997       -0.40592
 6777.11        5.53529       -0.37287       -0.43299
 4596.48        1.51918       -0.33986        0.09597
 6720.56        15.4161       -0.00158        -0.0433
 2652.65        5.51849        0.41896       -0.61039

I have written the following functions to read the file

Read the number of lines

unsigned int getnumline(const char *sn){
   unsigned int n;
   char lcstring[LCLENGTH];
   FILE *lcpipe;
   char buff[512];
   snprintf( lcstring, LCLENGTH, 
      "wc -l %s | cut -d ' ' -f1", sn);
   lcpipe = popen( lcstring, "r" );
   if (lcpipe == NULL)
     exit_failure( "popen: " );

   while(fgets(buff, sizeof(buff), lcpipe)!=NULL){
        n=atoi(buff);
   }
   pclose(lcpipe);
   printf("Number of lines in the input file: %d\n", n);
   return n;
}

Read the text file

double **callocmatrix( unsigned int m, unsigned int n ) {

  double **matrix;
  unsigned int i;

  matrix = (double **)calloc(m, sizeof(double *));
  if ( !matrix ) 
    return NULL;

  matrix[0] = (double *)calloc(m*n, sizeof(double));
  if ( !matrix[0] )
    return NULL;

  for ( i = 1; i < m; i += 1 )
    matrix[i] = matrix[i-1] + n;

  return matrix;
}

void freematrix( double **matrix ) {

  free( (void *)matrix[0] );
}



double **ellcat;   
ngal = getnumline(sname);
ellcat = callocmatrix( ngal, 4 );
void readcat( double **ellcat, unsigned int catlen, const char *sn ) {
     unsigned int i;      
     FILE *fp=fopen(sn,"r");
     if(fp == NULL) 
      {
         printf("Error in opening file\n");
         exit(0);
      }          
     for (i=0 ; i< catlen ; i++)
     {        
       fscanf(fp, "%lf %lf %lf %lf", &ellcat[i][0], &ellcat[i][1], &ellcat[i][2], &ellcat[i][3]);        
     }    
     for (i=0 ; i< catlen ; i++)
     {
      printf("x = %lf, y = %lf, e1 = %lf, e2 = %lf\n", &ellcat[i][0], &ellcat[i][1], &ellcat[i][2], &ellcat[i][3]);
     }
     fclose(fp); 
}

But the ellcat components are empty

x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000
x = 0.000000, y = 0.000000, e1 = 0.000000, e2 = 0.000000

I can not figure out what the problem is with my readcat function.

PS I have to mention I am a newbie with respect to the C language.

Firstly, using an external program to count the number of lines is a bit awkward. It would be more efficient (involving less code, and less system resources) to simply count the number of newlines in the file:

size_t get_newline_count(char const *f) {
    size_t newline_count = 0;
    FILE *fd = fopen(f, "r");
    if (!fd) {
        return 0;
    }
    int c;
    do {
        c = fgetc(fd);
        newline_count += (c == '\n');
    } while (c >= 0);
    fclose(fd);
    return newline_count;
}

unsigned int n;
...
printf("Number of lines in the input file: %d\n", n);

The return type of the function I wrote above is size_t , so you'll want to use the %zu format specifier to print it. When the type you want to print is unsigned int (as in your code), use %u ; if you supply the wrong type of argument ( %d corresponds to int ), the behaviour is undefined.

fscanf(fp, "%lf %lf %lf %lf", &ellcat[i][0], &ellcat[i][1], &ellcat[i][2], &ellcat[i][3]);

Future note : It is your responsibility to give us a complete MCVE that compiles and reproduces the problem without having to fill in any blanks .

You haven't given us the declaration for ellcat , so we can only speculate that you may have given the wrong type (since you did precisely that above). ellcat needs to have two levels of indirection referring to a double for this code to be correct.

printf("x = %lf, y = %lf, e1 = %lf, e2 = %lf\n", &ellcat[i][0], &ellcat[i][1], &ellcat[i][2], &ellcat[i][3])

Finally, as another answer has indicated, when using printf to print double (or float , the l in %lf is redundant in this case due to default promotion to double ), "You don't need to pass address of variables"... Indeed, you shouldn't pass an address, because that would mean you're providing the incorrect type, which is undefined behaviour as we covered earlier.

While printing values , in printf -

printf("x = %lf, y = %lf, e1 = %lf, e2 = %lf\n",  
        &ellcat[i][0], &ellcat[i][1], &ellcat[i][2], &ellcat[i][3]);
        ^              ^              ^              ^   

You don't need to pass address of variables . Just pass them . Remove the & operator.

Unless there is a requirement that you pre-read to determine the number of lines in the file before allocating space/reading the values into your array, there is no reason to do it. You can easily read values from your file, allocate space to hold them, and keep track of the number of lines of data read in a single function/read loop. In fact, you make this quite easy by using a pointer to pointer to type (double pointer) as your data type.

The normal approach in your situation is to allocate memory for some reasonably anticipated number of lines (pointers) and then to read/allocate space for each line of data until you reach this original limit, and then to realloc additional pointers as required to read the entire file into your array of pointers to array of doubles. The line count is passed an a pointer argument to the function and updated within the function making the total number of lines available back in the calling function ( main() here).

An example of one function that would meet your requirements would be:

/* read rows in filename 'fn' that consist of 4 whitespace
 * separated double values into the dynamically allocated
 * array of pointer to array of 4 doubles 'ar', updating the
 * size_t value 'nrows' with the number of rows of data read.
 * returns pointer to allocated array on success, NULL otherwise
 */
double **readcat (double ***ar, size_t *nrow, const char *fn) 
{
    double tmp[4] = {0.0};                    /* tmp array for values  */
    FILE *fp = fn ? fopen (fn, "r") : stdin;  /* read from fn or stdin */
    if (!fp) {
        fprintf (stderr, "readcat() error: file open failed '%s'.\n", fn);
        return NULL;
    }

    /* allocate MAXR pointers to double* */
    if (!(*ar = calloc (MAXR, sizeof **ar))) {
        fprintf (stderr, "readcat() error: virtual memory exhausted.\n");
        return NULL;
    }

    *nrow = 0; /* set index to 0, read each row in file */
    while (fscanf (fp, "%lf %lf %lf %lf", 
                &tmp[0], &tmp[1], &tmp[2], &tmp[3]) == 4) {

        /* 4 values read, allocate memory in *ar */
        if (!((*ar)[*nrow] = calloc (4, sizeof ***ar))) {
            fprintf (stderr, "readcat() error: virtual memory exhausted.\n");
            return NULL;
        }
        /* copy values to *ar */
        memcpy ((*ar)[*nrow], tmp, sizeof tmp);
        (*nrow)++;  /* increment row index */

        if (*nrow == MAXR) {    /* test rows againt max (break/realloc) */
            fprintf (stderr, "readcat() warning: MAXR rows read.\n");
            break;
        }
    }

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

    return *ar;
}

Note, the reallocation code is omitted above and the read loop is exited if the maximum number of pointers (lines) MAXR is reached. Also note, that by returning a pointer, you not only can check success/failure based on the return, you also have the option of assigning the return to a pointer.

A short driver program making use of the function with your data would be:

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

#define MAXR 64

double **readcat (double ***ar, size_t *nrow, const char *fn);

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

    size_t i, n = 0;
    double **ellcat = NULL;
    char *filename = argc > 1 ? argv[1] : NULL;

    ellcat = readcat (&ellcat, &n, filename);

    if (!ellcat) {  /* validate address returned by readcat */
        fprintf (stderr, "error: readcat returned no values.\n");
        exit (EXIT_FAILURE);
    }

    printf ("\n %zu rows of data read:\n\n", n);
    for (i = 0; i < n; i++)  /* print each row of data */
        printf ("  %8.2lf  %8.5lf  %8.5lf  %8.5lf\n",
                ellcat[i][0], ellcat[i][1], ellcat[i][2], ellcat[i][3]);
    putchar ('\n');

    for (i = 0; i < n; i++)  /* free all allocated memory */
        free (ellcat[i]);
    free (ellcat);

    return 0;
}

Use/Output

$ ./bin/fscanf_4col_dyn dat/float_4col.txt

 13 rows of data read:

   2078.62   5.69982  -0.17815  -0.04732
   5234.95   8.40361   0.04028   0.10852
   2143.66   5.35245   0.10747  -0.11584
   7216.99   2.93732  -0.18327  -0.20545
   1687.24   3.37211   0.14195  -0.14865
   2065.23  34.01880   0.18280   0.21199
   2664.57   2.91035   0.19513   0.35112
   7815.15   9.48227  -0.11522   0.19523
   5166.16   5.12382  -0.29997  -0.40592
   6777.11   5.53529  -0.37287  -0.43299
   4596.48   1.51918  -0.33986   0.09597
   6720.56  15.41610  -0.00158  -0.04330
   2652.65   5.51849   0.41896  -0.61039

Memory Error/Leak Check

In any code your write that dynamically allocates memory, you have 2 responsibilites regarding any block of memory allocated: (1) always preserves a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed. It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory and to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice. There are so many subtle ways to misuse a block of memory that can cause real problems, there is no excuse not to do it. There are similar memory checkers for every platform. They are all simple to use. Just run your program through it.

$ valgrind ./bin/fscanf_4col_dyn dat/float_4col.txt
==21392== Memcheck, a memory error detector
==21392== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==21392== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==21392== Command: ./bin/fscanf_4col_dyn dat/float_4col.txt
==21392==

 13 rows of data read:

   2078.62   5.69982  -0.17815  -0.04732
<snip>
   2652.65   5.51849   0.41896  -0.61039

==21392==
==21392== HEAP SUMMARY:
==21392==     in use at exit: 0 bytes in 0 blocks
==21392==   total heap usage: 15 allocs, 15 frees, 1,496 bytes allocated
==21392==
==21392== All heap blocks were freed -- no leaks are possible
==21392==
==21392== For counts of detected and suppressed errors, rerun with: -v
==21392== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

打印时不需要提供地址,将其更改为

printf("x = %lf, y = %lf, e1 = %lf, e2 = %lf\n",ellcat[i][0],ellcat[i][1],ellcat[i][2],ellcat[i][3]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM