Grouping Data with struct
Example 1: Test Scores and Averages
For a course that involves three tests and a final exam, the record for a single student might have the form:
struct student
{
char name[20];
double test1;
double test2;
double test3;
double final;
};
Consider the program
test-scores-1.c
which declares four student variables, initializes the variables, and
prints their records.
-
Copy
test-scores-1.c
to your account, compile and run it, and review how it works.-
Where is
struct student
declared? Why do you think the declaration comes before procedureprintStudent
and beforemain
? -
How are the fields of variable
stu1
initialized? -
Why do you think
strcpy
is used to initialize thename
field forstu4
? -
In printing the output, how are the titles
and
printf
statements coordinated, so that the test scores appear in aligned columns? -
The format
%-20s
is used to print the name of a student. What does the minus sign accomplish? (Hint: What happens if the minus sign is removed?)
-
Where is
-
Add function
computeSemesterAverage
totest-scores-1.c
. This function should take astruct student
as a parameter and return (not print) the weighted average that counts each test with a weight of 1 and the final exam with a weight of 2. That is, the semester average should be computed as;(test1 + test2 + test3 + 2*final) / 5.0;UsecomputeSemesterAverage
to add a column to the output of the program. This should include two parts (plus the definition of the function):-
expand the
printf
statement inprintStudent
to include another value—the average for the student, and -
expand the printing of the title in
main
to label the new column.
-
expand the
-
As an experiment, change the value of the
test1
field withincomputeSemesterAverage
to 120.0. Is this new value printed inprintStudent
? Do you think the parameterstruct student stu
references the original data or makes a copy? Explain. -
To change a value in
main
, step 3 illustrates that one must pass the address of struct, so the parameter will refer back to the original value. Write a procedureaddTenPercent
that adds 10% totest2
of a student. The relevant procedure signature is:
This function would be called withinvoid addTenPercent (struct student * stu)
main
with the address operator (&
), with a call such as
WithinaddTenPercent (&stu1);
addTenPercent
, remember to use the asterisk*
to refer back to the original struct inmain
:
After printing the original records, call(*stu).test2 = ...
addTenPercent
for each of the four students, and then print their records again to check that the test2 scores have been changed.
An Array of Student Scores
Although test-scores-1.c
was satisfactory for four
students, the declaration of a different variable (e.g., stu1,
stu2, stu3, stu4
) for each student is tedious. As an
alternative, program
test-scores-2.c
defines an array of struct student
s.
In this program:
-
students
represents the entire array, -
students[0]
,students[1]
,students[2]
,students[3]
specify the individual records for each of the four students, -
students[0].test1
,students[1].test1
,students[2].test1
,students[3].test1
refer to the test1 scores for each of the students.
-
Working with
test-scores-2.c
, copy functionscomputeSemesterAverage
andaddTenPercent
fromtest-scores-1.c
. Also, add the revisedprintStudent
procedure and the revisedprintf
for the title.-
Compile and run the updated
test-scores-2.c
, and check that the output is the same as you obtained fromtest-scores-1.c
. -
Add at least six more student records, so that
the
students
array contains information for at least ten students. -
Write a procedure
printMinMax
that computes and prints the maximum and minimum semester averages for the entire class. Do NOT assume that all averages will be between 0.0 and 100.0, but rather initialize your search for a maximum and minimum with the averages of the first student. The signature of this procedure should be
wherevoid printMinMax (const struct student students[], int numStudents)
numStudents
indicates the number of students in thestudents
array. -
Modify
printMinMax
so that it prints the maximum and minimum semester averages, but also the names of the students with those averages. -
Modify
test-scores-2.c
so that 10% is added to each student's score for test 2, using theaddTenPercent
procedure. This adjustment of student scores should occur after initialization, but before scores are printed or averages computed.
-
Compile and run the updated
typedef
statements
When working with test-scores-1.c
and test-scores-2.c
, you may have found it somewhat
tedious to write struct student
in the declaration of
every variable and parameter. To simplify this syntax, C allows
programmers to define new types. In this case, we might write
typedef struct
{
char name[20];
double test1;
double test2;
double test3;
double exam;
} student_t;
This defines a new data type student_t
that you can use
freely within your program with no further explicit mention of
the keyword struct
.
-
Copy
test-scores-3.c
to your account, compile and run it, and review how thetypedef
statement works.-
What happens if you move
the
typedef
declaration after the definition ofprintStudent
? -
Append an additional field,
semesterAvg
, to thestudent_t
definition, but leave the initialization as it is. Does the program compile and run? -
Add printing of the average member
to
printStudent
, and observe what value is printed forstu.semesterAvg
. How is a field initialized, if other fields of astruct
are initialized, but all fields are initialized?
-
What happens if you move
the
-
After initializing the
student_t
array, use a loop to compute and store each student's semester average:
Check that these computed averages are now printed by the program.for (int i = 0; i < 4; i++) { students[i].semesterAvg = computeSemesterAverage(students[i]); }
Musical Notes
Consider a song as a sequence of notes, each of which has a pitch and a duration. Playing a song might including announcing the song's title and then playing the notes. In this context, a note might have the following specification:
typedef struct
{
int pitch;
double duration;
} note_t;
-
Write a program to announce two songs and play them, organizing
your program in two parts:
-
Write a procedure to play a song, using the following
signature:
void playSong (const char * title, note_t song[], int numNotes)
In this signature, the
title
is a string giving the title of the song,song
is the sequence of notes as an array, andnumNotes
specifies how many notes are in thesong
array.Procedure
playSong
should useprintf
to announce a song, using the given title. Then,playSong
should go through thenote_t
sequence, telling the Scribbler 2 robot to play each note with the designated pitch and frequency. -
Write a main program containing at least two songs, defined
as
note_t
arrays of at least 10 notes each. After declaring and initializing the song arrays, useplaySong
to announce and play each song.
-
Write a procedure to play a song, using the following
signature:
-
Write a procedure to adjust the length of a note, using the following
signature
void scaleNoteLength (note_t * note, double factor)
note
refers back to the variable inmain
, so that a call toscaleNoteLength
will change the original stored note. In processing,factor
indicates the multiplier for a note's duration. For example:-
When
factor
is 0.5, the duration of the note will be changed to half as long as the original duration. -
When
factor
is 1.0, the duration of the note will be unchanged. -
When
factor
is 2.0, the duration of the original note will be doubled.
- play each song once, as initialized.
-
call
scaleNoteLength
for the first song with an adjustment of 0.5, so each note will be shorter. Then play the song usingplaySong
, so the entire song will be completed in half the time of the original. -
call
scaleNoteLength
for the second song with an adjustment of 2.0, so each note will be longer. Then play the song usingplaySong
, so the entire song will require twice the time of the original.
-
When