| CSC 161 | Grinnell College | Spring, 2015 |
| Imperative Problem Solving and Data Structures | ||
C provides several mechanisms for grouping data together into logical units. Already, we have seen the use of 1-dimensional arrays to store and retrieve data of the same type using an index. In this module, we consider two additional mechanisms for collections: a struct to store data of different types, and 2-dimensional arrays to store tables. This lab introduces the struct concept; the next lab covers two-dimensional arrays, and the third lab puts the ideas of a struct and 2-dimensional arrays together to address the processing of image data.
C groups variables together using structs. Using a struct, you can simplify parameters, organize data, and protect information. Please refer to your textbook and the reading on structs for details regarding structs in C.
Conceptually, structs allow a programmer to group related data together; pragmatically, the programmer needs to be able to work with the collection of data at some times and with individual pieces at other times. Within a program, the use of structs draws upon basic syntax and semantics.
Before proceeding, be sure you understand the reading and examples listed above in the "Preparation before Class"!
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 exam;
}
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.
Add function computeSemAvg to test-scores-1.c. This function should take a struct 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;
Use computeSemAvg to add a column to the output of the program. This should include two parts (plus the definition of the function):
As an experiment, change the value of the test1 field within computeSemAvg to 120.0. Is this new value printed in printStudent? Do you think the parameter struct 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 procedure add10Percent that adds 10% to test2 of a student. The relevant procedure signature is:
void add10Percent (struct student * stu)
This function would be called within main with the address operator (&), with a call such as
add10Percent (&stu1);
Within add10Percent, remember to use the asterisk * to refer back to the original struct in main:
(*stu).test2 = ...
After printing the original records, call add10Percent for each of the four students, and then print their records again to check that the test2 scores have been changed.
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 awkward. As an alternative, program test-scores-2.c defines an array of struct students.
In this program:
Working with test-scores-2.c, copy functions computeSemAvg and add10Percent from test-scores-1.c. Also, add the revised printStudent procedure and the revised printf for the title.
void printMinMax (struct student stu [], int numStudents)where numStudents indicates the number of students in the stu array.
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;
} studentType;
This defines a new data type studentType 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 the typedef statement works.
After initializing the studentType array, use a loop to compute and store each student's semester average:
int i;
for (i = 0; i < 4; i++)
{
stu[i].semAvg = computeSemAvg (stu[i]);
}
Check that these computed averages are now printed by the program.
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;
} noteType;
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 (char * title, noteType 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, and numNotes specifies how many notes are in the song array.
Procedure playSong should use eSpeak to announce a song, using the given title. Then, playSong should go through the note 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 noteTupe arrays of at least 10 notes each. After declaring and initializing the song arrays, use playSong to announce and play each song.
Write a procedure to adjust the length of a note, using the following signature
void adjustNoteLength (noteType * note, double adjustment)
note refers back to the variable in main, so that a call to adjustNoteLength will change the original stored note. In processing, adjustment indicates the amount of change for a note's duration.
Within the program,
Development of laboratory exercises is an iterative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.