Reputation: 59
Hmm not sure if the title of this makes any sense. I'm creating a program that reads information from the file marks.txt (This is homework by the way so please bear with me I'm still wandering in the dark a bit :) )
marks.txt:
U08006 3 30 40 30
12000001 55 42 60
12000002 37 45 40
12000003 58 0 24
12000004 74 67 80
12000005 61 50 38
12000006 70 45 58
99999999
the first line is a module code followed by number of assignments and weighting of each assignment. The other lines are student numbers followed by marks for each assignment. And finally the end of the list is indicated by the student number 99999999.
EDIT: the program should eventually output the number of students and the average and standard deviation, for each assignment and overall module mark. Also I've been instructed to use an array of structures.
I have stored the module information in a struct (module), but when it comes to storing the student information I am really lost. I need an array of structs for the students but the problem is that I don't know how many students are in the file (well obviously I do right now but if I want my program to be able to read the file even though the number of students changes having a fixed size array isn't helpful).
I was thinking maybe I could count the number of lines in the file between the first and the last. and then use malloc to allocate memory for the array? am I on the right track here. so something like student = malloc(sizeof(student???) * number of lines)
this is really confusing me! dealing with pointers arrays structs and files all at once is really getting me all muddled up and this is due during my next class so can't ask my tutor for help. So any tips would be greatly appreciated.
my code so far in all it's "work in progress"ness: EDIT: ok so I've pretty much finished the program i.e. it does what it is suppose to but I'm still struggling with the whole malloc thing. for my student struct I want an array of structs and I want to allocate the memory after I determine the number of students but I'm not sure how I'm suppose to do this?! this whole pointer to array of structs I'm not quite getting it so the following code doesn't work. (if I have an array of struct student of predetermined size everything is peachy)
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int studentCount();
void getModuleDetails(FILE *fileptr);
void getStudentDetails(FILE *fileptr, int classSize);
void overallMarks(int classSize);
void cleanUp(FILE *fileptr, int classSize);
void printModuleStats(int classSize);
struct ModuleSpec {
char moduleCode[7];
int numOfAssign; /*number of assignments*/
int *assignWeighting; /*assignment weighting*/
} module; /*declare struct with variable name module*/
struct StudentMarks {
int studentNumber;
int *assignMark; //assignment mark
int moduleMark; //overall mark for module
}*student;
/*******************************************************************************************/
int main() {
int i; /*index*/
FILE *pFile;
int studentCounter = studentCount(); //count number of students in class
student = malloc(sizeof(student)*studentCounter); /*allocate memory for array of student structs*/
pFile = fopen("marks.txt", "r"); /*open file marks.txt for reading*/
if(pFile==NULL){ /*check if file was opened successfully*/
printf("File could not be opened!");
exit(EXIT_FAILURE);
}
getModuleDetails(pFile); //get module details from pFile and store in struct module
getStudentDetails(pFile, studentCounter); //get student details from pFile and store in student array fo structs
overallMarks(studentCounter); //calculate and print overall marks of students
printModuleStats(studentCounter);
cleanUp(pFile, studentCounter);
return 0;
}
/*****************************************************************************************/
int studentCount(){
FILE *pFile;
int temp;
int studentCount = 0;
pFile = fopen("marks.txt", "r"); /*open file marks.txt for reading*/
if(pFile==NULL){ /*if file can't be opened*/
printf("File could not be opened!");
exit(EXIT_FAILURE);
}
do{
temp = fgetc(pFile);
if(temp == '\n'){
studentCount++;
}
}while(temp != EOF);
studentCount -=2; /*subtract first and last lines to count only students*/
//printf("The number of students on this module is: %d\n", studentCount);
fclose(pFile);
return studentCount;
}
/*******************************************************************************************/
void getModuleDetails(FILE *fileptr){
int i;
int sumWeighting = 0;
fscanf(fileptr, "%s %d", module.moduleCode, &module.numOfAssign);
printf("Module code: %s\n", module.moduleCode);
printf("Number of assignments: %d\n", module.numOfAssign);
module.assignWeighting = malloc(module.numOfAssign * sizeof(int)); /*Allocate memory to hold assignment weightings*/
if (module.assignWeighting == NULL) { /*check if memory allocation was successful*/
printf("Memory allocation failed.");
exit(EXIT_FAILURE);
}
/*get weighting for each assignment and store in module.assignWeighting*/
for(i=0;i<module.numOfAssign;i++){
fscanf(fileptr, "%d", &module.assignWeighting[i]);
//printf("module %d %d\n", i+1, module.assignWeighting[i]);
sumWeighting += module.assignWeighting[i];
}
/*check if sum of weighting equals 100*/
if(sumWeighting != 100){
printf("Error: Sum of weighting = %d\n Expected sum: 100\n", sumWeighting);
}
}
/*********************************************************************************************/
void getStudentDetails(FILE *fileptr, int classSize){
int i;
int j;
for(i=0;i<classSize;i++){
student[i].assignMark = (int *)malloc(sizeof(int) * module.numOfAssign);
if(student[i].assignMark == NULL) { //check if memory allocation was successful
printf("Memory allocation failed.");
exit(EXIT_FAILURE);
}
fscanf(fileptr, "%d", &student[i].studentNumber);
/*get student assignment marks*/
for(j=0;j<module.numOfAssign;j++){
fscanf(fileptr, "%d", &student[i].assignMark[j]);
//printf("mark for assignment %d: %d\n", j+1, student[i].assignMark[j] );
/*check if mark is within range 0 to 100*/
if(student[i].assignMark[j]<0 || student[i].assignMark[j]>100){
printf("Error: Assignment mark is not within the range 0 to 100");
}
}
}
}
/************************************************************************************************/
void overallMarks(int classSize){
int i;
int j;
float temp;
for(i=0;i<classSize;i++){
printf("Overall mark for student %d: \n", student[i].studentNumber);
for(j=0;j<module.numOfAssign;j++){
temp += (float)(module.assignWeighting[j] * student[i].assignMark[j]) / 100;
}
student[i].moduleMark = temp + 0.5; /*add 0.5 for rounding as converting float to int rounds down*/
printf("%d%%", student[i].moduleMark);
if(student[i].moduleMark<25){
printf(" is a FAIL\n");
}
else if(student[i].moduleMark<40){
printf(" is a RESIT\n");
}
else if(student[i].moduleMark<55){
printf(" is a PASS\n");
}
else if(student[i].moduleMark<70){
printf(" is a MERIT\n");
}
else if(student[i].moduleMark<100){
printf(" is a DISTINCTION\n");
}
temp = 0;
}
}
/***********************************************************************************************/
void printModuleStats(int classSize){
int i;
int j;
float averageDevMarks;
float stdDevMarks;
float averageDevModule;
float stdDevModule;
printf("\nModule Statistics for %s\n", module.moduleCode);
printf("\nNumber of students: %d\n", classSize);
/*assignments*/
for(i=0;i<module.numOfAssign;i++){
printf("\nAssignment %d:\n", i+1);
for(j=0;j<classSize;j++){
averageDevMarks += student[j].assignMark[i];
}
averageDevMarks /= classSize;
printf("Average deviation: %f\n", averageDevMarks);
for(j=0;j<classSize;j++){
stdDevMarks += (student[j].assignMark[i] - averageDevMarks)*(student[j].assignMark[i] - averageDevMarks);
}
stdDevMarks = sqrt(stdDevMarks/classSize);
printf("Standard deviation: %f\n", stdDevMarks);
stdDevMarks = 0;
averageDevMarks = 0;
}
/*modules*/
for(i=0;i<classSize;i++){
averageDevModule += student[i].moduleMark;
}
averageDevModule /= classSize;
printf("\nAverage deviation for module mark: %f\n", averageDevModule);
for(i=0;i<classSize;i++){
stdDevModule += (student[i].moduleMark - averageDevModule)*(student[i].moduleMark - averageDevModule);
}
stdDevModule = sqrt(stdDevModule/classSize);
printf("Standard deviation for module mark: %f\n", stdDevModule);
}
/************************************************************************************************/
void cleanUp(FILE *fileptr, int classSize){
int i;
fclose(fileptr); /*close file*/
/*free previously allocated memory*/
free(student);
free(module.assignWeighting);
for(i=0;i<classSize;i++){
free(student[i].assignMark);
}
}
Upvotes: 0
Views: 1433
Reputation: 754450
You can either use a linked list instead of the array as Luchian Grigore suggests, or you can use dynamically allocated array of structures, or you can use a dynamically allocated array of pointers to dynamically allocated structures. The advantage of the latter is that there is less copying to do when the array is allocated; the disadvantage is that there are more memory allocations to release.
How does this translate into code? This is an outline of the 'dynamically allocated array of pointers to dynamically allocated structures'. I hypothesize a routine to read a single student's information; it will allocate a structure with the right number of marks too. Basically, it reads a line from the file (spotting EOF and returning a null pointer when the sentinel value is read). For a student, it allocates a student mark structure, and an array of integers of the right size. It parses the line for the student number and each grade. It can report an error if there are marks missing. It returns the pointer to the allocated student. I assume there are two separate allocations — one for the student marks structure and a separate one for the array of marks.
typedef struct StudentMarks Marks;
static Marks *read_student_marks(FILE *fp, int num_grades);
Marks **marks = 0; /* Array of pointers to student marks */
size_t num_marks = 0; /* Number of marks in use */
size_t max_marks = 0; /* Number of marks allocated */
Marks *student;
while ((student = read_student_marks(fp, num_grades)) != 0)
{
assert(num_marks <= max_marks);
if (num_marks == max_marks)
{
/* Not enough space left - allocate more */
size_t new_size = max_marks * 2 + 2;
Marks **new_marks = realloc(marks, new_size * sizeof(*marks));
if (new_marks == 0)
...handle out of memory error...
...NB: you still have the original array to work with...
marks = new_marks;
max_marks = new_size;
}
marks[num_marks++] = student;
}
This starts with a small allocation (2 entries), so that the reallocation code is exercised. I've also exploited the fact that if you pass a NULL pointer to realloc()
, it will allocate new memory. An alternative version would use malloc()
for the initial allocation and realloc()
thereafter:
size_t num_marks = 0;
size_t max_marks = 2;
Marks **marks = malloc(max_marks * sizeof(*marks));
if (marks == 0)
...handle out of memory condition...
while ((students = ...
If you're worried about having allocated too much space at the end of the loop, you can release the surplus with:
marks = realloc(marks, num_marks * sizeof(*marks));
max_marks = num_marks;
The release code is:
for (i = 0; i < num_marks; i++)
{
free(marks[i]->assignMark);
free(marks[i]);
}
free(marks);
marks = 0;
num_marks = 0;
max_marks = 0;
If you want to allocate the array of student marks, you need to decide how the student reader function is going to work. You can use the design outlined above; you'd copy the returned structure into the allocated array. You then release just the marks structure (but not the array of marks; that is still in use). You can grow the array in very much the same way as I did above. The release code is different (simpler), though:
for (i = 0; i < num_marks; i++)
free(marks[i]->assignMarks);
free(marks);
marks = 0;
num_marks = 0;
max_marks = 0;
ASCII art to the rescue — maybe...
The first code assumes you get a pointer to a set of marks for a student from the read_student_marks()
function. It keeps an array of pointers to those entries:
+----------+ +--------------------+
| marks[0] |-------->| marks for 12000001 |
+----------+ +--------------------+ +--------------------+
| marks[1] |------------------------------------>| marks for 12000002 |
+----------+ +--------------------+ +--------------------+
| marks[2] |-------->| marks for 12000003 |
+----------+ +--------------------+ +--------------------+
| marks[3] |------------------------------------>| marks for 12000004 |
+----------+ +--------------------+
Notice how the marks
array is contiguous, but the marks for each student are separately allocated. The code periodically reallocates the marks
array when it needs to grow, but does not move the individual marks for each student.
The alternative scheme outlined is like this:
+--------------------+
| marks for 12000001 |
+--------------------+
| marks for 12000002 |
+--------------------+
| marks for 12000003 |
+--------------------+
| marks for 12000004 |
+--------------------+
Here you would be reallocating all the memory, copying the complete marks structures around. At some levels, this is simpler than the scheme I outlined first.
Complicating this discussion is the fact that the 'marks for' structures are not as simple as I showed:
+--------------------------+
| studentNumber: 12000001 | +------+------+------+
| assignMark: *--------=----------->| 52 | 45 | 60 |
| moduleMark: 92 | +------+------+------+
+--------------------------+
If you have a C99 compiler, you could use a stucture with a 'flexible array member' for the last element of the structure; this would hold the marks:
struct StudentMarks
{
int studentNumber;
int moduleMark;
int assignMark[]; // Flexible array member
}
You would allocate this using a notation such as:
struct StudentMarks *marks = malloc(sizeof(*marks) +
num_assignments * sizeof(marks->assignMark[0]));
This allocates the student mark structure and the array in a single memory allocation. However, you cannot have an array of structures with a flexible array member; you can only use an array of pointers to a structure with a flexible array member (getting you back to the first code I showed).
In case you haven't already gathered, there is more than one way to do it — which is the motto of Perl (aka TMTOWTDI), but applies well here.
As some advice, I recommend drawing diagrams of pointers similar to those I made to help you understand what you are doing. After a while, they'll cease to be necessary, but diagrams can be a great help while you need them.
Upvotes: 3
Reputation: 258618
Pre-reading the number of students would require two passes through the file.
A cleaner solution would be to implement a linked list
, and add to it each time you read a new student.
Upvotes: 1
Reputation: 4290
Given some of the constraints on how you can write the program, here is an outline of one way to do it:
Read the module information, and put values into a module_struct which has the module_name and number_of_assignments (this does not need to be malloc'd)
malloc enough space for an array to hold each assignment_weighting, then put the weightings into that.
Scan the file, counting the number_of_students.
malloc enough space for an array of student_structs, number_of_students long
for each entry in the array of student_structs, malloc enough space to hold the assignment_marks, and point a field in the student_struct entry at that assignment_marks array
There should now be enough space to hold all of the values in the file
rewind the file to the start, either using fseek, or fclose and fopen it again.
scan past the module header
read each student row, assigning values into an entry of student_structs, and its assignment_marks
Do all of the calculations on the array of student_structs, and print the answers.
Is that enough information to get you moving?
Upvotes: 0