Explicamos los Punteros en C

COMPARTIR EN REDES SOCIALES

En este artículo explicamos los Punteros en C. Los punteros son referencias a direcciones de memoria que contienen datos. Se suelen utilizar en la asignación de memoria dinámica y nos permiten reservar tanta memoria como sea necesaria en nuestras aplicaciones.

Podemos ver un puntero como la siguiente representación:

Representación de un puntero en C
Representación de un puntero en C

Si os fijáis la caja morada es el puntero, que se encuentra en la dirección 0x503fb43 y cuyo contenido es 0x7f5f0ac, en esta dirección de memoria es donde tenemos nuestra variable edad cuyo valor es 10. Es decir, la caja morada apunta a través de un puntero a la caja azul, que es verdaderamente donde están los datos.

Para entender que es un puntero primero es necesario comprender que toda variable que declaremos se halla en una posición de memoria, podemos comprobarlo de la siguiente manera:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Persona{
    int edad;
    char nombre[30];
    char apellidos[50]; 
};

int main(){
    //Declaramos una variable llamada "a" cuyo contenido es 3
    int a = 3;
    //Declaramos una cadena
    char micadena[] = "Hola, esto es una cadena";
    //Declaramos un struct Persona 
    struct Persona p1;
    //Le asignamos valores
    p1.edad = 32;
    strncpy(p1.nombre, "Ivan\0",5);
    strncpy(p1.apellidos, "Ramon Moran\0",12);
    

    //Imprimimos el contenido de a y su direccion de memoria
    printf("El contenido de a es: %d\n", a);
    printf("a se encuentra en la posicion de memoria: %p\n", &a);

    //Imprimimos el contenido de micadena y su direccion de memoria
    printf("El contenido de micadena es: %s\n", micadena);
    printf("micadena se encuentra en la posicion de memoria: %p\n", micadena);
    //Otra forma de acceder a la posicion de memoria de un array
    printf("micadena se encuentra en la posicion de memoria: %p\n", &micadena[0]);

    //Imprimimos el contenido de nuestro struct Persona
    printf("Nombre: %s\n", p1.nombre);
    printf("Apellidos: %s\n", p1.apellidos);
    printf("Edad: %d\n", p1.edad);

    //Imprimimos las posiciones de memoria
    //Primero el struct
    printf("La posicion de memoria del struct es: %p\n", &p1);
    //Ahora la posicion de memoria de sus propiedades
    printf("La posicion de memoria de la propiedad nombre es: %p\n", &p1.nombre);
    printf("La posicion de memoria de la propiedad apellidos es: %p\n", &p1.apellidos);
    printf("La posicion de memoria de la propiedad edad es: %p\n", &p1.edad);

    return 0;
}

La salida de nuestro programa

El contenido de a es: 3
a se encuentra en la posicion de memoria: 0061FF1C
El contenido de micadena es: Hola, esto es una cadena
micadena se encuentra en la posicion de memoria: 0061FF03
micadena se encuentra en la posicion de memoria: 0061FF03
Nombre: Ivan
Apellidos: Ramon Moran
Edad: 32
La posicion de memoria del struct es: 0061FEAC
La posicion de memoria de la propiedad nombre es: 0061FEB0
La posicion de memoria de la propiedad apellidos es: 0061FECE
La posicion de memoria de la propiedad edad es: 0061FEAC

Por lo tanto, dada una variable siempre tenemos la posición de memoria donde se encuentra su contenido y el nombre de la variable.

Mediante los punteros podemos hacer referencia a las posiciones de memoria de nuestras variables, porque al fin y al cabo un puntero no es más que una variable cuyo interior es una dirección de memoria, veámoslo con el siguiente ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Persona{
    int edad;
    char nombre[30];
    char apellidos[50]; 
};

