First Next Previous Last Glossary About

Further work with pointers and dynamic objects

Introduction

 int * i;
 char * c;

In the lesson on pointers (many moons ago) I introduced the concept of dynamic variables. A dynamic variable is a variable created at run-time, the compiler doesn't allocate memory at compile time. A program which uses dynamic variables just declares a variable of type pointer to the data type that will be created. In the examples shown here you can see that:

You can review the first lesson pointers here if you need to.

Creating new variables

New and Delete

We have already used new and delete in the earlier lesson. We use them again here but with some more significant data structures.

typedef struct Clients	
{ string sname;
  string fname;
  float due;
};

Assume we have a type definition as shown here. We can declare static variables of this type:

 Clients client1;

and we can reference the fields of structure this way:

 client1.sname = "Smith";
 client1.fname = "Bob";
 client1.due = 23.45;

Alternatively we can use new to create a dynamic variable of type Clients:

 Clients client1;
 Clients * client2;

The variable client2 is pointer to a variable of type Clients. To create the data structure we can use new:

 client2 = new Clients;

Since client2 is a pointer we need to dereference it in order to access the fields in the structure pointed to.

 (*client2).sname = "Crusoe";
 (*client2).fname = "Robinson";
 (*client2).due = 3.45;

Note how I use the parentheses to dereference the pointer. The field or member operator . has a higher order of precedence than the dereference operator * so we must force the order of evaluation. The compiler will baulk at:

 *client2.fname = "Crusoe";

There is a shorthand way of making these kinds of references using the pointer or structure indirection operator which saves some typing.

      -> structure indirection operator
       x->y
	   
is equivalent to

      (*x).y

Code Explanation
#include <iostream>
#include <string>

typedef struct Clients	
{ string sname;
  string fname;
  float due;
};

Clients * newClient(string, string, float);
void showClient_1(Clients);
void showClient_2(Clients *);
void showClient_3(Clients &);
We declare a type definition called Clients and some function prototypes that will operate on the Clients structure in a number of ways.
int main ()
{ Clients   client1;
  Clients * client2, * client3, * client4;
 
  client1.sname = "Smith"; client1.fname = "Bob";
  client1.due = 23.45;
  showClient_1( client1 );

  client2 = newClient("Robinson","Crusoe",3.45);
  showClient_1( *client2 );  //reference arg

  client3 = newClient("Jonathon","Swift",1003.45);
  showClient_2( client3 );  //pointer arg
 
  client4 = newClient("Evelyn", "Waugh", 10.00);
  showClient_3( *client4 ); //reference arg

  return 1;
}
We have 1 static variable client1 and 3 pointer variables client1, client2 and client3. At this stage the pointer variables don't point at anything useful and it may be dangerous to use them. The client1 variable has some values assigned to it in the normal way via the member operator . (dot). The pointer variables use the newClient() function which creates a MyClients structure and returns the pointer. There is also a group of showClient_?() functions which take different forms of arguments for MyClients structures.
Clients * newClient(string x, string y, float z)
{ Clients * c;

  c = new Clients;  //allocate memory for a new variable
  c->fname = x;     //use the pointer operator
  c->sname = y;     //to assign function arguments
  c->due = z;       //to the structure members
                    //don't delete the structure  
  return c;         //return the pointer
}

void showClient_1(Clients c)   //c is a copy of whatever the actual
{ cout << c.fname << " "       //argument is
       << c.sname << " owes "
       << c.due   << endl;
}

void showClient_2(Clients * c)    //c is a pointer argument
{ cout << (*c).fname << " "       //if we make a change to
       << (*c).sname << " owes "  //to c we change the actual
       << (*c).due   << endl;     //argument
}

void showClient_3(Clients & c)    //c is a reference argument
{ cout << c.fname << " "          //like the previous
       << c.sname << " owes "     //function this one
       << c.due   << endl;        //permits changes
}


Multiple levels of indirection

The pointers we have used so far all have a single level of indirection, that is, the pointer points at a variable. We can have more levels of indirection, for instance a pointer which points to a pointer which points to a variable. We can have so many levels that the result is confusion.

Single indirection was introduced in the earlier pointer lesson. If x is a pointer to variable then the variable is referenced with a single level of indirection. To access what the pointer points at we use *x. If x is a pointer to a pointer to a variable then we have two levels of indirection and to access what is pointed at needs **x.


   int v = 12,    //a plain integer
       *p1,       //a pointer to an integer
       **p2,      //a pointer to a pointer to an integer
       ***p3;     //a pointer to a pointer to a pointer to an integer 

   p1 = &v;  // p1 points at v  *p1 gives 12
   p2 = &p1; // p2 points at p1 **p2 gives 12
   p3 = &p2; // p3 points at p2 ***p3 gives 12
   

Take a look at this example with command line arguments.

