If you want to:
manually input the student name, id, homework 1 until 3, exercise 1-3,
mid and final exam
You will need a way to temporarily store the 3 homework grades and the 3 exercise grades so you can sum and average each to include the averages in your struct. Additionally while you are prompting and filling values in your struct, it is often convenient to fill a temporary struct and when all conditions are satisfied, assign the temporary struct as an element in your array.
When taking user input, using scanf() is full of pitfalls for the new C-programmer. Especially when taking whitespace separated values (like Firstname Lastname) or when taking numeric values (if non-digit is entered, a matching failure occurs, character extraction from the input stream ceases leaving the offending character unread in stdin, just waiting to bite you again). And then there is the problem with extraneous characters entered after you numeric input, such as a slip of the key reaching for 4 that results in "4r" actually being entered (or a cat steps on the keyboard).
Unless each of these conditions are testes against for every input -- you are tempting undefined behavior or an infinite loop.
That is why is it highly recommended that you take user input using a line-oriented input function like fgets() or POSIX getline() so that an entire line of input is consumed by your read each time, ensuring nothing remains in the input buffer to cause your next attempted input to fail.
When using fgets() to read user input, make sure you declare a sufficiently sized buffer (character array) to hold all the input from the user (and the cat). Don't Skimp on buffer size. An array of 1024 or 2048 characters is fine. (If you are on a memory-limited embedded system, then adjust downward as appropriate). Know also that the line oriented functions read (and include) the '\n' at the end of each line of input in the buffer they fill. If you are not parsing individual values from the buffer and need to store the whole string of input, make sure you trim the trialing '\n' from the buffer before storing it in whatever variable you are using to hold the string.
A simple method for trimming the trailing '\n' is to use the strcspn(const char *str, const char *reject) function which returns the number of characters in str until the first character in reject is encountered. If you use "\n" as your reject string it will return the length of the string not including the '\n' allowing you to easily overwrite the '\n' with '\0' (just plain 0), e.g.
buffer[strcspn (buffer, "\n")] = 0; /* trims '\n' from end of buffer */
Don't use Magick-Numbers in your code (e.g 10, 50, etc..), Instead #define a constant or use a global enum to define several, e.g.
#include <stdio.h>
#include <string.h>
/* if you need a constant, #define one (or more) or use an enum */
enum { NGRDS=3, MAXS=10, MAXN=50, MAXC=1024 };
struct Student {
char name[MAXN];
int id;
float HWavg; /* homework average */
float EXCavg; /* exercise average */
float MidEx; /* MidExam */
float FinEx; /* Final exam */
};
To receive and track input into your Students array you will need a counter variable, a buffer to hold each input and your array:
int main (void) {
size_t n = 0; /* counter */
char buf[MAXC]; /* array to hold each line from file (stdin here) */
struct Student Students[MAXS] = {{ .name = "" }}; /* array of struct */
To fill your array, you will loop until an exit condition is reached, but always protecting the array bounds for Students, e.g.
while (n < MAXS) {
struct Student tmp; /* temporary struct */
int i = 0; /* general loop counter to use */
size_t len; /* length for string length */
float sum = 0., tmparr[NGRDS]; /* sum, array of NGRDS grades */
To read your first name input, prompt and then read the input into your buffer, you can let the user just press Enter alone for the input and use that to indicate the user is done entering values and exit the read loop. Always trim the '\n' from the end of buf and then check that it will fit in name before copying from buf to name, e.g.
fputs ("\nenter name: ", stdout); /* prompt for name */
if (!fgets (buf, MAXC, stdin)) /* read name into buf/validate */
return 1;
if (*buf == '\n') /* [Enter] alone - input done */
break;
buf[strcspn (buf, "\n")] = 0; /* trim trailing '\n' */
if ((len = strlen (buf)) >= MAXN) { /* validate lenth of name */
fputs (" error: name too long.\n", stderr);
continue;
}
memcpy (tmp.name, buf, len + 1); /* copy buf to tmp.name */
(the key to successful code is the validate, validate, validate)
The input for id can be read directly into your temporary struct, e.g.
fputs ("enter id: ", stdout); /* prompt id */
if (!fgets (buf, MAXC, stdin)) /* read id into buf/validate */
return 1;
if (sscanf (buf, "%d", &tmp.id) != 1) { /* convert to int/validate */
fputs (" error: invalid integer value.\n", stderr);
continue;
}
Then for homeword and exercises, you will use your tmparray to hold each value entered before summing the value and storing the average in your tmp stuct, e.g.
puts ("enter homework"); /* prompt for homework */
while (i < NGRDS) { /* loop until NGRDS entered */
printf (" hw[%d] : ", i + 1); /* prompt for grade to enter */
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%f", &tmparr[i]) != 1) { /* add to tmparr/validate */
fputs (" error: invalid float input.\n", stderr);
continue;
}
else
i++; /* increment on good coversion */
}
for (i = 0; i < NGRDS; i++) /* sum homework grades */
sum += tmparr[i];
tmp.HWavg = sum / NGRDS; /* find homework average */
(input for exercises is identical, just don't forget to reset i and sum before taking the input)
The input for MidEx and FinEx will be the same as for id but with float instead of int. If you successfully store all values, then just assign your temporary struct to your Students array and increment the counter before looping to get the information for the next student, e.g.
Students[n++] = tmp; /* add tmp to Students array */
To output each of the value for each of the students, just loop over the Students array outputting the values in whatever format you need, e.g.
fputs ("\nstored values", stdout); /* output stored values */
for (size_t i = 0; i < n; i++)
printf ("\nname: %s\n id: %d\n HWavg: %.2f\n MidEx: %.2f\n FinEx: %.2f\n",
Students[i].name, Students[i].id, Students[i].HWavg,
Students[i].MidEx, Students[i].FinEx);
If you put it altogether, you would have something similar to the following:
#include <stdio.h>
#include <string.h>
/* if you need a constant, #define one (or more) or use an enum */
enum { NGRDS=3, MAXS=10, MAXN=50, MAXC=1024 };
struct Student {
char name[MAXN];
int id;
float HWavg; /* homework average */
float EXCavg; /* exercise average */
float MidEx; /* MidExam */
float FinEx; /* Final exam */
};
int main (void) {
size_t n = 0; /* counter */
char buf[MAXC]; /* array to hold each line from file (stdin here) */
struct Student Students[MAXS] = {{ .name = "" }}; /* array of struct */
while (n < MAXS) {
struct Student tmp; /* temporary struct */
int i = 0; /* general loop counter to use */
size_t len; /* length for string length */
float sum = 0., tmparr[NGRDS]; /* sum, array of NGRDS grades */
fputs ("\nenter name: ", stdout); /* prompt for name */
if (!fgets (buf, MAXC, stdin)) /* read name into buf/validate */
return 1;
if (*buf == '\n') /* [Enter] alone - input done */
break;
buf[strcspn (buf, "\n")] = 0; /* trim trailing '\n' */
if ((len = strlen (buf)) >= MAXN) { /* validate lenth of name */
fputs (" error: name too long.\n", stderr);
continue;
}
memcpy (tmp.name, buf, len + 1); /* copy buf to tmp.name */
fputs ("enter id: ", stdout); /* prompt id */
if (!fgets (buf, MAXC, stdin)) /* read id into buf/validate */
return 1;
if (sscanf (buf, "%d", &tmp.id) != 1) { /* convert to int/validate */
fputs (" error: invalid integer value.\n", stderr);
continue;
}
puts ("enter homework"); /* prompt for homework */
while (i < NGRDS) { /* loop until NGRDS entered */
printf (" hw[%d] : ", i + 1); /* prompt for grade to enter */
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%f", &tmparr[i]) != 1) { /* add to tmparr/validate */
fputs (" error: invalid float input.\n", stderr);
continue;
}
else
i++; /* increment on good coversion */
}
for (i = 0; i < NGRDS; i++) /* sum homework grades */
sum += tmparr[i];
tmp.HWavg = sum / NGRDS; /* find homework average */
i = 0; /* reset counter and sum */
sum = 0.;
puts ("enter exercises"); /* prompt for homework */
while (i < NGRDS) { /* loop until NGRDS entered */
printf (" exc[%d] : ", i + 1); /* prompt for grade to enter */
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%f", &tmparr[i]) != 1) { /* add to tmparr/validate */
fputs (" error: invalid float input.\n", stderr);
continue;
}
else
i++; /* increment on good coversion */
}
for (i = 0; i < NGRDS; i++) /* sum homework grades */
sum += tmparr[i];
tmp.EXCavg = sum / NGRDS; /* find homework average */
fputs ("enter midterm: ", stdout); /* prompt midterm */
if (!fgets (buf, MAXC, stdin)) /* read midterm into buf/validate */
return 1;
if (sscanf (buf, "%f", &tmp.MidEx) != 1) { /* convert to float/validate */
fputs (" error: invalid float value.\n", stderr);
continue;
}
fputs ("enter final: ", stdout); /* prompt final */
if (!fgets (buf, MAXC, stdin)) /* read final into buf/validate */
return 1;
if (sscanf (buf, "%f", &tmp.FinEx) != 1) { /* convert to float/validate */
fputs (" error: invalid float value.\n", stderr);
continue;
}
Students[n++] = tmp; /* add tmp to Students array */
}
fputs ("\nstored values", stdout); /* output stored values */
for (size_t i = 0; i < n; i++)
printf ("\nname: %s\n id: %d\n HWavg: %.2f\n MidEx: %.2f\n FinEx: %.2f\n",
Students[i].name, Students[i].id, Students[i].HWavg,
Students[i].MidEx, Students[i].FinEx);
}
While it may appear long, much of the code is simple Validation of the user-input. Similar inputs will have similar code (which you may want to think about putting in a function), but every input needs to be fully validated.
NOTE: these are the MINIMUM validations necessary. Currently if an invalid id, MidEx or FinEx is entered, all values are discarded and you start again with the name input (you should loop continually until a valid int or float is received or the user cancels by generating a manual EOF by pressing Ctrl+d or Ctrl+z on windows -- that is left to you to implement)
Example Use/Output
$ ./bin/student_hw_mid_fin
enter name: Mickey Mouse
enter id: 1123
enter homework
hw[1] : 91
hw[2] : 88
hw[3] : 94
enter exercises
exc[1] : 84
exc[2] : 88
exc[3] : 93
enter midterm: 82
enter final: 94
enter name: Minnie Mouse
enter id: 1124
enter homework
hw[1] : 94
hw[2] : 95
hw[3] : foo
error: invalid float input.
hw[3] : 98
enter exercises
exc[1] : 93
exc[2] : 93
exc[3] : 95
enter midterm: 90
enter final: 89
enter name:
stored values
name: Mickey Mouse
id: 1123
HWavg: 91.00
MidEx: 82.00
FinEx: 94.00
name: Minnie Mouse
id: 1124
HWavg: 95.67
MidEx: 90.00
FinEx: 89.00
If you want to abuse the inputs a little more, the following would be handled without issue:
$ ./bin/student_hw_mid_fin
enter name: Pluto Dog
enter id: 1125 and he thinks he is the smartest dog on the planet!
enter homework
hw[1] : 75 chew toys
hw[2] : more chew toys please....
error: invalid float input.
hw[2] : 80 dog treats!
hw[3] : ice cream??
error: invalid float input.
hw[3] : 85 ................................. treats! ....................................
enter exercises
exc[1] : 70
exc[2] : eighty
error: invalid float input.
exc[2] : 80
exc[3] : 90
enter midterm: 85
enter final: 95
enter name:
stored values
name: Pluto Dog
id: 1125
HWavg: 80.00
MidEx: 85.00
FinEx: 95.00
Note how fgets() reads and then silently discards all the extraneous information entered. It can be done with scanf() -- but will require additional effort.
Look things over and let me know if you have further questions. Sometimes it helps to see it all put together.
A Few Additions to Add FinScr member, Write id, name, FinScr to File
The additions you need to make are fairly simple at this point. To add FinScr as a float member to your struct simply do:
struct Student {
char name[MAXN];
int id;
float HWavg; /* homework average */
float EXCavg; /* exercise average */
float MidEx; /* MidExam */
float FinEx; /* Final exam */
float FinScr; /* final weighted score for semester */
};
You can compute the FinScr member just before assigning tmp to Students[n++] = tmp; in your code, e.g.
tmp.FinScr = (0.15 * tmp.HWavg) + /* compute weighted-average */
(0.15 * tmp.EXCavg) +
(0.3 * tmp.MidEx) +
(0.4 * tmp.FinEx);
Students[n++] = tmp; /* add tmp to Students array */
}
To write to a file, you need a filename. (don't hardcode filenames in your code) The main() function takes argument int main (int argc, char **argv) which allows you to pass the filename to write to as the first argument to your program (or you can just write to stdout by default if no filename is given). For example, you can implement that as:
int main (int argc, char **argv) {
...
/* open filename provided on command line for writing (default stdout) */
FILE *grades = argc > 1 ? fopen (argv[1], "w") : stdout;
(otherwise you can take the filename as input from the user -- your choice)
Now you have an open file-stream named grades you can write to. To write the student id, name and FinScr to the file, where we are currently outputting the raw homework and exam grades, e.g.
/* output weighted avg for each student to file */
for (size_t i = 0; i < n; i++) {
fprintf (grades, "%4d %-20s Weighted Average: %.2f\n",
Students[i].id, Students[i].name, Students[i].FinScr);
}
if (grades != stdout) { /* file not stdout, close file */
if (fclose (grades) == EOF) /* always check close-after-write */
perror ("fclose-grades"); /* if any error, show the error */
else
printf ("grades written to file '%s'\n", argv[1]); /* show success */
}
(note: after writing data to a file, you always check that fclose() completed without error. It is you last chance to catch any problems with flusing the final data to your file that may not have been captured with earlier validations)
To avoid writing all the raw grades to the terminal, you can just wrap that code in a pre-processor conditional, I just gave it the name DEBUG which if you define and add that as part of your compile string (e.g. -DDEBUG) it will include that code in the final executable, but if you don't define it that code isn't included), example:
#ifdef DEBUG
fputs ("\nstored values", stdout); /* output stored values */
for (size_t i = 0; i < n; i++)
printf ("\nname: %s\n id: %d\n HWavg: %.2f\n MidEx: %.2f\n FinEx: %.2f\n",
Students[i].name, Students[i].id, Students[i].HWavg,
Students[i].MidEx, Students[i].FinEx);
#endif
(that is a simple way to conditionally include or exclude different parts of code)
Example Output File
Now calling your program with a filenames as the first argument and entering grades, e.g.
$ ./bin/student_hw_mid_fin dat/final_grades.txt
enter name: Mickey Mouse
enter id: 1123
enter homework
hw[1] : 91
hw[2] : 88
hw[3] : 94
enter exercises
exc[1] : 84
exc[2] : 88
exc[3] : 93
enter midterm: 82
enter final: 94
enter name: Minnie Mouse
enter id: 1124
enter homework
hw[1] : 94
hw[2] : 95
hw[3] : 98
enter exercises
exc[1] : 93
exc[2] : 93
exc[3] : 95
enter midterm: 90
enter final: 89
enter name:
grades written to file 'dat/final_grades.txt'
Will result in data written to that file as follows:
$ cat dat/final_grades.txt
1123 Mickey Mouse Weighted Average: 89.10
1124 Minnie Mouse Weighted Average: 91.00
You are free to change the format to anyting you like to meet your needs. That should get you going. Let me know if you have more questions (though they should probably be asked in a new question at this point :)
The complete code is now:
#include <stdio.h>
#include <string.h>
/* if you need a constant, #define one (or more) or use an enum */
enum { NGRDS=3, MAXS=10, MAXN=50, MAXC=1024 };
struct Student {
char name[MAXN];
int id;
float HWavg; /* homework average */
float EXCavg; /* exercise average */
float MidEx; /* MidExam */
float FinEx; /* Final exam */
float FinScr; /* final weighted score for semester */
};
int main (int argc, char **argv) {
size_t n = 0; /* counter */
char buf[MAXC]; /* array to hold each line from file (stdin here) */
struct Student Students[MAXS] = {{ .name = "" }}; /* array of struct */
/* open filename provided on command line for writing (default stdout) */
FILE *grades = argc > 1 ? fopen (argv[1], "w") : stdout;
while (n < MAXS) {
struct Student tmp; /* temporary struct */
int i = 0; /* general loop counter to use */
size_t len; /* length for string length */
float sum = 0., tmparr[NGRDS]; /* sum, array of NGRDS grades */
fputs ("\nenter name: ", stdout); /* prompt for name */
if (!fgets (buf, MAXC, stdin)) /* read name into buf/validate */
return 1;
if (*buf == '\n') /* [Enter] alone - input done */
break;
buf[strcspn (buf, "\n")] = 0; /* trim trailing '\n' */
if ((len = strlen (buf)) >= MAXN) { /* validate lenth of name */
fputs (" error: name too long.\n", stderr);
continue;
}
memcpy (tmp.name, buf, len + 1); /* copy buf to tmp.name */
fputs ("enter id: ", stdout); /* prompt id */
if (!fgets (buf, MAXC, stdin)) /* read id into buf/validate */
return 1;
if (sscanf (buf, "%d", &tmp.id) != 1) { /* convert to int/validate */
fputs (" error: invalid integer value.\n", stderr);
continue;
}
puts ("enter homework"); /* prompt for homework */
while (i < NGRDS) { /* loop until NGRDS entered */
printf (" hw[%d] : ", i + 1); /* prompt for grade to enter */
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%f", &tmparr[i]) != 1) { /* add to tmparr/validate */
fputs (" error: invalid float input.\n", stderr);
continue;
}
else
i++; /* increment on good coversion */
}
for (i = 0; i < NGRDS; i++) /* sum homework grades */
sum += tmparr[i];
tmp.HWavg = sum / NGRDS; /* find homework average */
i = 0; /* reset counter and sum */
sum = 0.;
puts ("enter exercises"); /* prompt for homework */
while (i < NGRDS) { /* loop until NGRDS entered */
printf (" exc[%d] : ", i + 1); /* prompt for grade to enter */
if (!fgets (buf, MAXC, stdin))
return 1;
if (sscanf (buf, "%f", &tmparr[i]) != 1) { /* add to tmparr/validate */
fputs (" error: invalid float input.\n", stderr);
continue;
}
else
i++; /* increment on good coversion */
}
for (i = 0; i < NGRDS; i++) /* sum homework grades */
sum += tmparr[i];
tmp.EXCavg = sum / NGRDS; /* find homework average */
fputs ("enter midterm: ", stdout); /* prompt midterm */
if (!fgets (buf, MAXC, stdin)) /* read midterm into buf/validate */
return 1;
if (sscanf (buf, "%f", &tmp.MidEx) != 1) { /* convert to float/validate */
fputs (" error: invalid float value.\n", stderr);
continue;
}
fputs ("enter final: ", stdout); /* prompt final */
if (!fgets (buf, MAXC, stdin)) /* read final into buf/validate */
return 1;
if (sscanf (buf, "%f", &tmp.FinEx) != 1) { /* convert to float/validate */
fputs (" error: invalid float value.\n", stderr);
continue;
}
tmp.FinScr = (0.15 * tmp.HWavg) + /* compute weighted-average */
(0.15 * tmp.EXCavg) +
(0.3 * tmp.MidEx) +
(0.4 * tmp.FinEx);
Students[n++] = tmp; /* add tmp to Students array */
}
/* output weighted avg for each student to file */
for (size_t i = 0; i < n; i++) {
fprintf (grades, "%4d %-20s Weighted Average: %.2f\n",
Students[i].id, Students[i].name, Students[i].FinScr);
}
if (grades != stdout) { /* file not stdout, close file */
if (fclose (grades) == EOF) /* always check close-after-write */
perror ("fclose-grades"); /* if any error, show the error */
else
printf ("grades written to file '%s'\n", argv[1]); /* show success */
}
#ifdef DEBUG
fputs ("\nstored values", stdout); /* output stored values */
for (size_t i = 0; i < n; i++)
printf ("\nname: %s\n id: %d\n HWavg: %.2f\n MidEx: %.2f\n FinEx: %.2f\n",
Students[i].name, Students[i].id, Students[i].HWavg,
Students[i].MidEx, Students[i].FinEx);
#endif
}
scanf()or can you use something better?mainfor areint main (void)andint main (int argc, char *argv[])(which you will see written with the equivalentchar **argv). See: C11 Standard - §5.1.2.2.1 Program startup(p1). See also: What should main() return in C and C++?