CS/CE 218 Lecture -*- Outline -*- Connection: so far we've looked at pointers to elementary data. In C, pointer variables can also be pointed at, and stored in arrays. This gives several techniques for dealing with higher order structures. * Higher order pointer structures advert: pointers to pointers, arrays of pointers, etc. come up quite frequently in C programs, in part because characters strings are arrays of characters. ** typedefs, a notation for making declarations more sensible (section 6.7) Q: What does typedef do? typedef creates abbreviations for type names -------------- typedef int bool; bool is_open = 1; typedef int feet; feet length, width; /* means int length, width */ length = 3; width = 3 + 2 -------------- like #define feet int Q: What are typedefs good for? Aesthetics, naming convenience, documentation: typedef char *string; string p; string argv[]; Parameterize program against changes e.g., typedef int size_t; Q: Does typedef define new types? Is the following legal? -------------- typedef int feet; typedef int kilometers; feet height = 1000; kilometers size = 5; height = size; -------------- so bool is the same as int, string is the same as char *, ... Q: What does typedef int IntArray[50]; mean? What about a declaration IntArray x, y[20], *z;? Give equivalents int x[50], y[20][50], (*z)[50]; Draw some pictures esp diff between z and y, and int *ap[50]; ** Pointer arrays (section 5.6) Arrays of pointers useful when dealing with variable sized data like strings *** example, a find_grade function (section 5.6) want to return a string, representing the letter grade achieved. so header is char * find_grade(...) **** Step 1: design an algorithm -------------------- for each grade in ("A", "A-", ...) if my weighted total score >= minimum weighted total for grade then return grade return "F" -------------------- **** step 2: What data do I need? need the letter grades as strings need the minimum weighted totals for each grade **** step 3: put the grade strings ("A", etc.) in an array ---------------------- /* number of different grades: Perfect, A, A-, B+, ..., D- */ #define NUM_GRADES 12 char* find_grade(double wtotal, ...) { static char *grade_strings[] = { "Perfect", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-"}; int i; for (i = 0; i < NUM_GRADES; i++) { if (wtotal >= weighted_total(...) { return(grade_strings[i]); } } return("F"); } ---------------------- Note: the declaration of grade strings, it's initialization. Draw the picture for it... Q: Is grade_strings an array of pointers or a pointer to an array? an array of pointers (see the precedence rules in Ch. 2, p. 53) Q: What would the above look like if we used a typedef char * string;? Q: What would the pointer version of the for loop look like? It's clear that this is right, except that we need to deal with the minimum grades. Q: What happens if the statemment grade_strings[1] = grade_strings[2]; is executed before the loop? draw the picture *** triple indirection, etc. Q: Can you draw pictures for the following int a[2] = {1,2}; int *b; int **c; int ***d; ? Q: Why is the definition int *e[]; illegal? Q: What does int f(int *g[]) mean? ** multi-dimensional arrays (section 5.7) continuing the above example... *** step 4: How to get the minimum grades? Have 12 grades and 4 kinds of grades (quizzes, labs, hw, exam) want weighted total of minimums for each grade... **** design 0: keep 12 weighted totals won't consider this further, as doesn't deal with excuses **** design 1: 4 arrays of 12 minimum grade totals each -------------------- #define PERFECT 0 char* find_grade(double wtotal, double quizzes[], double labs[], double hws[], double exams[]) { /* ... */ if (wtotal >= weighted_total( quizzes[i], quizzes[PERFECT], labs[i]+hws[i], labs[PERFECT]+hws[PERFECT], exams[i], exams[PERFECT])) { /* ... */ } } -------------------- Note: can define this as ... -------------------- char* find_grade(double wtotal, double *quizzes, double *labs, double *hws, double *exams) -------------------- and then write a pointer version of the loop (instead of indexing for perfect, use pointers) or declare it like that and still use array indexing... I didn't like having so many different arguments to find_grade also, for other reasons (looping in reading the scores...) I wanted an array of these minimum grades... so I considered **** design 2: a 4 by 12 array of minimum score totals -------------------------------- /* minimum grade arrays are such that the 0th element corresponds to a perfect score, the 1st element to the minimum score for an 'A', the 2nd element to the minumum score for 'A-', etc. */ typedef double mins_array[NUM_GRADES]; #define KINDS 4 enum kinds {QUIZ, LAB, HW, EXAM}; mins_array mins[KINDS]; -------------------------------- This last declaration is like (double [NUM_GRADES]) mins[KINDS]; /* illegal syntax... */ double mins[KINDS][NUM_GRADES]; so 4 elements, each of which is a 12 element (double) array DRAW A PICTURE FOR mins! Hint: it's not an 4-array of pointers... Q: Can you write mins[EXAM][i] as mins[EXAM,i]? no (see page 112) What does mins[i][j] mean? mins[i][j] == by precedence rules (as [] groups from left) (mins[i])[j] == by meaning of subscript [j] *(mins[i] + j) == as type of mins[i] is double [NUM_GRADES], i of these = i * NUM_GRADES doubles *(&mins[0][0] + NUM_GRADES * i + j) also *(mins[i] + j) == by meaning of subscript [i] *((*(mins + i)) + j) *** Passing multi-dimensional arrays (page 112) ------------------------ char* find_grade(double wtotal, mins_array mins[KINDS]) ---------------------- Q: In this context would it do just as well to declare mins as mins_array * mins; ? yes, it means the type of mins in find_grade is the same as double mins[KINDS][NUM_GRADES]; or double mins[][NUM_GRADES]; or double (*mins)[NUM_GRADES]; but NOT double * mins[NUM_GRADES]; that would be an array of 12 pointers to doubles i.e., mins+1 increments over NUM_GRADES doubles so mins[1] is twelve doubles past mins[0][0] and has type mins_array a statement like double *p = mins[1]; is legal and means double *p = &(mins[1][0]) i.e., double *p = &*mins[1] Q: Can the mins argument above be declared as double mins[][] or double * mins[]? NO! Why not? because mins[i][j] means *(mins + (column_width*i) + j) but don't know how big column_width is. ------------------------ char* find_grade(double wtotal, mins_array mins[KINDS]) { static char *grade_strings[] = { "Perfect", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-"}; int i; for (i = 0; i < NUM_GRADES; i++) { if (wtotal >= weighted_total( mins[QUIZ][i], mins[QUIZ][PERFECT], mins[LAB][i] + mins[HW][i], mins[LAB][PERFECT] + mins[HW][PERFECT], mins[EXAM][i], mins[EXAM][PERFECT])) { return(grade_strings[i]); } } return("F"); } ------------------------- ** Initialization of Pointer arrays (section 5.8) Q: Why don't you have to give the size of grade_strings above? ** Pointer vs. Multi-dimensional arrays (section 5.9) Q: What advantage is there to using an array of pointers vs. a two dimensional array of characters? Rows may be of different lengths => you don't have to count the lengths of the strings! (vs. Pascal) e.g. p. 113 ** Command line arguments (section 5.10) command line arguments come as strings in 2nd argument to main char **argv; count + 1 is in int argc; Draw the picture on page 115. Q: Can you complete the following table shell C ======================= $1 argv[1] $# $0 ? *** example (shpp.c) -------------------- #include #include "print_errors.h" int main(int argc, char *argv[]) { FILE *fp; void shpp(FILE *fp); program_usage_init(argv[0], "[file]"); if (--argc == 0) { shpp(stdin); } else if (argc == 1) { /* allow single - to stand for stdin */ if (strcmp(argv[1], "-") == 0) { shpp(stdin); exit(0); } /* argument should be a file name, not an option */ if (argv[1][0] == '-') { usage(); } if ((fp = fopen(argv[1], "r")) == NULL) { sys_err("cannot read %s", argv[1]); } shpp(fp); } else { usage(); } return(0); } -------------------- *** better ways to deal with options, use getopt(3) ------------------------ int main(int argc, char *argv[]) { extern int getopt(int argc, char **argv, char *optstring); extern int optind; /* ... */ char mins_file_name[256]; /* minimum grades file name */ FILE *minsf; /* file with minimum grades */ bool give_percentage = false; /* give percentage instead of grade? */ bool debug = false; /* produce debugging output? */ int i, c; program_usage_init(argv[0], "[-pd] [minimum-grade-file]"); while ((c = getopt(argc, argv, "pd")) != -1) { switch (c) { case 'p': give_percentage = true; break; case 'd': debug = true; break; default: usage(); break; } } if (argc - optind >= 2) { usage(); } if (argc - optind == 1) { strcpy(mins_file_name, argv[optind]); } else { strcpy(mins_file_name, DEFAULT_MINS_FILE); } if ((minsf = fopen(mins_file_name, "r")) == NULL) { sys_err("cannot read %s", mins_file_name); } /* ... */ } ------------------------ can call this with grade_estimate -p -d file grade_estimate -pd file grade_estimate -dp file grade_estimate -- -p (file is named -p) use "pdf:" if want a -f option with an argument and in that case can use grade_estimate -pffilename grade_estiamte -f filename -p grade_estimate -ffilename -p see also getopt(1) ** pointers to functions (section 5.11) a function is not a variable, but it is an object, so can point to it pointers to functions are first-class citizens, can be stored in arrays, passed to functions, etc. Q: What are some uses for pointers to functions? Important uses for pointers to functions: abstracting algorithms e.g., example in book, use different comparisions for differnent kinds of sorting (page 119...) table-driven designs at run-time, decide what to do, fetch function from table very important in window systems (select from menu) also a key idea in object-oriented programming ------------------ int iterate(int (*f)(int), int num, int x) /* effect: returns f(...(f(x))...) where f is applied num times */ { (num == 0) ? x ? f(iterate(f, num, x)); } /* example call: */ res = iterate(T, 3, 4); ------------------ f is the address of a function, saying f(iterate(&f, num, x)) would be an error, as would be passing a pointer to the variable f not it's value it's better in general not to put & in front of function names they are automatically coerced to their addresses Q: What's the difference between int (*f)(int) and int *f(int) ? the first declares f to be a pointer to a function ... the second declares f as a function that returns an int* ** generic pointers (type void*) (page 120) For defining general containers, procedures that can act on arguments of different types, a type that can be "anything" is needed But each basic type has a different size, so can't have a type any such that any x; x = 1; x = 3.14159; /* double */ makes sense But all pointers are the same size... so use a generic pointer type... pointers can be cast to void * and back without loss of information. -------------- void *generic[2]; int i = 27; double d = 3.14159; generic[0] = (void *) &i; generic[1] = (void *) &d; printf("%d %g\n", *((int *) generic[0]), *((double *) generic[1])); ------------- so can have non-homogeneous arrays, lists, etc. Q: Do you understand the qsort function on page 120?