Inapoi
Inainte
Cuprins
Function addresses
Once a function is compiled and loaded
into the computer to be executed, it occupies a chunk of memory. That memory,
and thus the function, has an address.
C has never been a language to bar entry
where others fear to tread. You can use function addresses with pointers just as
you can use variable addresses. The declaration and use of function pointers
looks a bit opaque at first, but it follows the format of the rest of the
language.
Defining a function pointer
To define a pointer to a function that
has no arguments and no return value, you say:
void (*funcPtr)();
When you are looking at a
complex definition like this, the
best way to attack it is to start in the middle and work your way out.
“Starting in the middle” means starting at the variable name, which
is funcPtr. “Working your way out” means looking to the right
for the nearest item (nothing in this case; the right parenthesis stops you
short), then looking to the left (a pointer denoted by the asterisk), then
looking to the right (an empty argument list indicating a function that takes no
arguments), then looking to the left (void, which indicates the function
has no return value). This right-left-right motion works with most
declarations.
To review, “start in the
middle” (“funcPtr is a ...”), go to the right (nothing
there – you're stopped by the right parenthesis), go to the left and find
the ‘*’ (“... pointer to a ...”), go to the right
and find the empty argument list (“... function that takes no arguments
... ”), go to the left and find the void (“funcPtr is
a pointer to a function that takes no arguments and returns
void”).
You may wonder why *funcPtr
requires parentheses. If you didn't use them, the compiler would
see:
void *funcPtr();
You would be declaring a function (that
returns a void*) rather than defining a variable. You can think of
the compiler as going through the same process you do when it figures out what a
declaration or definition is supposed to be. It needs those parentheses to
“bump up against” so it goes back to the left and finds the
‘*’, instead of continuing to the right and finding the empty
argument list.
Complicated declarations & definitions
As an aside, once you figure out how the
C and C++ declaration syntax works you can create much more complicated items.
For instance:
//: C03:ComplicatedDefinitions.cpp
/* 1. */ void * (*(*fp1)(int))[10];
/* 2. */ float (*(*fp2)(int,int,float))(int);
/* 3. */ typedef double (*(*(*fp3)())[10])();
fp3 a;
/* 4. */ int (*(*f4())[10])();
int main() {} ///:~
Walk through each one and use the
right-left guideline to figure it out. Number 1 says “fp1 is a
pointer to a function that takes an integer argument and returns a pointer to an
array of 10 void pointers.”
Number 2 says “fp2 is a
pointer to a function that takes three arguments (int, int, and
float) and returns a pointer to a function that takes an integer argument
and returns a float.”
If you are creating a lot of complicated
definitions, you might want to use a typedef. Number 3 shows how a
typedef saves typing the complicated description every time. It says
“An fp3 is a pointer to a function that takes no arguments and
returns a pointer to an array of 10 pointers to functions that take no arguments
and return doubles.” Then it says “a is one of these
fp3 types.” typedef is generally useful for building
complicated descriptions from simple ones.
Number 4 is a function declaration
instead of a variable definition. It says “f4 is a function that
returns a pointer to an array of 10 pointers to functions that return
integers.”
You will rarely if ever need such
complicated declarations and definitions as these. However, if you go through
the exercise of figuring them out you will not even be mildly disturbed with the
slightly complicated ones you may encounter in real life.
Using a function pointer
Once you define a
pointer to a function, you must assign it to a function
address before you can use it. Just as the address of an array arr[10] is
produced by the array name without the brackets (arr), the address of a
function func() is produced by the function name without the argument
list (func). You can also use the more explicit syntax
&func(). To call the function, you dereference
the pointer in the same way that you declared it (remember that C and C++ always
try to make definitions look the same as the way they are used). The following
example shows how a pointer to a function is defined and used:
//: C03:PointerToFunction.cpp
// Defining and using a pointer to a function
#include <iostream>
using namespace std;
void func() {
cout << "func() called..." << endl;
}
int main() {
void (*fp)(); // Define a function pointer
fp = func; // Initialize it
(*fp)(); // Dereferencing calls the function
void (*fp2)() = func; // Define and initialize
(*fp2)();
} ///:~
After the pointer to function fp
is defined, it is assigned to the address of a function func() using
fp = func (notice the argument list is missing on the function name). The
second case shows simultaneous definition and initialization.
Arrays of pointers to functions
One of the more interesting constructs
you can create is an array of pointers to functions. To select a function, you
just index into the array and dereference the pointer. This supports the concept
of table-driven code;
instead of using conditionals or case statements, you select functions to
execute based on a state variable (or a combination of state variables). This
kind of design can be useful if you often add or delete functions from the table
(or if you want to create or change such a table dynamically).
The following example creates some dummy
functions using a preprocessor macro, then creates an array of pointers to those
functions using automatic
aggregate initialization. As you
can see, it is easy to add or remove functions from the table (and thus,
functionality from the program) by changing a small amount of
code:
//: C03:FunctionTable.cpp
// Using an array of pointers to functions
#include <iostream>
using namespace std;
// A macro to define dummy functions:
#define DF(N) void N() { \
cout << "function " #N " called..." << endl; }
DF(a); DF(b); DF(c); DF(d); DF(e); DF(f); DF(g);
void (*func_table[])() = { a, b, c, d, e, f, g };
int main() {
while(1) {
cout << "press a key from 'a' to 'g' "
"or q to quit" << endl;
char c, cr;
cin.get(c); cin.get(cr); // second one for CR
if ( c == 'q' )
break; // ... out of while(1)
if ( c < 'a' || c > 'g' )
continue;
(*func_table[c - 'a'])();
}
} ///:~
At this point, you might be able to
imagine how this technique could be useful when creating some sort of
interpreter or list processing
program.
 |
|