int main(){
    //Declaramos una variable llamada "a" cuyo contenido es 3
    int a = 3;
    //Declaramos una cadena
    char micadena[] = "Hola, esto es una cadena";
    //Declaramos un struct Persona 
    struct Persona p1;
    //Le asignamos valores
    p1.edad = 32;
    strncpy(p1.nombre, "Ivan\0",5);
    strncpy(p1.apellidos, "Ramon Moran\0",12);
    

    //Imprimimos el contenido de a y su direccion de memoria
    printf("El contenido de a es: %d\n", a);
    printf("a se encuentra en la posicion de memoria: %p\n", &a);

    //Imprimimos el contenido de micadena y su direccion de memoria
    printf("El contenido de micadena es: %s\n", micadena);
    printf("micadena se encuentra en la posicion de memoria: %p\n", micadena);
    //Otra forma de acceder a la posicion de memoria de un array
    printf("micadena se encuentra en la posicion de memoria: %p\n", &micadena[0]);

    //Imprimimos el contenido de nuestro struct Persona
    printf("Nombre: %s\n", p1.nombre);
    printf("Apellidos: %s\n", p1.apellidos);
    printf("Edad: %d\n", p1.edad);

    //Imprimimos las posiciones de memoria
    //Primero el struct
    printf("La posicion de memoria del struct es: %p\n", &p1);
    //Ahora la posicion de memoria de sus propiedades
    printf("La posicion de memoria de la propiedad nombre es: %p\n", &p1.nombre);
    printf("La posicion de memoria de la propiedad apellidos es: %p\n", &p1.apellidos);
    printf("La posicion de memoria de la propiedad edad es: %p\n", &p1.edad);

    //Punteros
    //Vamos a crear un puntero que apunte a la dirección de memoria de a
    int *puntero_variable_a = &a;
    //Mostramos su contenido
    printf("El contenido de \"a\" a traves de nuestro puntero es: %d\n", *puntero_variable_a);
    //Vamos a comprobar que realmente nuestro puntero apunta a "a"
    printf("La posicion de memoria de la variable a es: %p\n", &a);
    printf("La posicion de memoria del puntero a la variable a es %p\n", puntero_variable_a);

    //Vamos ahora a modificar el contenido de "a" a traves del puntero
    *puntero_variable_a = 5;
    //Imprimimos ahora el contenido de a y el contenido de la dirección de memoria donde apunta nuestro puntero
    //Como podeis observar a ha cambiado de valor
    printf("El nuevo contenido de a es: %d\n", a);
    printf("El nuevo contenido de \"a\" a traves del puntero es: %d\n", *puntero_variable_a);
    
    return 0;
}

La salida de nuestro programa

El contenido de a es: 3
a se encuentra en la posicion de memoria: 0061FF18
El contenido de micadena es: Hola, esto es una cadena
micadena se encuentra en la posicion de memoria: 0061FEFF
micadena se encuentra en la posicion de memoria: 0061FEFF
Nombre: Ivan
Apellidos: Ramon Moran
Edad: 32
La posicion de memoria del struct es: 0061FEA8
La posicion de memoria de la propiedad nombre es: 0061FEAC
La posicion de memoria de la propiedad apellidos es: 0061FECA
La posicion de memoria de la propiedad edad es: 0061FEA8
El contenido de "a" a traves de nuestro puntero es: 3
La posicion de memoria de la variable a es: 0061FF18
La posicion de memoria del puntero a la variable a es 0061FF18
El nuevo contenido de a es: 5
El nuevo contenido de "a" a traves del puntero es: 5

Como podéis observar al cambiar el contenido de nuestro puntero «puntero_variable_a», también cambia el contenido de «a», esto es así porque «puntero_variable_a» apunta a la dirección de memoria de «a»:

//Vamos a crear un puntero que apunte a la dirección de memoria de a
int *puntero_variable_a = &a;

¿ Cómo se declara un puntero ?

Para declarar un puntero es necesario definir su tipo. El tipo de datos de un puntero puede ser cualquier tipo de datos de C, veamos un ejemplo a continuación:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Persona{
    int edad;
    char nombre[30];
    char apellidos[50]; 
};

int main(){
    int *puntero_entero;
    float *puntero_flotante;
    double *puntero_double;
    struct Persona *puntero_persona;

    return 0;
}

Hemos definido un puntero de enteros, de floats, de doubles y de nuestro struct persona.

¿ Cómo asignamos memoria y asignamos contenido a nuestros punteros ?

Veámoslo mejor con un ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Persona{
    int edad;
    char nombre[30];
    char apellidos[50]; 
};

int main(){
    int *puntero_entero;
    float *puntero_flotante;
    double *puntero_double;
    struct Persona *puntero_persona;

    //necesitamos asignar memoria antes de asignar su contenido
    puntero_entero = (int *)malloc(sizeof(int));
    *puntero_entero = 5;
    printf("El contenido de nuestro puntero a entero es: %d\n", *puntero_entero);

    //necesitamos asignar memoria antes de asignar su contenido
    puntero_flotante = (float *)malloc(sizeof(float));
    *puntero_flotante = 5.3;
    printf("El contenido de nuestro puntero a float es: %f\n", *puntero_flotante);

    //necesitamos asignar memoria antes de asignar su contenido
    puntero_double = (double *)malloc(sizeof(double));
    *puntero_double = 5.7;
    printf("El contenido de nuestro puntero a double es: %f\n", *puntero_double);

    //necesitamos asignar memoria antes de asignar su contenido
    puntero_persona = (struct Persona *)malloc(sizeof(struct Persona));
    puntero_persona->edad = 32;
    strncpy(puntero_persona->nombre, "Ivan\0", 5);
    strncpy(puntero_persona->apellidos, "Ramon Moran\0", 12);

    printf("El contenido de Edad es: %d\n", puntero_persona->edad);
    printf("El contenido de Nombre es: %s\n", puntero_persona->nombre);
    printf("El contenido de Apellidos es: %s\n", puntero_persona->apellidos);

    //Liberamos la memoria
    free(puntero_entero);
    free(puntero_flotante);
    free(puntero_double);
    free(puntero_persona);
    
    return 0;
}

