Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.2.4b  Invocar funciones mediante punteros

§1  Sinopsis

Una vez declarado e iniciado, un puntero a función puede ser utilizado de dos formas:

  • Acceder (invocar), a la función que representa
  • Usarlo como parámetro en la invocación de otra función.

Como en último extremo los punteros siempre sirven para invocar las funciones que representan, comenzamos la exposición por el primero de los dos usos.

§2  Invocación

En los programas reales, la invocación de funciones a través de punteros frecuentemente sigue una lógica larga y complicada (las aplicaciones que lo requieren suelen serlo), sin embargo el proceso siempre se puede esquematizar como sigue:

char func(int);            // L.1: declara función (prototipo)
...                        // suponemos su definición en algún sitio.
char (*fptr)(int) = func;  // L.2: define puntero-a-función
...
int x = 5;
char c;
c = (*fptr)(x);            // L.3: invoca func, pasa 5 como argumento


Como puede verse en el prototipo de L.1, la función func cumple las condiciones exigidas por el puntero en L.2 (recibir un int y devolver un char). La línea L.2 declara e inicia el puntero fptr. Podría hacerse igualmente en dos sentencias separadas:

char (*fptr)(int);      // L.2: declara puntero-a-función
fptr = func;            // L.2b: inicia puntero

A partir de L.2, (*fptr) es equivalente a func, de forma que las dos expresiones que siguen son equivalentes [1]:

func(x)        // invoación estándar
(*fptr)(x)     // invocación mediante puntero §2a
(*func)(x)     // invocación mediante puntero

La última expresión es consecuencia de aplicar la igualdad resultante de la expresión L.2b a la expresión §2a (fptr == func).

C++ proporciona otra sintaxis simplificada para la invocación de una función mediante su puntero:

fptr(x)        // Otra forma de invocación mediante puntero  §2b

Nota: esta segunda forma (§2b) es coherente con lo indicado anteriormente ( 4.2.4a) que el nombre de la función puede ser considerado como equivalente a su puntero.

Observe que en esta última sintaxis (§2b) es imposible saber que la invocación de la función no es directa, sino indirecta (a través de un puntero). Además algunas situaciones pueden dar lugar a expresiones casi "hirientes" a la vista. Por ejemplo, la línea M.5 del ejemplo de la página anterior ( 4.2.4a) podría ser sustituida por:

apf[3]();     // M.5bis

Por esta razón, algunos programadores prefieren la sintaxis (§2a) para recalcar que la invocación es mediante un puntero, dejando la sintaxis (§2b) para cuando se utiliza el nombre de la función.


§2.1  Un ejemplo básico:

#include <iostream.h>

void fun () { cout << "Una funcion cualquiera " << endl; }
void (*pf)() = fun;    // L.4 puntero-a-función
void (**ppf)() = &pf;  // L.5 puntero-a-puntero-a-función
 
int main (void) {      // ================
  fun ();              // M.1
  (*pf)();             // M.2
  pf();                // M.3
  (**ppf)();           // M.4
  (*ppf)();            // M.5
  return 0;
}

Salida:

Una funcion cualquiera
Una funcion cualquiera
Una funcion cualquiera
Una funcion cualquiera
Una funcion cualquiera

Comentario:

L.4 define un puntero-a-función de las características de fun (función que devuelve void y no recibe argumentos). Se inicia con la dirección de la función anterior. Observe que esta sentencia es equivalente a:

void (*pf)() = &fun;    // L.4bis

L.5 declara que ppf es un puntero-a-puntero-a-función devolviendo void que no recibe argumentos. Este puntero se inicia con la dirección de pf que es del tipo adecuado.

La función main invoca la función de cinco formas diferentes, todas ellas equivalentes. La primera, en M.1, es una invocación directa estándar. Las demás son a través de punteros. Observe que las sentencias M.2/M.3 son equivalentes, así como M.4/M.5.


§2.2  Otro ejemplo compilable:

#include <iostream.h>
char func(int);             // prototipo de func

void main () {              // ===========
  char (*fptr)(int) = func; // define puntero fptr
  int x = 65;
  char c;
  c = (*fptr)(x);           // invoca func, pasa 65 como argumento
  cout << "El valor es: " << c << endl;
  return (0);
}