Double and single indirection

We have an array of the C-style strings which represent the command line of our program. Cell[0] (argv[0]) contains the program name and directory, cell[1] (argv[1]) contains the string "Billy" and cell[2] (argv[2]) contains the word "Jack".

We can reference the strings in variety of ways and the figure attempts to show the relationship between references for single indirection and double indirection.


include <stdio.h>

int main(int argc, char *argv[])
{ int i;
  char *c;
  char **d;

  c = *(argv + 1);
  printf("* %s\n", c);

  for (i = 0; i < argc; i++)
   printf("* %s\n", *(argv + i));

  d = argv + 2;
  c = *d;
  printf("* %s\n", c);
 
  for (i = 0; i < argc; i++)
   { d = argv + i;
     printf("** %s\n", *d );
   }

  return 1;
}
include <stdio.h>

int main(int argc, char **argv)
{ int i, k;
  char *c;
  char **d;

  for (i = 0; i < argc; i++)
   printf("* %s\n", *(argv + i));
    
  for (i = 0; i < argc; i++)
   { d = argv + i;
     printf("** %s\n", *d );
   }

  return 1;
}

Return to top of page

Pointers to functions

#include <iostream>
#include <cstdlib>

typedef int (*ptrFunc) ( int, int );

int product ( int, int );
int sum     ( int, int );
int diff    ( int, int );

int main(void)
{ ptrFunc pf;

  pf = product; cout << (*pf)(12, 11) << endl;
  pf = sum;     cout <<    pf(12, 11) << endl;
  pf = &diff;   cout << (*pf)(12, 11) << endl;
  
  return EXIT_SUCCESS;
}

int product (int x, int y) { return x * y; }
int sum     (int x, int y) { return x + y; }
int diff    (int x, int y) { return x - y; }

You will occasionally come across code like:

int (*ptrFunc) ( int, int );

...

(*pf) (12, 11)

You can see it is an application of pointers but what does it mean?

The line int (*ptrFunc) ( int, int ); prototypes a pointer to a function that returns an integer. The function has two arguments.

We can use this by assigning any function to it that matches the type. If we had a dozen integer functions, each of which took two integer arguments then we could assign each of them, in turn, to an instance of this type.

The example program shows how we do it. The only significant addition is the use of typedef.

When the pointer to function is declared it is important to enclose the *pf in parentheses - (*pf). You should know that int (*pf)(int, int) is quite different to int *pf(int, int). In the first case we are stating the pointer operator refers to the function identifier, in the second case we are stating the pointer operator refers to the function return type.


Return to top of page

Tutorial 1

1.1

1.2 Write a program which declares an array of five C-style strings and which uses double indirection to iterate through the array and display each string.

View the sample answer.

1.3 Write a program which uses a function pointer. The pointer should be used to point to an integer product function and then an integer sum function both of which use variable argument lists, ie you might call the product function with 4 numbers to be multiplied, another time you might call it with five numbers to be multiplied. You cannot call the product and sum functions directly you must use a function pointer.

Return to top of page

Creating dynamic objects

The objects we have used so far have all been automatic variables but there are occasions when we need to declare objects dynamically.

 domestic * home; 
 home = new domestic();
 

It is quite simple to do, just use the new operator. The declaration domestic * home reads "home is a pointer to an instance of the domestic class". After the declaration domestic * home but prior to the new operation home doesn't point at anything valid and if you tried to do anything it at this point with your program would most likely crash. Always avoid using an uninitialised pointer.


 domestic * home; 
 home = new domestic();
 home->inspect();
 

I've used the parameter-less constructor domestic() which will still leave us with some problems if we try to do anything with the object we've just constructed. Since the inspect() method wants to display some details about a building the message home.inspect() will want to display details that don't exist. We should either use the parameterised constructor or provide a method that will initialise the domestic object before we use it. Note too the use of the structure indirection operator ->, home->inspect() is equivalent to (*home).inspect().


 domestic * home; 
 home = new domestic(PITCHED, CLAYBRICK, 1, 8, 3, 1);
 home->inspect();
 delete home;

To use the parameterised constructor we just supply the arguments in the statement using new. Now we can safely refer to the object. Notice that we use the delete operator to delete the object.


Return to top of page

Arrays of objects

 domestic * homes[4];
 int i;
 
 for (i = 0; i < 4; i++)
  homes[i] = new domestic(PITCHED, CLAYBRICK, 1, 8, 3, 1);
 homes[2]->inspect();
 for (i = 0; i < 4; i++)
  delete homes[i];

Creating a dynamic array of objects is simply a matter of declaring a pointer to an array - domestic * homes[4] - and then creating each element in the array with new.


Return to top of page

Tutorial 2


Return to top of page

First Next Previous Last Glossary About


Copyright © 1999 - 2001 David Beech