La salida de nuestro programa

El contenido de nuestro puntero a entero es: 5
El contenido de nuestro puntero a float es: 5.300000
El contenido de nuestro puntero a double es: 5.700000
El contenido de Edad es: 32
El contenido de Nombre es: Ivan
El contenido de Apellidos es: Ramon Moran

Es necesario fijarse en varias cosas:

  1. Cuando declaramos un puntero, para asignar contenido a dicho puntero es necesario reservar memoria con «malloc» o «calloc».
  2. La forma de trabajar con los punteros cambia cuando se trata de un struct o de una variable simple, fijaos en los printf y en como asignamos contenido:
    1. Para variables simples:
      1. Asignación de contenido: *puntero_double = 5.7;
      2. Mostrar su contenido: printf(«El contenido de nuestro puntero a double es: %f\n», *puntero_double);
    2. Para las propiedades de los struct:
      1. Asignación de contenido: puntero_persona->edad = 32;
      2. Mostrar su contenido: printf(«El contenido de Edad es: %d\n», puntero_persona->edad);

Os dejo una referencia a malloc y a calloc.

Arrays y aritmética de punteros

También es posible crear punteros que apunten a Arrays y crear Arrays utilizando punteros. Aquí utilizamos aritmética de punteros tanto para mostrar el contenido del Array como para asignar contenido en cada posición:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //Declaramos un Array de 5 posiciones de tipo entero
    int mis_numeros[5];
    
    //Asignamos valores
    mis_numeros[0] = 1;
    mis_numeros[1] = 2;
    mis_numeros[2] = 3;
    mis_numeros[3] = 4;
    mis_numeros[4] = 5;

    //Creamos un puntero que apunta al array
    int *puntero_a_array = mis_numeros;

    //Utilizamos aritmética de punteros para acceder al contenido de cada posición del array a través de su puntero
    printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros));
    printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros + 1));
    printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros + 2));
    printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros + 3));
    printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros + 4));

    //Ahora vamos a crear un array utilizando solo punteros
    int *mis_numeros2 = (int*)malloc(5 * sizeof(int));

    //Asignamos contenido con Aritmetica de Punteros
    *(mis_numeros2) = 6;
    *(mis_numeros2 + 1) = 7;
    *(mis_numeros2 + 2) = 8;
    *(mis_numeros2 + 3) = 9;
    *(mis_numeros2 + 4) = 10;

    //Mostramos su contenido
    printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros2));
    printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros2 + 1));
    printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros2 + 2));
    printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros2 + 3));
    printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros2 + 4));

    //Liberamos la memoria que hemos asignado
    free(mis_numeros2);
    
    return 0;
}

La salida de nuestro programa

Contenido de la posicion 0 del array: 1
Contenido de la posicion 1 del array: 2
Contenido de la posicion 2 del array: 3
Contenido de la posicion 3 del array: 4
Contenido de la posicion 4 del array: 5
Contenido de la posicion 0 del array: 6
Contenido de la posicion 1 del array: 7
Contenido de la posicion 2 del array: 8
Contenido de la posicion 3 del array: 9
Contenido de la posicion 4 del array: 10

En el ejemplo anterior primero hemos creado un Array de la forma tradicional, le asignamos su contenido y después mostramos su contenido mediante aritmética de punteros:

printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros));
printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros + 1));
printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros + 2));
printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros + 3));
printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros + 4));

Después creamos un array a través de un puntero, asignamos su contenido con aritmética de punteros y mostramos su contenido con la misma técnica:

//Ahora vamos a crear un array utilizando solo punteros
int *mis_numeros2 = (int*)malloc(5 * sizeof(int));

//Asignamos contenido con Aritmetica de Punteros
*(mis_numeros2) = 6;
*(mis_numeros2 + 1) = 7;
*(mis_numeros2 + 2) = 8;
*(mis_numeros2 + 3) = 9;
*(mis_numeros2 + 4) = 10;

//Mostramos su contenido
printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros2));
printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros2 + 1));
printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros2 + 2));
printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros2 + 3));
printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros2 + 4));

