• No se han encontrado resultados

Direcciones de función

In document CI 2125 Pensar En C Volumen 1 pdf (página 165-168)

int main() { int i = 100; assert(i != 100); // Fails } ///:~

La macro original es C Estándar, así que está disponible también en el fichero de cabeceraassert.h.

Cuando haya terminado la depuración, puede eliminar el código generado por la macro escribiendo la siguiente línea:

#define NDEBUG

en el programa, antes de la inclusión de<cassert>, o definiendoNDEBUGen la línea de comandos del compilador.NDEBUGes una bandera que se usa en<cassert>

para cambiar el código generado por las macros.

Más adelante en este libro, verá algunas alternativas más sofisticadas aassert- ().

3.10. Direcciones de función

Una vez que una función es compilada y cargada en la computadora para ser ejecutada, ocupa un trozo de memoria. Esta memoria, y por tanto esa función, tiene una dirección.

C nunca ha sido un lenguaje [FIXME] donde otros temen pisar. Puede usar di- recciones de función con punteros igual que puede usar direcciones variables. La declaración y uso de punteros a función parece un poco opaca al principio, pero sigue el formato del resto del lenguaje.

3.10.1. Definición de un puntero a función

Para definir un puntero a una función que no tiene argumentos y no retorna nada, se dice:

void (*funcPtr)();

Cuando se observa una definición compleja como esta, el mejor método para en- tenderla es empezar en el medio e ir hacia afuera. «Empezar en el medio» significa empezar con el nombre de la variable, que esfunPtr. «Ir hacia afuera» significa mi- rar al elemento inmediatamente a la derecha (nada en este caso; el paréntesis derecho marca el fin de ese elemento), después mire a la izquierda (un puntero denotado por el asterisco), después mirar de nuevo a la derecha (una lista de argumentos vacía que indica que no función no toma argumentos), después a la izquierda (void, que indica que la función no retorna nada). Este movimiento derecha-izquierda-derecha funciona con la mayoría de las declaraciones.6

6(N. del T.) Otra forma similar de entenderlo es dibujar mentalmente una espiral que empieza en el

Para repasar, «empezar en el medio» («funcPtres un ...», va a la derecha (nada aquí - pare en el paréntesis derecho), va a la izquierda y encuentra el * («... puntero a ...»), va a la derecha y encuentra la lista de argumentos vacía («... función que no tiene argumentos ...») va a la izquierda y encuentra el void («funcPtres un puntero a una función que no tiene argumentos y retorna void»).

Quizá se pregunte porqué*funcPtrnecesita paréntesis. Si no los usara, el com-

pilador podría ver:

void *funcPtr();

Lo que corresponde a la declaración de una función (que retorna un void*) en lugar de definir una variable. Se podría pensar que el compilador sería capaz distin- guir una declaración de una definición por lo que se supone que es. El compilador necesita los paréntesis para «tener contra qué chocar» cuando vaya hacia la izquier- da y encuentre el *, en lugar de continuar hacia la derecha y encontrar la lista de argumentos vacía.

3.10.2. Declaraciones y definiciones complicadas

Al margen, una vez que entienda cómo funciona la sintáxis de declaración de C y C++ podrá crear elementos más complicados. Por ejemplo:

//: V1C03: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() {}

Estudie cada uno y use la regla derecha-izquierda para entenderlos. El número 1 dice «fp1es un puntero a una función que toma un entero como argumento y retorna un puntero a un array de 10 punteros void».

El 2 dice «fp2es un puntero a función que toma tres argumentos (int, int y float) de retorna un puntero a una función que toma un entero como argumento y retorna un float»

Si necesita crear muchas definiciones complicadas, debería usartypedef. El nú- mero 3 muestra cómo untypedefahorra tener que escribir una descripción compli- cada cada vez. Dice «Un fp3 es un puntero a una función que no tiene argumentos y que retorna un puntero a un array de 10 punteros a funciones que no tienen ar- gumentos y retornan doubles». Después dice «aes una variable de ese tipo fp3».

typedefes útil para construir descripciones complicadas a partir de otras simples. El 4 es una declaración de función en lugar de una definición de variable. Dice «f4es una función que retorna un puntero a un array de 10 punteros a funciones que retornan enteros».

3.10. Direcciones de función

Es poco habitual necesitar declaraciones y definiciones tan complicadas como éstas. Sin embargo, si se propone entenderlas, no le desconcertarán otras algo menos complicadas pero que si encontrará en la vida real.

3.10.3. Uso de un puntero a función

Una vez que se ha definido un puntero a función, debe asignarle la dirección de una función antes de poder usarlo. Del mismo modo que la dirección de un array

arr[10] se obtiene con el nombre del array sin corchetes (arr), la dirección de una funciónfunc()se obtiene con el nombre de la función sin lista de argumentos (func). También puede usar una sintáxis más explícita:&func(). Para invocar la función, debe dereferenciar el puntero de la misma forma que lo ha declarado (re- cuerde que C y C++ siempre intentan hacer que las definiciones se parezcan al modo en que se usan). El siguiente ejemplo muestra cómo se define y usa un puntero a función:

//: 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)(); } ///:~

Una vez definido el puntero a funciónfp, se le asigna la dirección de una fun- ciónfunc()usandofp = func(fíjese que la lista de argumentos no aparece junto al nombre de la función). El segundo caso muestra una definición e inicialización simultánea.

3.10.4. Arrays de punteros a funciones

Una de las construcciones más interesantes que puede crear es un array de punte- ros a funciones. Para elegir una función, sólo indexe el array y dereferencie el punte- ro. Esto permite implementar el concepto decódigo dirigido por tabla(table-driven code); en lugar de usar estructuras condicionales o sentencias case, se elige la función a eje- cutar en base a una variable (o una combinación de variables). Este tipo de diseño puede ser útil si añade y elimina funciones de la tabla con frecuencia (o si quiere crear o cambiar una tabla dinámicamente).

El siguiente ejemplo crea algunas funciones falsas usando una macro de prepro- cesador, después crea un array de punteros a esas funciones usando inicialización automática. Como puede ver, es fácil añadir y eliminar funciones de la table (y por tanto, la funcionalidad del programa) cambiando una pequeña porción de código.

//: 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’])(); }

} ///:~

A partir de este punto, debería ser capaz de imaginar cómo esta técnica podría resultarle útil cuando tenga que crear algún tipo de intérprete o programa para pro- cesar listas.

3.11. Make: cómo hacer compilación separada

In document CI 2125 Pensar En C Volumen 1 pdf (página 165-168)