char func (int p) {         // definición de func
  char c = p;               // "casting" implícito
  cout << "El valor es: " << c << endl;
  return char(p+32);        // observe el modelado del valor a devolver!!
}

Salida:

El valor es: A
El valor es: a


§2.3  Si en lugar de un puntero aislado se hubiese utilizado una matriz de punteros, el esquema seguiría siendo muy parecido:

char fun1(int);       // declara función (prototipo)
char fun2(int);       // idem
...
char (*fptr[])(int) = {fun1, fun2}; // define matriz de punteros
...
int x = 5, y = 1;
char c, d;
c = (*fptr[y])(x);    // invoca fun2, pasa 5 como argumento
d = (*fptr[0])(x);    // invoca fun1, pasa 5 como argumento

§3  Ejemplo_1

Una vez conocida la notación básica para invocación de funciones a través de punteros, consideremos un ejemplo más completo:

#include <stdio.h>          // prueba de puntero a función
void aux1(char letra);      // prototipo de aux1       L.2
int main(void)   {          // ===========
   void (* fptr)(char);     // fptr puntero a función  L.4
   fptr = aux1;             // inicia fptr             L.5
   char c;
   printf("- Pulse una tecla \n");
   while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) { //L.8
      if (c != 13 && c != 10) printf("  Debe ser una letra!\n");
   }
   (* fptr)(c);             // invoca aux1 con un argumento, L.11
   return 0;
}
void aux1(char ch) {        // definición de aux1 L.14
   if (ch < 97)
     printf("\"%c\" es Mayúscula !!\n", ch);
   else
     printf("\"%c\" es minúscula !!\n", ch);
}


El programa espera la introducción de una letra por el teclado (stdin) y declara si es mayúscula o minúscula; la salida depende de la tecla X/x pulsada:

"X" es Mayúscula !!
"x" es minúscula !!

En un primer paso (línea 4), definimos la variable fptr como "puntero a función recibiendo carácter y devolviendo void".

En línea 5 iniciamos la variable fptr, asignándole la dirección de la función aux1 que, como puede verse en su prototipo de la línea 2, cumple las condiciones exigidas por el puntero (recibir un char y devolver void). A partir de este momento, (*fptr) es equivalente a aux1.

El bucle de las líneas 8 a 10 es para asegurarnos que la tecla pulsada es una letra (mayúscula o minúscula). La línea 11 es una invocación a la función aux1(c) a través de su puntero.

§4  Ejemplo_2

Aunque didáctico, el ejemplo anterior es un poco tonto, pues para obtener el resultado no habría sido necesario utilizar un puntero a función, simplemente invocar auxch(str) directamente. En el ejemplo que sigue aprovechamos el carácter de variable de fptr para hacer una selección de la función a invocar.

El programa es muy parecido al anterior, con la excepción de que en este caso, también anuncia si la tecla pulsada es vocal o consonante.

#include <stdio.h>              // Prueba-2
#include <string.h>
 
   void aux1(char letter_key);  // prototipo de aux1
   void aux2(char letter_key);  // prototipo de aux2
 
int main(void)      {           // =========
   void (* fptr) (char);        // declara fptr puntero a función L.6
   char c;
   printf("- Pulse una tecla \n");
   while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) {
      if (c != 13 && c != 10) printf("  Debe ser una letra!\n");
   }
   fptr = aux1;   (* fptr)(c);  // L.12
   fptr = aux2;   (* fptr)(c);  // L.13
   return (0);
}
 
void aux1(char ch) {            // definición de aux1
   char *str = "aeiouAEIOU";
   if (strstr(str, &ch) != NULL)
      printf("\"%c\" es vocal ", ch);
   else
      printf("\"%c\" es consonante ", ch);
}
 
void aux2(char ch) {            // definición de aux2
   if (ch < 97)
      printf("MAYUSCULA !!\n");
   else
      printf("minuscula !!\n");
}


