CMSC202 Lecture Notes for week 4

During the week of Feb. 22, we discussed the following:

Pointers

A pointer is a variable which, rather than holding the value of an object, holds its location. So, the declaration
    //example 1 
    int * pi;
says that *pi is of type integer, therefore, pi is of type pointer to integer. Note that the * associates with (in a manner of speaking)with int, not with pi. so that
    //example 2 
    int * pi, p2;   //oops! p2 is an int, not a pointer to an int
has the effect of making pi a pointer, but p2 is an integer!

To avoid ambiguity in declaring pointers, we use type definitions to create pointer types. So, instead of example 2, we code

    //example 3
    typedef int * int_ptr;
    int_ptr pi, p2;      // now, p2 is a pointer

Pointer Arithmetic

You are responsible for all of the discussion in the text, even though we discussed only parts of the material in lecture

Adding or subtracting an int from a pointer is legal, and has the effect of causing the pointer to point to the next object of the same type as the pointer. So,

    //example 4
    int A[10];
    int_ptr pA = A;   //pA points to the zero element of the array
pA+1 is the address of the array element A[1]. In general, pA+i = &(A+i), and *(pA+i) = A[i].

When passing an array as a parameter to a function, the array name is demoted in the function to a pointer. So -

    //example 5
    int foo(int a[], int size);  // prototype
    ...  in some function
    int Array[10];
    z = foo(Array, 10);
inside function foo, a is treated as type pointer to integer.

MultiDimensional Arrays

All arrays in C++ are 1-dimensional (really!). A 2-dimensional array is really an array, each of whose elements are 1-dimensional arrays. The declaration int TwoDim [10][20] creates 10 arrays. Those 10 arrays are themselves arrays of size 20 int's. TwoDim[0] is a 20-element array. Initializing an array when it is declared, therefore, requires some care.
    //example 6
    int A[2][3] = { {1,2,3}, {4, 5,6} };
The declaration of A is as 2 arrays, each of which is a 3-element array. Hence, the declaration shows A gets initialized with 2 elements.

The compiler will figure out the size of an initialized array for us, if we wish.

    //example 7
    char B[ ][2] = { {'x', 'y'}, {'z','W'}, {'?','!'} };
creates B as an array with 3 elements, or an array of size [3][2].

(as a reminder from 201 days, you can get the number of elements in an array by this little trick: int asize = sizeof A / sizeof A[0]; .)

Function pointers

A pointer may also point to a function. This has the particular advantages of allowing us to use function pointers as parameters in function calls. In this example
    //example 8
    int apply( int (*fp)(int), int x) {
            return (*fp)(x);
    }
the function "apply" takes 2 arguments. The first argument is a pointer to a function that takes an integer and returns an integer. The second arg. is an integer, and function apply just invokes the function pointed to by fp with argument x.

As with example 3, we can create pointer-to-function types, e.g.
typedef int (*fp)(int);
to cause fp to assume type "pointer-to-function returning an int and taking 1 int".

This construct allows us to enforce a strong abstraction barrier between different abstractions. For example, suppose that I were to write a function to build a table of values:
#include <iostream.h>
#include <iomanip.h>
void tabulate(char * header, fp fun, int low, int high, int step) {
cout << header << endl;
int x;
for (x = low; x <= high; x+=step)
cout <<setw(8) << x << setw(10) <<" " <<(*fun)(x);
}
Note that the construction of the table does not depend on what function I'm putting in the table. I might code a function int sq(int x) { return x*x;}, or a function int cube (int x){return x*x*x'} . To print a table of cubes of multiples of 5 between 5 and 50, I just write: tabulate("cubes",cube, 5, 50, 5) . To do a table of squares of the same values, I code tabulate("squares",sq, 5, 50, 5) . (pretty useful!)

the system function qsort

This guy is found in the standard library <stdlib.h%gt; and exemplifies the idea of abstracting out the quick sort function from almost all details of the array being sorted.

The problem in writing a general-purpose sorting routine is that C++ is strongly-typed, and when we try to pass an array of type T as a function parameter, we know that what gets passed is a pointer to an object of type T. It would be wonderful to instruct C++ to ignore the type, because we know what to do with the object, and we really do not care to invoke strong type checking just now (Hmmm. do you see the potential problem this creates?). We have such a mechanism, the void pointer, void *. Typecasting any pointer to type void * means:

  1. ignore the type of the object being cast
  2. I promise never to dereference this thing as a void *. I will always cast it to a data type before dereferencing the thing myself.
The function prototype for qsort is
   void qsort( void * A, size_t nelem, size_t width, int (*comp)(void *, void *));
(size_t is essentially an unsigned int)
qsort solves the generality problem by asking the user to supply the number of elements (nelem) in the array to be sorted, the size of each such element, and a pointer to a function, comp, which decides which of 2 array elements is to precede the other.

An example would make these points more solid.

    //example 9
    int Itab[50];
    int compare_ints(void * a, void * b) {
            return ( *(int *)a - *(int * ) b);
    }  
    ....
    qsort(Itab, 50, sizeof(int), compare_ints);
    ...
It is essential to note that the compare function, comnpare_ints, must conform to the prototype int (*compare_function)(void *, void *) for qsort to work correctly.

We can understand the body of compare_ints as follows: the function's 2 arguments are pointers to array elements, so we must dereference them to compare them. BUT, since these arguments are void pointer (void *), we must also typecast them to actual C++ data types or to programmer-devised types. The expression *(int*)a means just that. Since the typecast and dereferencing items are right-associative, (i.e. they associate from right to left), and the precedence of typecasts is the same as that of the dereference operator, the expression means "first, cast a to be a pointer to int, and dereference it."

You might think how to write a comparison function for strings (hint: use strcmp) or for this class, where the sorting order is last name, first name, middle initial.

    class Student {
    friend  int Compare_Student(void *, void *); 
    private:
      char * FirstName;
      char * MiddleInit;
      char * LastName;
      ... more stuff
    public:
      ... even more stuff
};

Previous Lecture
Next Lecture


Last Modified: 6 Mar 1999 12:39:55 EST by Mitch Edelman edelman@cs.umbc.edu

Back up to Spring 1999 CMSC 202 Section Homepage