1

I wonder if anyone could kindly help me out? I have the following basic struct that holds subject scores for students:

typedef struct student{
   char name[25];
   int  maths, 
        science, 
        english, 
        age;  
} student; 

I've created an array of this type and populated with data. I wrote 3 functions that work out the highest and lowest scores per subject. These functions are almost identical, but the only difference is the subjects they're working on, for example to work out min/max scores for maths

void highestAndLowestMaths(student s1[]){  

   int highest = s1[0].maths, highestPos = 0,
       lowest  = s1[0].maths, lowestPos  = 0; 

   for( int x = 1; x < MAX_RECS; x++ ){
      if( s1[x].maths > highest ){
         highest    = s1[x].maths;
         highestPos = x;
      }
      if( s1[x].maths < lowest ){
         lowest    = s1[x].maths;
         lowestPos = x;
      }      
   }
   // then display details of highest/lowest in maths etc..
   

The other 2 functions for science and english are identical and the only part of the code that needs to change is s1[x].maths to s1[x].science and s1[x].english respectively.

So rather than write 3 separate and almost identical functions, could this not be achieved by altering the s1[x].maths/science/english section of code, that would update depending on another parameter passed?

The nearest I got to solving this involved sending a char array:

void allHighestLowest(student s1[], char subject[]){
   
   int highest = -1,  highestPos = 0, 
       lowest  = 101, lowestPos  = 0, 
       option;

   if(strcmp(subject, "MATHS") == 0){
      option = 0;      
   } else if(strcmp(subject, "SCIENCE") == 0){
      option = 1;      
   } else if( strcmp(subject, "ENGLISH" ) == 0){
      option = 2;      
   } else {
      printf("Invalid subject:\t[%s]\nExiting...\n", subject); 
      exit(1);
   }

   int member[3]; 

   for(int x = 0; x < MAX_RECS; x++){
      
      member[0] = s1[x].maths;
      member[1] = s1[x].science;
      member[2] = s1[x].english;
      
      if(member[option] > highest){
         highest    = member[option];
         highestPos = x;
      }

      if(member[option] < lowest){
         lowest    = member[option];
         lowestPos = x;
      }      
   }
   // then display details of highest/lowest in chosen subject etc..

Although it works, I'm sure there has to be a better way to dynamically create the line of code to s1[x].maths/science/english during runtime, rather than using a temp array to grab the values as shown above? The struct itself could change and have many more members added, so I'm looking for the best solution, rather than duplicating functions and code.

Can anyone point me in the right directions? Thanks in advance!

  • `int highest = s1[0]` is very wrong. You're assigning a variable with type `int` to a value of type `struct student` – klutt Mar 13 '21 at 19:13
  • Maybe you could make getter fonctions to access to struct member and use those as fonction pointers. Take a look at [how-do-function-pointers-in-c-work](https://stackoverflow.com/questions/840501/how-do-function-pointers-in-c-work) – Missu Mar 13 '21 at 19:23
  • @klutt corrected code in 1st function, not sure how it didnt copy across. Missu, will take a look, thanks – frank fbiman Mar 13 '21 at 20:53

2 Answers2

1

This is the kind of thing that simply doesn't work well in C. The C language is intended for low level programming, and not high level data abstractions.

You could do something like this:

enum subject { MATH, SCIENCE, ENGLISH };

typedef struct student{
   char name[25];
   int  subject[3];
   int  age;  
} student; 

and then something like this:

void highestAndLowest(student s1[], enum subject s){  

   int highest = s1[0].subject[s], 
       lowest = highest,           // Removed code duplication
       highestPos = 0,
       lowestPos  = 0; 

   for( int x = 1; x < MAX_RECS; x++ ){
      if( s1[x].subject[s] > highest ){
         highest    = s1[x].subject[s];
         highestPos = x;
      }
      if( s1[x].subject[s] < lowest ){
         lowest    = s1[x].subject[s];
         lowestPos = x;
      }      
   }

   ...

If you want to do this as neatly as in Python, then code Python.

klutt
  • 30,332
  • 17
  • 55
  • 95
1

One way of doing this is to provide an accessor function as a parameter to allHighestLowest:

typedef int (*fn_getter)(student *s);

int get_maths(student *s) { return s->maths; }
int get_science(student *s) { return s->science; }
int get_english(student *s) { return s->english; }

void highestAndLowest(student s1[], fn_getter getter)
{
    // code here
}

Now, instead of accessing maths, science, and english directly you do:

if (getter(&s1[x]) > highest) {
    highest = getter(&s1[x]);
    highestPos = x;
}
if (getter(&s1[x]) < lowest) {
    lowest = getter(&s1[x]);
    lowestPos = x;
}

So instead of calling highestAndLowestMaths(students_list) you now do highestAndLowest(students_list, get_maths).

Note: I wrote the example code without having access to a compiler so it might contain some small errors or typos.

For details about function pointers see this question.

icebp
  • 1,608
  • 1
  • 14
  • 24