Se dispone de dos funciones auxiliares aux1 y aux2, cada una de las cuales se encarga de una parte de la salida, ambas aceptan el mismo tipo de argumento y devuelven void, por lo que pueden ser apuntadas por el mismo puntero fptr.

En las líneas 12 y 13 asignamos sucesivamente a la variable fptr las direcciones de las funciones aux1 y aux2, y las invocamos a través del puntero con el mismo parámetro en cada caso.

§5  Ejemplo_3

En la versión que sigue del mismo programa; en vez de un único puntero, que va cambiando sucesivamente de valor según la función que hay que invocar, utilizamos una matriz de punteros.

Nota: para simplificar, hemos suprimido el texto de las definiciones de aux1 y aux2, que son exactamente iguales que en el caso anterior; el cuerpo queda como sigue:


#include <stdio.h>                   // Prueba-3
#include <string.h>
   void aux1(char letter_key);       // prototipo de aux1
   void aux2(char letter_key);       // prototipo de aux2
 
int main(void)      {                // =========
   void (* fptr[2]) (char) = {aux1, aux2}; // L.6 
   char c;
   printf("- Pulse una tecla \n");
   while ((c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) {
      if (c != 13 && c != 10) printf("  Debe ser una letra!\n");
   }
   (* fptr[0])(c);  (* fptr[1])(c);  // L.12
   return (0);
}
 
// definiciones de aux1 y aux2 como en el ejemplo anterior


La novedad es que ahora fptr es un array de dos punteros a función, que se declara e inicia directamente en la línea 6. Obsérvese la diferencia de las declaraciones en la líneas 6 de este ejemplo y del anterior:

void (* fptr) (char);      // declara puntero a función
void (* fptr[2]) (char);   // declara array de punteros a funcion 


En le línea 12 se invoca sucesivamente aux1 y aux2 mediante sus punteros utilizando subíndices.

Estas matrices de punteros-a-función representan tablas de funciones con la que se puede manejar alternativas. En lugar de utilizar las clásicas sentencias del tipo if... then else, se ejecuta una u otra función en base a una variable. Esta forma de codificación, denominada código dirigido por tablas, es especialmente interesante y muy adecuada en determinadas circunstancias.

§6  Ejemplo_4

A continuación, un ejemplo de la técnica expuesta. Representa un refinamiento del programa anterior, en este caso, la selección se realiza en el cuerpo de la función main, fuera de las funciones auxiliares.

#include <stdio.h>          // Prueba-4
#include <string.h>
 
void aux1(void);            // prototipos
void aux2(void);
void aux3(void);
void aux4(void);
 
int main(void) {           // ========
   void (* fptr[])(void) = {aux1, aux2, aux3, aux4};
   char *str = "aeiouAEIOU", c;
   printf("- Pulse una tecla \n");
   while (( c=getchar() ) < 65 || c > 122 || (c > 90 && c < 97)) {
      if (c != 13 && c != 10) printf("  Debe ser una letra!\n");
   }
   printf("\"%c\" es ", c);
   (* fptr[(strstr(str, &c) != NULL) ? 0 : 1])();  // L.15
   (* fptr[(c < 97) ? 2 : 3])();                   // L.16
   return (0);
}
 
void aux1(void) { printf("vocal "); }      // definiciones
void aux2(void) { printf("consonante "); }
void aux3(void) { printf("MAYUSCULA !!\n"); }
void aux4(void) { printf("minúscula !!\n"); }


Utilizamos cuatro funciones auxiliares representadas en este caso por fptr, un array de cuatro punteros a función.

La línea 15 se resuelve en (*fptr[0])() ó (*fptr[1])() según el resultado de la cláusula strstr(str, &c) != NULL. Del mismo modo, la línea 16 se resuelve en (*fptr[2])() ó (*fptr[3])(), según el resultado de la condición  c < 27.

  Inicio.


[1]  El paréntesis de la expresión (*fptr) es necesario. La razón es que el operador ( ) de llamada a función tiene precedencia más alta que el de indirección * ( 4.9.0a), de forma que *fptr(x) sin paréntesis equivale a *(fptr(x)), lo que supone aplicar el operador de indirección sobre el valor devuelto por la función. Cosa que no está permitida.