When you are reading a line-at-a-time, use a line-oriented input function such as fgets()
or POSIX getline()
. This avoids the many pitfalls for new C programmers who attempt to read from the file with scanf()
alone.
The problems with using scanf()
alone are many, but boil down to what is left in the input stream after an attempted read. For example, when using scanf()
to attempt to read an integer value, if a non-digit is encountered before a digit, a matching-failure occurs and character extraction from the input stream ceases at that point leaving all offending characters in the input stream unread -- just waiting to bite you again on your next attempted read unless you manually empty the characters from your input stream. After reading a float
as the last value, the '\n'
is left in the input-stream unread and if on your next read, your format specifier doesn't ignore leading whitespace, the whitespace will be taken as your input. (this is just the tip of the iceberg...)
So use fgets()
to read an entire line-at-a-time into an appropriately sized character array. That way you are consuming an entire line at a time. You then parse what you need from your line of data using sscanf()
. Regardless of the success/failure of the parse, you do not impact your read of the next line of data from the file. The read of data and parse of value are no longer coupled in a single operation.
Putting the pieces together, in your case you can do something similar to:
#include <stdio.h>
#include <string.h>
#define MAXC 1024
#define MAXNM 20
typedef char String20[MAXNM]; /* your typedef */
struct nameTag {
String20 first;
String20 last;
};
struct studentTag {
int ID;
struct nameTag name;
float grade;
};
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to hold each line */
size_t n = 0; /* counter for number of students */
struct studentTag std[MAXNM] = {{ .ID = 0 }};
/* 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;
}
/* read each line -- until array full of EOF reached */
while (n < MAXNM && fgets (buf, MAXC, fp)) {
struct studentTag tmp = { .ID = 0 }; /* temporary struct to fill */
if (sscanf (buf, "%d %19s %19s %f", /* parse value from line */
&tmp.ID, tmp.name.first, tmp.name.last, &tmp.grade) == 4)
std[n++] = tmp; /* assign temp struct to array on success */
else /* on error */
fprintf (stderr, "error: line format - %s", buf);
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (size_t i = 0; i < n; i++) /* output all stored student data */
printf ("\nID: %d\nFirst: %s\nLast: %s\nGrade: %f\n",
std[i].ID, std[i].name.first, std[i].name.last, std[i].grade);
return 0;
}
(note: you always validate every user-input and every parse of values by checking the return, see Henry Spencer's 10 Commandments for C Programmers - No. 6 "Ye be warned...")
Example Use/Output
With your data in dat/studenttag.txt
, you would provide the filename as the first argument and receive:
$ ./bin/readstudenttag dat/studenttag.txt
ID: 1010
First: Jose
Last: Manalo
Grade: 3.500000
ID: 2003
First: Luisa
Last: Santos
Grade: 2.500000
ID: 1008
First: Andres
Last: Espinosa
Grade: 4.000000
Look things over and let me know if you have further questions.