Cuando hablamos de C o C++, punteros es un tema que se relaciona casi de forma inmediata. Puntero, recuerda, es un tipo de variable que “apunta” a una determinada posición de memoria. Si, si ya se que quizás tienes algo de rechazo a este tema, pero quizás fue por la forma que lo aprendiste, pero te aseguro que si dominas o te interesas aún más por el tema de punteros, puedes manejar mejor los recursos de la computadora.
En esta ocasión te voy a hablar acerca de una forma distinta para trabajar con punteros. El concepto como tal de puntero no cambiará; es decir, seguirá “apuntando” a una dirección de memoria, pero en dicha posición de memoria, ya no estará almacenado un dato, sino una porción o bloque de código, en forma más concreta, “apuntará” a una función.
De seguro ahora tienes un gran signo de interrogación acerca de lo que me refiero, no te preocupes, trataré de ser lo más claro posible con mi explicación.
Hasta ahora, la idea para un puntero que tienes es la de: Una variable, que almacena un espacio de memoria (valor Hexadecimal), y es en dicho espacio de memoria donde recién se encuentra el dato (valor entero, real, carácter, etc.) al que deseamos acceder.
Ahora, imagínate que en lugar de desear acceder a un dato guardado en memoria, usando punteros, lo que deseamos es acceder a una porción de código. Qué es lo que necesitamos?….a ver, piensa.. CORRECTO!!, necesitamos que el código que deseamos acceder ocupe un lugar en memoria para que el puntero “apunte” a la dirección donde está almacenado el bloque de código.
Ya vez, que es sencillo el tema, pero de seguro te estarás preguntando: como rayos hago para que mi código tenga asociada una dirección de memoria, pues aquí te va mi respuesta: No tienes que hacer nada!, solo tener claro el siguiente concepto:
Cuando inicializamos una aplicación, el Sistema Operativo, prepara el ambiente de trabajo para la aplicación que se ejecutará. Esto implica reservar espacios de memoria, para que se almacene la información necesaria y también el código que se ejecutará durante la aplicación. Bajo este esquema, se reserva lo siguiente:
| Local Heap | Local Heap: Es un área fija de memoria, la cual se asigna durante la ejecución del programa. Por lo anterior, nos podemos dar cuenta que aquí se encuentran las variables del tipo puntero. |
| Stack | Stack: Es un área de memoria manejada directamente por la computadora. Es aquí donde se almacenan los datos de la aplicación. |
| Datos estáticos no inicializados | Datos estáticos no inicializados: Aquí se almacenan los datos estáticos de la aplicación a los cuales NO se les ha asignado un valor por defecto en el código fuente del programa. |
| Datos estáticos inicializados | Datos estáticos inicializados: Aquí se almacenan los datos estáticos de la aplicación a los cuales se les ha asignado un valor por defecto en el código fuente del programa. |
| Código del Programa (Code Segment) | Código del Programa: contiene el código (algoritmo) ejecutable y todos los valores que han podido ser resueltos en tiempo de compilación. En esta zona también se encuentra el código de todas las funciones. |
Con el gráfico anterior espero que te haya quedado claro que, en el Code Segment, se almacena la dirección de memoria del código de las funciones que hemos definido en nuestra aplicación, por lo que un puntero a una función, deberá “apuntar” a una dirección de memoria dentro de este bloque.
Definiendo un puntero a función
Un puntero a una función sigue siendo una variable pero que “apunta” no a una sola función, sino a cualquier función que cumpla con un formato definido. Este formato depende del valor de retorno y de los parámetros que se reciben, pudiendo existir infinidad de combinaciones. El formato al cual me refiero no es otra cosa que la firma o cabecera de la función.
Veamos el siguiente ejemplo:
Si hablamos de una función que recibe dos parámetros y retorna un tipo de dato void, podríamos hablar de los siguientes casos:
void Funcion_Uno (int a, int b);void Funcion_Dos (int a, char b);void Funcion_Tres (int a, int* b);void Funcion_Cuatro (int a, double b);void Funcion_ETC (int a, float b);
Y la lista puede seguir creciendo, por lo que debemos de definir el esquema de una función determinando exactamente dos cosas: el tipo de los parámetros que recibe y el valor de retorno que va a tener.
Ahora, si hablamos de forma genérica de una función que reciba dos parámetros, por ejemplo del tipo int, y retorne un tipo de dato, por ejemplo void, podemos hablar de lo siguiente:
void (int , int);
Nótese que no le he asignado un nombre a la función, y tampoco le he asignado un nombre a los parámetros, sólo he colocado el tipo. Por qué?, sencillo, porque es el primer paso que debemos hacer para definir un puntero a una función.
Para definir un puntero a una función con el esquema anterior, te bastará con agregar lo siguiente a la línea de código anterior:
void (*ptrFn) (int, int);
Donde *ptrFn es un puntero a una función que recibe dos parámetros del tipo int y retorna un void. Recuerda que ptrFn es el nombre de la variable del tipo puntero y lo puedes cambiar.
Veamos más ejemplos para que quede claro a lo que me refiero:
1. Puntero a una función que no recibe parámetros y que retorna int :
int (*ptrFn)();
Si quieres una función que retorne otro tipo de dato distinto, sólo deberás cambiar int por el tipo de dato que desees. El tipo de dato puede ser puntero.
2. Puntero a una función que recibe como parámetros dos punteros a int y char respectivamente y retorna void :
void (*ptrFn)(int*, char*);
Si quieres una función que reciba otro tipo de dato distinto, sólo deberás cambiar los punteros, por los tipos de datos que deseas.
Ambos casos son sólo para demostrar como se define un puntero a una función, puedes definir puntero para la función que desees.
Bueno, hasta este punto te he indicado como definir un puntero a una función, pero falta lo más importante, como se usan.
Veamos el siguiente código:
#include <stdio.h>
#include <conio.h>
int MiFuncionSuma(int a,int b)
{
return a + b;
}
int MiFuncionResta(int a,int b)
{
return a - b;
}
int MiFuncionModulo(int a,int b)
{
return a % b;
}
int main()
{
/*Declaración del puntero a una función que tiene dos parámetros int
y retorna un valor entero*/
int (*ptrFn) (int,int);
/*Asignación del puntero a la dirección de memoria de una función
que cumple con el esquema definido*/
ptrFn = &MiFuncionSuma;
printf("La suma es : %d\n", ptrFn(5, 6)); // Invocación a la Función
ptrFn = &MiFuncionResta;
printf("La resta es : %d\n", ptrFn(5, 6));
ptrFn = &MiFuncionModulo;
printf("El Modulo es : %d\n", ptrFn(5, 6));
}
Veamos una variación al ejemplo anterior:
#include <stdio.h>
#include <conio.h>
int MiFuncionSuma(int a,int b) // Función del Tipo A
{
return a + b;
}
void Imprime_Resultado(int a) // Función del Tipo B
{
printf("El resultado es : %d \n", a);
}
int main()
{
int (*ptrFnSum) (int,int); //Puntero a Función del Tipo A
void (*ptrFnRes) (int); //Puntero a Función del Tipo B
ptrFnSum = &MiFuncionSuma;
ptrFnRes = &Imprime_Resultado;
ptrFnRes(ptrFnSum(5, 6)); //Invocación a las funciones
}
Y una variación más:
#include <stdio.h>
#include <conio.h>
int* MiFuncionSuma(int a,int b) // Función del Tipo A
{
int *calculo;
calculo = new int();
*calculo = a + b;
return calculo;
}
void Imprime_Resultado(int *a) // Función del Tipo B
{
printf("El resultado es : %d \n",*a);
}
int main()
{
int* (*ptrFnSum) (int,int); //Puntero a Función del Tipo A
void (*ptrFnRes) (int*); //Puntero a Función del Tipo B
ptrFnSum = &MiFuncionSuma;
ptrFnRes = &Imprime_Resultado;
ptrFnRes(ptrFnSum(5, 6)); //Invocación a las funciones
}
Como hemos visto en los ejemplos anteriores, lo que hacemos es: primero, definir nuestras funciones, con o sin parámetros, luego definimos las variables del tipo puntero a función, por último enlazamos nuestras variables a la dirección de memoria del código de las funciones antes que las invoquemos. El enlace lo debemos hacer a la función, por lo que podemos enlazarlo sólo colocando el nombre de la función o usando el operador de dirección & seguido del nombre de la función. Por último, trabajamos únicamente con el puntero, ya no directamente con las funciones definidas.
Difícil?, no lo creo, sólo es cuestión de práctica para que te acostumbres a trabajar en el tema.
Bueno, hasta ahora el tema esta algo “light”, así que vamos a “complicarlo” un poquito.
Te imaginas definir un puntero a una función donde uno de los parámetros que recibes es a la vez un puntero a función? O definir una matriz donde cada uno de los elementos sea un puntero a función?
Alucinante, no?.. Bueno los que me conocen dirán, ya empezó con sus marcianadas ja ja ja, bueno sí . Bromas aparte, mi deber es decirte lo que es posible hacer con el lenguaje así que lo que menciono es totalmente válido, sólo hay que tener cuidado con las nomenclaturas.
Recibiendo un puntero a función como parámetro
Para que quede claro, lo vamos a ver en código.
Voy a empezar definiendo un puntero a una función que recibe dos parámetros del tipo int, y retorna un int:
int (*ptrFn) (int,int);
Ahora definiré una función que tiene tres parámetros, el primero es un puntero a una función, igual al definido en el punto anterior y los dos siguientes son del tipo int. El valor de retorno es void.
void (*ptrFnD) (int (*ptrFn) (int,int), int, int);
Veamos un ejemplo:
#include <stdio.h>
#include <conio.h>
// Función del Tipo A
int MiFuncionSuma(int a,int b)
{
return a+b;
}
// Función del Tipo B que recibe un puntero a función
void Imprime_Resultado(int (*funcion)(int , int),int x,int y)
{
printf("El resultado es : %d \n",funcion(x,y));
}
int main()
{
void (*ptrFnRes) (int (*f) (int,int), int x, int y); //Puntero a Función del Tipo B
ptrFnRes = &Imprime_Resultado;
ptrFnRes(MiFuncionSuma, 5, 6); //Invocación a las funciones
}
Ahora te mostraré un ejemplo que te puede servir. En dicho ejemplo ordenare un vector de enteros, haciendo uso de una función de comparación y una para cambiar los datos. Nota que la función de Ordenamiento recibe un puntero a una función.
#include <stdio.h>
#include <conio.h>
// Función para comparar dos valores. Retorna 1 o -1
int Comparacion(int valor1,int valor2)
{
return valor1 > valor2 ? 1 : -1;
}
// Función para intercambiar el valor de dos posiciones en un Vector
void CambiarDatos(int posI,int posJ,int *Vector)
{
int aux=Vector[posI];
Vector[posI]=Vector[posJ];
Vector[posJ]=aux;
}
// Función para ordenar, recibe un puntero a una función
void Ordenamiento (int (*funcion)(int , int), int *Vector,int maximo)
{
for (int i=0;i<maximo-1;i++)
{
for (int j=i+1;j<maximo;j++)
{
if (funcion(Vector[i],Vector[j])>0)
CambiarDatos(i,j,Vector);
}
}
}
// Función para imprimir un vector
void Imprime_Vector(int *Vector,int maximo)
{
for (int i=0;i< maximo;i++)
printf("%2d\n",Vector[i]);
}
int main()
{
int Vector[10]={5,76,4,8,50,2,44,7,23,6};
printf("Original: \n");
Imprime_Vector(Vector,10);
Ordenamiento(Comparacion,Vector,10);
printf("Ordenado: \n");
Imprime_Vector(Vector,10);
}
Ahora mira otra versión del código anterior. En esta ocasión la variable Vector es un puntero y utilizo aritmética de punteros para ir desplazándome entre las posiciones de memoria.
#include <stdio.h>
#include <conio.h>
// Función para comparar dos valores. Retorna 1 o -1
int Comparacion(int *valor1, int *valor2)
{
return *valor1 > *valor2 ? 1 : -1;
}
// Función para intercambiar dos valores
void CambiarDatos(int *datoI, int *datoJ)
{
int aux=*datoI;
*datoI=*datoJ;
*datoJ=aux;
}
// Función para ordenar, recibe un puntero a una función
void Ordenamiento (int (*funcion)(int * , int *), int *Vector,int maximo)
{
int *vi,*vj;
vi =Vector;
for (int i=0; i<maximo-1; i++)
{
vj = vi;
for (int j=i+1; j<maximo; j++)
{
if (funcion(vi,vj) > 0)
CambiarDatos(vi,vj);
vj++;
}
vi++;
}
}
// Función para imprimir un vector
void Imprime_Vector(int *Vector,int maximo)
{
int *vi;
vi = Vector;
for (int i=0;i<maximo;i++)
{
printf("%2d\n",*vi);
vi++;
}
}
int main()
{
int Vector[10]={5,76,4,8,50,2,44,7,23,6};
printf("Original: \n");
Imprime_Vector(Vector,10);
Ordenamiento(Comparacion,Vector,10);
printf("Ordenado: \n");
Imprime_Vector(Vector,10);
}
Definiendo una matriz de punteros a función.
Antes de continuar recuerda que un puntero a una función es una variable, y una matriz permite guardar n×m datos de un determinado valor.
Veamos un ejemplo por código:
Definamos un puntero a una función que recibe dos parámetros del tipo int, y retorna un int:
int (*ptrFn) (int,int);
Ahora, a partir de la definición anterior y sin hacer muchas cosas raras, definiré una matriz de diez punteros a función a partir del código anterior.
int (*ptrFn[10]) (int,int);
Veamos el siguiente código:
#include <stdio.h>
#include <conio.h>
#include <string.h>
struct StPersona
{
int edad;
double Salario;
char *Nombre;
};
// Función para comparar por edad. Retorna 1 o -1
int ComparacionxEdad(StPersona *Persona1, StPersona *Persona2)
{
return Persona1->edad > Persona2->edad ? 1 : -1;
}
// Función para comparar por salario. Retorna 1 o -1
int ComparacionxSalario(StPersona *Persona1, StPersona *Persona2)
{
return Persona1->Salario > Persona2->Salario ? 1 : -1;
}
// Función para comparar por nombre. Retorna 1 o -1
int ComparacionxNombre(StPersona *Persona1, StPersona *Persona2)
{
return strcmp(Persona1->Nombre, Persona2->Nombre);
}
// Función para intercambiar dos valores
void CambiarDatos(StPersona *datoI, StPersona *datoJ)
{
StPersona aux = *datoI;
*datoI = *datoJ;
*datoJ = aux;
}
// Función para ordenar, recibe un puntero a una función
void Ordenamiento (int (*funcion)(StPersona * , StPersona *), StPersona *Vector, int maximo)
{
StPersona *vi,*vj;
vi = Vector;
for (int i=0; i < maximo-1; i++)
{
vj=vi;
for (int j=i;j<maximo;j++)
{
if (funcion(vi,vj)>0)
CambiarDatos(vi,vj);
vj++;
}
vi++;
}
}
// Función para imprimir un vector
void Imprime_Vector(StPersona *Vector,int maximo)
{
StPersona *vi;
vi = Vector;
for (int i=0; i<maximo; i++)
{
printf("Edad : %2d\n", vi->edad );
printf("Nombre : %s\n", vi->Nombre);
printf("Salario : %2lf\n", vi->Salario );
vi++;
}
}
int main()
{
StPersona *Vector;
Vector=new StPersona[20];
for (int i=0;i<20;i++)
{
printf ("Edad : ");
scanf("%d", &Vector[i].edad);
printf ("Salario : ");
scanf("%lf", &Vector[i].Salario);
_flushall();
Vector[i].Nombre = new char[50];
printf ("Nombre : ");
gets(Vector[i].Nombre);
}
int (*ptrFn[3]) (StPersona *, StPersona *); // Matriz de 3 punteros a funciones
printf("Original: \n");
Imprime_Vector(Vector,20);
ptrFn[0] = ComparacionxEdad;
ptrFn[1] = ComparacionxSalario;
ptrFn[2] = ComparacionxNombre;
//Invocacion a cada uno de los ordenamientos
for (int i=0;i<3;i++)
{
Ordenamiento(ptrFn[i], Vector,20);
printf("Ordenado %d: \n", i+1);
Imprime_Vector(Vector,20);
}
}
En el código anterior puedes observar que he asignado 3 métodos de ordenamiento, los cuales son invocados dentro del último for y asignados en las líneas anteriores.
Espero que la información que te he mencionado en este post te sea útil dentro de las aplicaciones que hagas.