Fijaos que en la aritmética de punteros simplemente sumamos la posición del array a donde queremos acceder a la dirección de memoria base donde empieza nuestro array. Por ejemplo, para acceder a la posición 3, procedemos de la siguiente manera:

*(mis_numeros2 + 2) = 8;

Cabe destacar que aunque creemos nuestro Array mediante un puntero podemos acceder a él a través de «[indice]» con el método tradicional, veámoslo en el ejemplo anterior:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //Declaramos un Array de 5 posiciones de tipo entero
    int mis_numeros[5];
    
    //Asignamos valores
    mis_numeros[0] = 1;
    mis_numeros[1] = 2;
    mis_numeros[2] = 3;
    mis_numeros[3] = 4;
    mis_numeros[4] = 5;

    //Creamos un puntero que apunta al array
    int *puntero_a_array = mis_numeros;

    //Utilizamos aritmética de punteros para acceder al contenido de cada posición del array a través de su puntero
    printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros));
    printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros + 1));
    printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros + 2));
    printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros + 3));
    printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros + 4));

    //Ahora vamos a crear un array utilizando solo punteros
    int *mis_numeros2 = (int*)malloc(5 * sizeof(int));

    //Asignamos contenido con Aritmetica de Punteros
    *(mis_numeros2) = 6;
    *(mis_numeros2 + 1) = 7;
    *(mis_numeros2 + 2) = 8;
    *(mis_numeros2 + 3) = 9;
    *(mis_numeros2 + 4) = 10;

    //Mostramos su contenido
    printf("Contenido de la posicion 0 del array: %d\n", *(mis_numeros2));
    printf("Contenido de la posicion 1 del array: %d\n", *(mis_numeros2 + 1));
    printf("Contenido de la posicion 2 del array: %d\n", *(mis_numeros2 + 2));
    printf("Contenido de la posicion 3 del array: %d\n", *(mis_numeros2 + 3));
    printf("Contenido de la posicion 4 del array: %d\n", *(mis_numeros2 + 4));

    //Ahora accedemos a traves de [] a las posiciones de nuestro array creado con punteros
     //Mostramos su contenido
    printf("Contenido de la posicion 0 del array: %d\n", mis_numeros2[0]);
    printf("Contenido de la posicion 1 del array: %d\n", mis_numeros2[1]);
    printf("Contenido de la posicion 2 del array: %d\n", mis_numeros2[2]);
    printf("Contenido de la posicion 3 del array: %d\n", mis_numeros2[3]);
    printf("Contenido de la posicion 4 del array: %d\n", mis_numeros2[4]);
    //Liberamos la memoria que hemos asignado
    free(mis_numeros2);
    
    return 0;
}

La salida de nuestro programa

Contenido de la posicion 0 del array: 1
Contenido de la posicion 1 del array: 2
Contenido de la posicion 2 del array: 3
Contenido de la posicion 3 del array: 4
Contenido de la posicion 4 del array: 5
Contenido de la posicion 0 del array: 6
Contenido de la posicion 1 del array: 7
Contenido de la posicion 2 del array: 8
Contenido de la posicion 3 del array: 9
Contenido de la posicion 4 del array: 10
Contenido de la posicion 0 del array: 6
Contenido de la posicion 1 del array: 7
Contenido de la posicion 2 del array: 8
Contenido de la posicion 3 del array: 9
Contenido de la posicion 4 del array: 10

Punteros a función

Por último en este artículo, vamos a ver los punteros a función, sí, en C podemos declarar un puntero a una función, ya sé que suena muy raro, pero se puede hacer. Veamos un ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int sumar(int a, int b);

int main(){
    //llamada normal
    int resultado = sumar(3,5);
    printf("El resultado de la suma es: %d\n", resultado);

    //llamada a través de un puntero a función
    int (*puntero_a_funcion)(int, int) = &sumar;
    resultado = (*puntero_a_funcion)(3,6);
    printf("El resultado de la suma es: %d\n", resultado);

    return 0;
}

int sumar(int a, int b){
    return a + b;
}

El formato del puntero es el siguiente:

tipo_de_datos_que_devuelve_la_funcion (*nombre_puntero)(parametros_de_la_función)

int (*puntero_a_funcion)(int, int) = &sumar;

y para llamar a nuestra función:

resultado = (*puntero_a_funcion)(3,6);

La salida de nuestro programa

El resultado de la suma es: 8
El resultado de la suma es: 9

Y esto ha sido todo sobre «explicamos los punteros en C», en la próxima entrada sobre punteros hablaremos del operador new y delete disponibles en C++.

Espero que os haya gustado, dejad un comentario en el blog si ha sido así y os ha servido. Compartirlo en redes sociales para ayudar a su difusión.

Un saludo programadores.


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *