Frank arrives at his job first, and starts figuring out the format of the data about songs. The data is all in one file called Music.txt. Go ahead and take a look at it. It is a text file, where each line is composed of six words:
Song-name Duration(m:ss) Artist Album-Name Genre Track-NumberNo words have spaces in them -- where there would normally be a space, there is instead an underscore. For example, here are the first five lines:
Back_In_Black 4:15 AC_DC Back_In_Black Rock 6 Larks'_Tongues_In_Aspic,_Part_III 5:56 King_Crimson Three_of_a_Perfect_Pair Rock 9 Ravel,_Menuet_Antique 5:28 Casadesus,_Robert Ravel,_Complete_Piano_Music,_Disc_2 Classical 8 Pungee 3:02 Meters,_The Look-Ka_Py_Py Rock 3 Naima 4:24 Coltrane,_John Giant_Steps Jazz 6 |
Note the underscores instead of spaces.
Now, Frank decides to get a head start on his work and write a program to read in the data and print out some information about it -- total number of songs and average song duration. Also he will print the same same information about Rock songs, Classical songs, and all the others. It's a straightforward program that uses the fields library. It uses strcmp() and field #4 (zero indexed) to identify the Rock and Classical songs, and it uses atoi(), strchr() and field #1 to determine song durations. It is in genreport.c.
Take a special look at how it determines duration:
time = atoi(is->fields[1])*60; x = strchr(is->fields[1], ':') + 1; time += atoi(x); |
The first atoi() gets the number of minutes, then multiplies it by 60. The strchr() finds the colon, and sets x to point to the first character after the colon. The last command uses atoi() to determine the number of seconds and adds it to time.
This runs fine. Like a good programmer, Frank double checks himself a little, by testing it on the first two lines. Since the first song has a duration of 3:05 and the second has a duration of 3:15, their average duration should be 3:10, or 190 seconds:
UNIX> head -2 Music.txt Back_In_Black 4:15 AC_DC Back_In_Black Rock 6 Larks'_Tongues_In_Aspic,_Part_III 5:56 King_Crimson Three_of_a_Perfect_Pair Rock 9 UNIX> head -2 Music.txt | genreport Total Songs: 2 Avg Duration: 305.50 Rock Songs: 2 Avg Duration: 305.50 Class Songs: 0 Avg Duration: nan Other Songs: 0 Avg Duration: nan UNIX> genreport < Music.txt Total Songs: 9126 Avg Duration: 277.42 Rock Songs: 3340 Avg Duration: 247.67 Class Songs: 3703 Avg Duration: 310.48 Other Songs: 2083 Avg Duration: 266.34 UNIX>Frank double-checks the last execution of genreport: 3340+3703+2083 = 9126. Moreover: (3340*247.67)+(3703*310.48)+(2083*266.34) = 2531711, and 9126*277.42 = 2531734. That's close enough for me (they'd match were we to print the averages to four decimal places.
First take a look at that header file:
typedef struct { double nsongs, ttime; double ncsongs, tctime; double nrsongs, trtime; double nosongs, totime; } Music_Info; *Music_Info read_music_file(char *filename); |
You'll see that the the relevant information is now bundled up in a struct whose name is Music_Info. Frank must convert his code to read filename instead of standard input, and allocate/return a Music_Info struct. Luther calls read_music_file(), and then prints out a report about the file in HTML. The makefile makes sure that all three files (frank.c, luther.c and flcommon.h) compile together correctly. Copy it all over and try it out:
UNIX> make fl gcc -g -c -I/home/plank/cs140/include luther.c gcc -g -c -I/home/plank/cs140/include frank.c gcc -O -o fl luther.o frank.o /home/plank/cs140/objs/libfdr.a UNIX> fl > report.htmlThe output is here. I will say that I made Luther's code ugly on purpose. If you think that Luther is overpaid at this point, you're probably correct, but it's hard to find good help these days....
Here's luther2.c, which is the same as luther.c, except for the bold-faced stuff lines:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "flcommon.h" main() { Music_Info *mi; mi = read_music_file("Music.txt"); mi->nsongs = mi->ncsongs; mi->ttime = mi->tctime; printf("<h2>Classical Music Data</h2>\n"); printf("<UL>\n"); printf("<LI> Total number of songs: %.0lf\n", mi->nsongs); printf("<LI> Average song duration: %.0lf seconds: (%d:%02.0lf)\n", mi->ttime/mi->nsongs, ((int) (mi->ttime/mi->nsongs))/60, mi->ttime/mi->nsongs - (((int) (mi->ttime/mi->nsongs))/60)*60); printf("</UL>\n"); } |
When you run fl2, the output looks as follows (in report2.html).
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "flcommon3.h" void print_report(Music_Info *mi) { mi->nsongs = mi->ncsongs; mi->ttime = mi->tctime; printf("<h2>Classical Music Data</h2>\n"); printf("<UL>\n"); printf("<LI> Total number of songs: %.0lf\n", mi->nsongs); printf("<LI> Average song duration: %.0lf seconds: (%d:%02.0lf)\n", mi->ttime/mi->nsongs, ((int) (mi->ttime/mi->nsongs))/60, mi->ttime/mi->nsongs - (((int) (mi->ttime/mi->nsongs))/60)*60); printf("</UL>\n"); } |
The third programmer now writes report3.c, which appends the total number of songs to the report.
#include <stdio.h> #include <stdlib.h> #include "flcommon3.h" main() { Music_Info *mi; mi = read_music_file("Music.txt"); print_report(mi); printf("\n"); printf("Total number of songs (classical and non-classical): %.0lf\n", mi->nsongs); } |
The output, as you can see, is incorrect, because Luther changed mi->nsongs. It is here.. The total number of songs should be 9126, not 3703.
What happens is that Frank now moves the Music_Info struct to frank4.c, instead of having it in the header file. The header file instead returns a (void *) from read_music_file(). When frank4.c returns from read_music_file(), it returns a pointer to its Music_Info struct, but it casts it to a (void *). This means that Luther can't do anything with it. Frank also augments the header file with eight accessor functions:
double get_nsongs(void *mi); double get_ttime(void *mi); double get_ncsongs(void *mi); double get_tctime(void *mi); double get_nrsongs(void *mi); double get_trtime(void *mi); double get_nosongs(void *mi); double get_totime(void *mi);
Frank implements these simply:
double get_nsongs(void *mi) { Music_Info *m; m = (Music_Info *) mi; return m->nsongs; } double get_ttime(void *mi) { return ((Music_Info *) mi)->ttime; } double get_ncsongs(void *mi) { return ((Music_Info *) mi)->ncsongs; } double get_tctime(void *mi) { return ((Music_Info *) mi)->tctime; } double get_nrsongs(void *mi) { return ((Music_Info *) mi)->nrsongs; } double get_trtime(void *mi) { return ((Music_Info *) mi)->trtime; } double get_nosongs(void *mi) { return ((Music_Info *) mi)->nosongs; } double get_totime(void *mi) { return ((Music_Info *) mi)->totime; } |
The implementation of get_nsongs() is pretty standard. The (void *) is cast back to a Music_Info *, and then the proper field of the Music_Info is returned. The remaining seven accessor functions are implemented in one line, performing the cast in the return statement.
Now, Luther must modify print_report() to work with a (void *). Note, he cannot modify nsongs like he did before. He may only access it. It took him a few hours, but he did eventually get it right (in luther4.c). Similarly, the code in report4.c works with a (void *) and the accessor functions. And the output (in report4.html) is now correct.
In CS102, you saw accessor functions defined in C++ classes. They are indeed an integral part of any object-oriented programming methodology.
Typical proponents of C++ dislike the use of (void *)'s, because they circumnavigate the type system and are rather unsafe (there's nothing illegal about casting a (void *) to any struct -- which will likely destroy your program). They prefer templates, inheritance, and specification of variables as private, public and protected to achieve the same purpose. Both methodologies (information hiding with void *'s in C, information hiding with protected variables in C++) ht have merit. However, you will find that:
In this class, we will use the C methodology, and in CS302, we will revert to the C++ methodology.