Which would be the best way to fill that struct?
For a homework assignement, I need to fill a list of Student structs, which look like the followi开发者_Python百科ng:
struct Student {
int matriculationNumber;
char *firstName;
char *lastName;
char *birthday;
double averageGrage;
}
The actual data has to be read from a .csv
file and looks somethink like
2345678;Meier;Hans;12.10.1985;2,4
1234567;Müller;Fritz;17.05.1990;1,9
For reading in the data, fgetc()
should be used.
Now, the problem is how do I actually fill in the fields of the struct and how to handle exceptional conditions (i.e. unexpected EOF
; think for example if a line does not contain a field birthday
or a field averageGroup
).
This is how I'd do it intuitively (which is most probably the wrong way ;-)):
Student student;
if (fillMatriculationNumber(&student, fp) == -1) { // return -1 on failure or EOF
goto failure;
}
if (fillFirstName(&student, fp) == -1) {
goto failure;
}
if (fillLastName(&student, fp) == -1) {
goto failure;
}
if (fillBirthday(&student, fp) == -1) {
goto failure;
}
if (fillAverageGrade(&student, fp) == -1) {
goto failure;
}
// OK
:failure
// print a message about what's wrong, and exit()
I would go in this order:
- first read the whole line
- then check that the number of field is correct (counting
;
should be fine for your example) and handle error situation (skip line or stop parsing?) - then split the line in a
char*[]
(you can do it in place by placing'\0'
and using directly the string or by creating new strings) - then check the required fields for correctness (matriculation is a number, birthday is a date, etc)
- then fill the real struct (you could use
strcpy
,strdup
or copying directly the pointer for strings according to your needs)
Since stress is there in fgetc(), you can change your code slightly.
while(!feof(fp)) { readRecordSuccess = 0; if (fillMatriculationNumber(&student, fp) != -1) { // return -1 on failure or EOF if (fillFirstName(&student, fp) != -1) { if (fillLastName(&student, fp) != -1) { if (fillBirthday(&student, fp) != -1) { if (fillAverageGrade(&student, fp) != -1) { readRecordSuccess = 1; } } } } } if(readRecordSuccess == 0) { /* may clean already filled structure(s) */ break; } /* * the structure will be overwritten in the next iteration * take proper measure */ }
I would read each CSV row, and then store it in a Student struct.
const unsigned int MaxFields = 5;
const unsigned int MaxContents = 80;
void readRow(FILE * f, char dataRow[MaxFields][MaxContents])
{
int c;
unsigned int i;
char buffer[MaxContents];
int pos;
int field;
// Empty all fields
for(i = 0; i < MaxFields; ++i) {
dataRow[ i ][ 0 ] = '\0';
}
// Read rows
buffer[ 0 ] = '\0';
c = fgetc( f );
pos = 0;
field = 0;
while( c != EOF
&& c != '\n' )
{
if ( c != ';' ) {
buffer[ pos++ ] = c;
} else {
buffer[ pos ] = '\0';
strcpy( dataRow[ field++ ], buffer );
buffer[ 0 ] = '\0';
pos = 0;
}
c = fgetc( f );
}
}
This way, you are reading the contents in a vector of strings. The vector of strings is initialised to the empty string so there is no problem if one field is empty or missing.
Once a row is read, you can store it in a Student struct:
char * safeStrDup(const char * src)
{
char * toret = strdup( src );
if ( toret == NULL ) {
fprintf( stderr, "Not enough memory\n" );
exit( EXIT_FAILURE );
}
return toret;
}
void store(Student *s, char dataRow[MaxFields][MaxContents])
{
s->matriculationNumber = atoi( dataRow[ 0 ] );
s->firstName = safeStrDup( dataRow[ 1 ] );
s->lastName = safeStrDup( dataRow[ 2 ] );
s->birthday = safeStrDup( dataRow[ 3 ] );
s->averageGrage = atof( dataRow[ 4 ] );
}
Take into account that some steps are missing. But this skeleton should give you a good starting point.
Hope this helps.
Initialize the pointer fields in the structure to null pointers; as pointed out in the comments, memset is not the right option here - use c99 way or do it explicitly for each field.
If reading a field fails for some reason, you should free the already allocated fields. For example, if reading average
fails for a student and you decide to ignore that student's record, you should free his name
fields to prevent memory leaks (assuming they're malloc'ed, of course).
精彩评论