Buenas, muy interesante el post, pero qué beneficios dan el uso de punteros a funciones respecto a la funciones normales de los programas. Es más por limpieza de código, flexibilidad en programación al poder cambiar las funciones que utilizo muy fácimente, da más performance, etc?
gracias!
hola, me peuden decir la utilidad que tienen lso punteros a funciones? creo que se usan para llamar a funciones de DLLs importadas no? gracias chau
Las utilidades son infinitas y dependen de tu creatividad, pero la idea principal es para que tu función permita a otro desarrollador realizar tareas sin necesidad de conocer toda la lógica de tus funciones. Es bastante dificil de explicar, pero a medida que vayas practicando te puedes ir dando cuenta de la gran variedad de utilidades que tienen.
Entre en tu web buscando info sobre delegados y como mi día a día es con C y C++, leí este articulo y me encanto la forma en que explicas este tema a veces peliagudo.
Un saludo desde tierras del Quijote.
Excelente !
Muchas gracias !
Las explicaciones de punteros a funciones que has puesto son las mejores que he encontrado en el web. gracias
Es la mejor explicación de punteros a funciones en C que he encontrado yo también!
Acerca de su utilidad, y dejando de lado los delegados en C#, la sintaxis de los códigos de este artículo se parecen mucho mucho pero muucho a las lambdas en C# 3.0, observad:
static void Main(string[] args)
{
FuncSumar = (x,y) => x+y;
Action<Func, int, int> Imprime = (K, x, y) => Console.WriteLine(K(x,y));
Imprime(Sumar, 5, 6);
Console.Read();
}
Probado y compilado en VS2008 Express