Dominar la sintaxis de C, especialmente las declaraciones complejas de punteros y funciones, puede ser un desafío. Esta guía desglosa un método sistemático para interpretar estas declaraciones, junto con ejemplos prácticos y técnicas para simplificar su comprensión.
La clave para descifrar estas declaraciones es un enfoque recursivo. Podemos pensar en la declaración en términos de qué representa cada componente:
- Identificador principal: ¿Es un puntero, una función o un array?
- Análisis interno: ¿Qué tipo de dato o entidad apunta, contiene o devuelve?
Aplicaremos este método a varios ejemplos, desde los más sencillos hasta los más complejos.
Nivel 1: Introducción
1. int (*tabla_dias)[13];
En esta declaración:
tabla_diases un puntero (*).- Este puntero apunta a un array (
[]). - El array contiene 13 elementos.
- Los elementos del array son de tipo
int.
Resultado: tabla_dias es un puntero a un array de 13 enteros. Este patrón es común para apuntar a estructuras de datos tabulares, como los días de los meses.
2. void (*signal(int, void (*)(int)))(int);
Esta es la declaración de la conocida función signal en C:
signales una función.signaltoma dos argumentos:- Un
int. - Un puntero a función que acepta un
inty devuelvevoid.
- Un
signaldevuelve un puntero a función.- El puntero a función devuelto apunta a una función que acepta un
inty devuelvevoid.
Resultado: signal es una función que acepta un número de señal (int) y un manejador de señales (un puntero a función), y devuelve un puntero a función del mismo tipo que el manejador.
Se puede simplificar enormemente con un typedef:
typedef void (*SignalHandler)(int);
SignalHandler signal(int signum, SignalHandler handler);
Nivel 2: Dificultad Intermedia
3. char (*(*x())[])();
Analizando de adentro hacia afuera:
x():xes una función.*x(): La función devuelve un puntero.(*x())[]: El puntero apunta a un array.*(*x())[]: Los elementos de este array son punteros.(*(*x())[])(): Estos punteros apuntan a funciones.char (*(*x())[])(): Estas funciones devuelven unchar.
Resultado: x es una función que devuelve un puntero. Este puntero apunta a un array de punteros a función. Cada una de estas funciones devuelev un char.
4. int (*(*foo())[])[3];
Siguiendo el mismo patrón:
foo():fooes una función.*foo(): Devuelve un puntero.(*foo())[]: El puntero apunta a un array.*(*foo())[]: Los elementos de este array son punteros.(*(*foo())[])[3]: Estos punteros apuntan a arrays de 3 elementos.int (*(*foo())[])[3]: Los elementos de estos arrays sonint.
Resultado: foo es una función que devuelve un puntero. Este puntero apunta a un array de punteros. Cada uno de estos punteros apunta a un array de 3 enteros.
Nivel 3: Modo Experto
5. void (*(* (*pfn)())())();
Descomponiendo la complejidad:
pfn: Es un identificador.*pfn: Es un puntero.(*pfn)(): Este puntero apunta a una función.*(*pfn)(): La función devuelve un puntero.(*(*pfn)())(): Este puntero devuelto apunta a otra función.*(*(*pfn)())(): La segunda función devuelve otro puntero.(*(*(*pfn)())())(): El último puntero apunta a una función.void (*(*(*pfn)())())(): Esta función final devuelvevoid.
Resultado: pfn es un puntero a función. La función a la que apunta devuelve un puntero a función. A su vez, esa función devuelve otro puntero a función. Finalmente, este último puntero a función apunta a una función que no devuelve nada (void).
Usando typedef para claridad:
typedef void (*FuncNivel3)();
typedef FuncNivel3 (*FuncNivel2)();
typedef FuncNivel2 (*FuncNivel1)();
FuncNivel1 pfn; // Equivalente a la declaración original
6. int (*(*(*func)(int*))[5])(double*);
Este es un ejemplo intrincado:
func: Identificador.*func: Puntero.(*func)(int*): Puntero a función que aceptaint*.*(*func)(int*): La función devuelve un puntero.(*(*func)(int*))[5]: El puntero devuelto apunta a un array de 5 elementos.*(*(*func)(int*))[5]: Los elementos de este array son punteros.(*(*(*func)(int*))[5])(double*): Estos punteros apuntan a funciones que aceptandouble*.int (*(*(*func)(int*))[5])(double*): Estas funciones devuelven unint.
Resultado: func es un puntero a función. La función a la que apunta acepta un int* y devuelve un puntero. Este puntero apunta a un array de 5 punteros a función. Cada uno de estos punteros a función apunta a una función que acepta un double* y devuelve un int.
Representación visual:
func → Función(int*) → Puntero → Array[5]
↓
Puntero a función → Función(double*) → int
Nivel 4: El Desafío Definitivo
7. char *(*(**foo[][8])())[];
Una declaración para practicar:
foo: Identificador.foo[][8]: Es un array bidimensional (la primera dimensión no está especificada, la segunda tiene tamaño 8).*foo[][]: Los elementos del array son punteros.**foo[][]: Estos punteros apuntan a punteros.(**foo[][8])(): Estos punteros apuntan a funciones.*(**foo[][8])(): Las funciones devuelven punteros.(*(**foo[][8])())[]: Los punteros devueltos apuntan a arrays.*(*(**foo[][8])())[]: Los elementos de estos arrays son punteros.char *(*(**foo[][8])())[]: Estos punteros son de tipochar*.
Resultado: foo es un array bidimensional (segunda dimensión de tamaño 8). Cada elemento de este array es un puntero a un puntero. Dicho puntero apunta a una función, la cual devuelve un puntero. Este puntero apunta a un array, cuyos elementos son punteros a char (char*).
Variaciones y Ejercicios
8. Comparación de Declaraciones Similares
char *(*str)[10];:stres un puntero a un array de 10 punteros achar.char *(*(*str)[]);:stres un puntero. Dicho puntero apunta a un array de punteros a función, donde cada función devuelve unchar*.char *(*str[])();:stres un array de punteros a función, donde cada función devuelve unchar*.char (*(*str)[])[10];:stres un puntero. Dicho puntero apunta a un array. Los elementos de este array son punteros que apuntan a arrays de 10char.
Técnicas de Análisis Resumidas
Para declaraciones complejas, siga esta regla:
- Comience por el nombre de la variable.
- Mire hacia la derecha hasta encontrar un
)o el final de la declaración. - Mire hacia la izquierda hasta encontrar un
(o el inicio de la declaración. - Repita los pasos 2 y 3 hasta que toda la declaración haya sido interpretada.
- Recuerde que la precedencia es: funciones
(), luego arrays[], y finalmente punteros*.
Uso de typedef
typedef es una herramienta invaluable para simplificar declaraciones complejas, haciéndolas más legibles.
// Declaración compleja
int (*(*foo)(int))[5];
// Simplificación con typedef
typedef int (*ArrayDeInts)[5]; // Alias para un puntero a array de 5 ints
typedef ArrayDeInts (*TipoFuncion)(int); // Alias para el tipo de función que devuelve ArrayDeInts
TipoFuncion foo; // Declaración simplificada
Herramienta cdecl
Utilidades como cdecl pueden ayudar a verificar su comprensión:
cdecl explain "int (*(*foo)(int))[5]"
# Salida:
# declare foo as pointer to function (int) returning pointer to array 5 of int
Autoevaluación
Intente analizar la siguiente declaración:
void (* (*(*fp)(int(*)(int, int), char*))[5])(double);
fp: Identificador.*fp: Puntero.(*fp)(int(*)(int, int), char*): Puntero a función que acepta dos argumentos: un puntero a función (int(int, int)) y unchar*.*(*fp)(...): La función devuelve un puntero.(*(*fp)(...))[5]: El puntero devuelto apunta a un array de 5 elementos.*(*(*fp)(...))[5]: Los elementos de este array son punteros.(*(*(*fp)(...))[5])(double): Estos punteros apuntan a funciones que aceptan undouble.void (*(*(*fp)(...))[5])(double): Estas funciones devuelvenvoid.
Resultado: fp es un puntero a función. La función a la que apunta acepta un puntero a función (int(int, int)) y un char*. Dicha función devuelve un puntero que apunta a un array de 5 punteros a función. Cada uno de estos punteros a función apunta a una función que acepta un double y devuelve void.
Diagrama:
fp → Función( PtrFunc(int,int), char* ) → Puntero → Array[5]
↓
Puntero a función → Función(double) → void