Lesson 3 Arrays and Pointers. You can allocate space for an array of elements at compile time with fixed dimension sizes of any data type, even functions and structs. So these are legal array definitions: char name[30]; /* An array of 30 signed characters. */ char *strings[50]; /* 50 pointers to strings. */ unsigned long int *(*func)()[20];/* An array of pointers to functions which */ /* return pointers to unsigned long ints. */ You can declare a pointer to point at any type of data element, and as in the array situation above functions and structs are included. struct ship { char name[30]; double displacement; /* in grammes */ float length_of_water_line; /* in meters */ unsigned short int number_of_passengers; unsigned short int number_of_crew; }; So using the ship concept from Lesson 2 you can declare a pointer to point at one of the ship structs in an array. struct ship *vessel_p; Note the use of the suffix "_p". This is my way of reminding myself that the variable is a pointer. struct ship fleet[5]; /* This allocates enough storage for 5 ships' info. */ Now lets set the pointer to point at the first vessel in the fleet. vessel_p = fleet; This pointer can be made to point at other ships in the fleet by incrementing it or doing additive arithmetic on it: vessel_p++; /* point a the next ship in the fleet array. */ vessel_p = fleet + 3; Also we can find out the index of the ship in the fleet at which we are pointing: i = vessel_p - fleet; It is also legal to find out the separation of two pointers pointing at elements in an array: d = vessel_p - another_vessel_p; /* This gives the separation in elements. */ So summarising, pointers may be, incremented, decremented, and subtracted one from another or have a constant subtracted from them. Any other mathematical operation is meaningless and not allowed. Assembler programmers should note that while the pointer variables contain a byte machine address, when the arithmetic is done using pointers the compiler also issues either a multiply or a divide as well as the add or subtract instruction so that the result is ALWAYS expressed in elements rather than bytes. Have a go and write yourself a trivial little program, and have a look at the compiler ouput code. Lesson 1 told you how! When using a pointer to reference a structure we have to use a "pointer offset" operator in order to access the member of the struct we require: vessel_p = fleet; vessel_p->name = "Queen Mary"; vessel_p->displacement = 97500000000.0; vessel_p->length_of_water_line = 750.0 vessel_p->number_of_passengers = 3575; vessel_p->number_of_crew = 4592; Remember: It's a "." when accessing a struct which is in storage declared in the program. It's a "->" when accessing a struct at which a pointer is pointing. Initialisation of arrays. 'C' has the facility to initialise variables in a program script. Some examples: char *qbf = "The quick brown fox jumped over the lazy dogs back"; int tic_tac_toe[3][3] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; struct ship fleet[2] = { { "Queen Elizabeth", 97500000000.0, 750.0, 3575, 4592 }, { "Queen Mary", 115000000000.0, 875.0, 4500, 5500 } }; Take a careful note of where the commas and semi-colons go ( and don't go )! Initialised Tables of Indeterminate Length. One nice feature 'C' offers is that it is able to calculate the amount of storage required for a table by 'looking' at the number of initialisers. char *verse[] = { "On top of the Crumpetty Tree", "The Quangle Wangle sat,", "But his face you could not see,", "On account of his Beaver Hat.", "For his Hat was a hundred and two feet wide.", "With ribbons and bibbons on every side,", "And bells, and buttons, and loops, and lace,", "So that nobody ever could see the face", "Of the Quangle Wangle Quee." NULL }; Note the * character in the definition line. This means that we are going to make an array of pointers to variables of type char. As there is no number between the [ ] characters the compiler calculates it for us. With this kind of set-up it is nice and easy to add extra information to the table as program development proceeds. The compiler will calculate the new dimension for you. The point to remember is that the program has to know - from the contents of the table - that it has come to the end of the table! So you have to make a special entry which CANNOT under any circumstances be a real data element. We usually use NULL for this. The other way is to calculate the size of the table by using the sizeof operator - Note that although use of sizeof looks like a function call it is in fact an intrinsic operator of the language. The result is available at compile time. So one can say:- #define SIZE_OF_VERSE sizeof verse There is one final initialised data type, the enum. It is a fairly recent addition to the language. enum spectrum { red, orange, yellow, green, blue, indigo, violet } colour; In this construct the first symbol is given the value of 0 and for each following symbol the value is incremented. It is however possible to assign specific values to the symbols like this: enum tub { anorexic = 65, slim = 70, normal = 80, fat = 95, obese = 135 }; Some compilers are bright enough to detect that it is an error if an attempt is made to assign a value to an enum variable which is not in the list of symbols, on the other hand many are not. Take care! In practice there is little difference between the enum language construct and a number of define statements except perhaps aesthetics. Here is another trivial program which demonstrates the use of enum and a pre-initialised array. #include enum spectrum { red, orange, yellow, green, blue, indigo, violet } colour; char *rainbow[] = { "red", "orange", "yellow", "green", "blue", "indigo", "violet" }; main() { for ( colour = red; colour <= violet; colour++ ) { printf ( "%s ", rainbow[colour]); } printf ( "\n" ); } The output of which is ( not surprisingly ): red orange yellow green blue indigo violet One quite advanced use of initialised arrays and pointers is the jump or dispatch table. This is a efficient use of pointers and provides a very much better ( In my opinion ) method of controlling program flow than a maze of case or ( heaven forbid ) if ( ... ) goto statements. Please cut out this program, read and compile it. ------------------------------------------------------------------------ char *ident = "@(#) tellme.c - An example of using a pointer to a function."; #include #include #include /* These declarations are not in fact needed as they are all declared extern in math.h. However if you were to use routines which are not in a library and therefore not declared in a '.h' file you should declare them. Remember you MUST declare external routines which return a type other than the int type. extern double sin (); extern double cos (); extern double tan (); extern double atof (); */ struct table_entry { char *name; /* The address of the character string. */ double (*function)(); /* The address of the entry point of the function. */ }; typedef struct table_entry TABLE; double help ( tp ) TABLE *tp; { printf ( "Choose one of these functions:- " ); fflush ( stdout ); for ( ; tp -> name; tp++ ) printf ( "%s ", tp -> name ); printf ( "\nRemember the input is expressed in Radians\n" ); exit ( 0 ); return ( 0.0 ); /* Needed to keep some nit-picking dumb compilers happy! */ } /* * This is the array of pointers to the strings and function entry points. * Is is initialised at linking time. You may add as many functions as you * like in here PROVIDED you declare them to be extern, either in some .h * file or explicitly. */ TABLE interpretation_table [ ] = { { "sin", sin }, { "tan", tan }, { "cos", cos }, { "help", help }, { NULL, NULL } /* To flag the end of the table. */ }; char *output_format = { "\n %s %s = %g\n" }; extern int errno; extern void perror(); main( argc, argv ) int argc; char **argv; { TABLE *tp; double x, answer; if ( argc > 3 ) { errno = E2BIG; perror ( "tellme" ); exit ( -1 ); } for (;;) /* This is the way to set up a continuous loop. */ { for ( tp = interpretation_table; ( tp -> name && strcmp ( tp -> name, argv[1] )); tp++ ) ; /* Note use of empty for loop to position tp. */ if ( tp -> function == help ) (*tp -> function )( interpretation_table ); if ( tp -> name == NULL ) { printf ( "Function %s not implemented yet\n", argv[1] ); exit ( 1 ); } break; /* Leave the loop. */ } x = atof ( argv[2] ); /* Convert the character string to a double. */ answer = ( *tp -> function )( x );/* Execute the desired function. */ printf ( output_format, /* Pointer to printf()'s format string. */ argv[1], /* Pointer to the name of the function. */ argv[2], /* Pointer to the input number ascii string. */ answer /* Value ( in double floating point binary ) */ ); } Copyright notice:- (c) 1993 Christopher Sawtell. I assert the right to be known as the author, and owner of the intellectual property rights of all the files in this material, except for the quoted examples which have their individual copyright notices. Permission is granted for onward copying, but not modification, of this course and its use for personal study only, provided all the copyright notices are left in the text and are printed in full on any subsequent paper reproduction. -- +----------------------------------------------------------------------+ | NAME Christopher Sawtell | | SMAIL 215 Ollivier's Road, Linwood, Christchurch, 8001. New Zealand.| | EMAIL chris@gerty.equinox.gen.nz | | PHONE +64-3-389-3200 ( gmt +13 - your discretion is requested ) | +----------------------------------------------------------------------+