Function Pointers

All C++ code is translated into some form of machine code, which resides in memory somewhere when a program is running (in the case of UnderC and Java, the machine code is for a virtual machine that runs on top of a real machine). So code is a form of data, and thus it has an address. Usually the operating system organizes it so that code occupies protected memory that can't be written to, but it can be read and executed. Applying the address-of operator (&) to a function reveals this address:

;> &sin;  &cos;
(double operator(*)(double)) 72B364
(double operator(*)(double)) 72B390

This function address can be kept in a special kind of pointer, which can be used as a function. The dereference operator (*) is used to extract the value of the pointer. The function pointer declaration in the following code may seem strange at first. It is similar to a function prototype declaration, except for the (*). The parentheses are necessary because otherwise it would simply be a declaration, double *pfn(double), which is just a function taking a double argument and returning a pointer to a double. Once pfn is declared, it can be assigned the address of any compatible function. pfn can then be used to call the function using a double argument:

;> double (*pfn)(double);
;> pfn = &sin;
;> (*pfn)(1.0);
(double) 0.841471

Function pointer declarations are awkward, so usual practice is to define a typedef. As usual, the typedef looks exactly like a variable declaration, except the variable becomes a type alias:

;> typedef double (*DFN)(double);
;> DFN p1 = &sin, p2 = &cos;
;> p2(1.0);
(double) 0.540302

Note that you don't have to use the dereference operator here because it's clear from the context that this is a function call. The address-of operator is also implied when a function appears without being called. This is different from the usual pointer behavior; you also cannot freely convert function pointers to void pointers, because you usually cannot treat function pointers as data.

You can use function pointers to pass functions to other functions. A classic example of this would be code that plots a given function, such as the following:


void vplot(TG& g, DFN pfn,
   double x1, double x2, double dx=0.1)
{
// construct a vector containing the function values
  int n = (x2 - x1)/dx;
  vector<double> v(n);
  int i = 0;
  for(double x = x1; x < x2; x += dx)
     v[i++] = pfn(x);

// find our min/max data values
  vector<double>::iterator v1 = v.begin(), v2 = v.end();
  double vmin = *min_element(v1,v2);
  double vmax = *max_element(v1,v2);

// scale our graphics and plot out the points....
  g.scale(x1,x2,1.5*vmin,1.5*vmax);
  g.init();
  g.penup();
  for(double x = x1; x < x2; x += dx)
     g.plot(x,*v1++);
}

An example of using this would be vplot(tg,0,10,&sin). (Note the default argument.) This example uses the standard algorithms min_element() and max_element() (you must include <algorithm> for these), which return iterators, not values (hence the dereference, (*)). The remainder of the example shows how you can do plain Cartesian graphics with a turtle graphics window. TG::plot()draws a line to the given point, unless the pen is up. After a call to plot(), the pen is always down. (This style is less hassle than needing different calls, move_to() and draw_to(), to draw lines. It comes from the days when you really could see the pen moving about on the plotter.) You can of course pass any function, not just standard library functions, provided that the function has the correct signature, double fn(double).

Several standard library algorithms rely on function pointers. It is important to avoid writing out loops as much as possible. For example, say you want to print out a list of integers. Previously you would have declared a suitable iterator and incremented that iterator from ls.begin() to ls.end(). The standard algorithm for_each() does the job without needing the clumsy iterator declaration. For each element in the sequence, for_each() calls the specified function. The algorithm transform() is similar, except that it modifies the sequence by saving the result of applying the function to each element. Keep in mind that transform() doesn't necessarily write the result back to the sequence. As with copy(), you can use transform() to modify another, different, output; but often the output is the same as the input. In the following example, you set any negative numbers in the sequence to zero and use the library function toupper() to change each character in a string to uppercase:


;> list<int> ls;
;> ls << 10 << -20 << 30;   // using operator<< defined previously!
;> void print_int(int i) {  cout << i << endl; }
;> for_each(ls.begin(), ls.end(), print_int);
10
-20
30
;> int positive(int i) {  if (i < 0) return 0; }
;> transform(ls.begin(),ls.end(),ls.begin(),positive);  // ls is now 10 0 30
;> string s = "hello";
;> #include <cctype>  // for toupper()
;> transform(s.begin(),s.end(),s.begin(),toupper);
;> s;
;> (string) s = "HELLO"
;> #define ALL(s)  s.begin(),s.end()
;> transform(ALL(s),s.begin(),tolower);
;> for_each(ALL(ls),print_int);
10
0
30

This example uses the #define macro ALL, which takes one parameter that will be substituted when the macro is replaced (or expanded). This makes experimenting with these algorithms more fun because it saves having to type (and possibly get wrong) the begin/end sequence. (You should not use such shortcuts in official programs.)

Function pointers behave like ordinary pointers in many ways; you can construct arrays, lists, vectors, or maps, using them. Here is an array of pointers to trigonometric functions (note again that the dereference isn't necessary when calling these functions):

;> double my_fun(double x) {  return sin(x*x); }
;> DFN funs[] = {  &sin, &cos, &tan, &my_fun };   // an array of function pointers
;> (*funs[0])(1.0);  funs[0](1.0);
(double) 0.841471
(double) 0.841471

Arrays of function pointers make general menu code easy and flexible. After all, without such things, you are forced to write an explicit switch statement, which is impossible to extend at runtime. That is not an option for the function plotter because it must plot any function. Maps of function pointers can be particularly powerful. For instance, in the reverse-Polish calculator case study in Chapter 4, “Programs and Libraries,” you could have kept a map of strings to function pointers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.133.54