Punteros

Los punteros permiten almacenar referencias a posiciones de memoria y los usamos para acceder y manipular el valor de celdas concretas de memoria.

Cuando programamos en C, una de las herramientas más poderosas y a la vez complejas que encontramos son los punteros. Para entenderlos bien, primero debemos imaginar la memoria de nuestro ordenador como un enorme casillero lleno de celdas, cada una con una dirección única. Cuando declaramos una variable, como por ejemplo x, lo que realmente hacemos es reservar una de esas celdas para guardar un valor. Pero para que el ordenador pueda acceder a ese valor, necesita saber en qué casillero está guardado, es decir, su dirección de memoria.

Los punteros son variables especiales que almacenan precisamente esas direcciones de memoria. Esto nos permite no solo recuperar el valor almacenado en una dirección concreta, sino también modificarlo directamente, sin necesidad de trabajar con copias. Sin embargo, debemos ser muy cuidadosos, porque si intentamos acceder a una dirección que no nos pertenece o que no está reservada para nuestro programa, el sistema operativo nos lo impedirá y nuestro programa se cerrará abruptamente.

Para declarar un puntero en C, usamos el tipo de dato que apunta seguido de un asterisco. Por ejemplo, si tenemos una variable entera x, podemos declarar un puntero a entero así:

int x = 10;
int *dir_x = &x;

Aquí, &x nos da la dirección de memoria donde está almacenada x, y la guardamos en dir_x. Es importante que el tipo del puntero coincida con el tipo de dato al que apunta, para que el ordenador sepa cuántos bytes debe leer o escribir en esa dirección.

Una de las aplicaciones más importantes de los punteros es en el paso de argumentos a funciones. Cuando pasamos una variable normal a una función, lo que realmente se pasa es una copia de su valor. Por ejemplo:

void jugar(int n) {
    n = n + 3 * 2 - 1;
    printf("%d\n", n);
}

int main() {
    int x = 10;
    printf("%d\n", x); // Imprime 10
    jugar(x);          // Imprime 25
    printf("%d\n", x); // Sigue imprimiendo 10
    return 0;
}

Aunque dentro de la función jugar modificamos n, la variable original x no cambia porque solo trabajamos con una copia. Pero si queremos modificar directamente el valor original, debemos pasar la dirección de memoria usando punteros:

void jugar(int *n) {
    int y = *n; // Recuperamos el valor apuntado por n
    y = y + 2 / 2 + 2 - 3 * y; // Operación cualquiera
    *n = y; // Guardamos el nuevo valor en la dirección apuntada por n
}

int main() {
    int x = 10;
    printf("%d\n", x); // Imprime 10
    jugar(&x);
    printf("%d\n", x); // Ahora imprime el valor modificado
    return 0;
}

Aquí, al pasar &x a la función, le damos la dirección de x. Dentro de jugar, usamos el operador * para acceder al valor almacenado en esa dirección y modificarlo. Así, el cambio afecta directamente a la variable original.

Este mecanismo de pasar referencias mediante punteros es fundamental para evitar copias innecesarias, especialmente cuando trabajamos con estructuras o arrays grandes, que pueden consumir mucha memoria si se copian. Además, nos permite que una función modifique varios valores a la vez, simplemente recibiendo punteros a esas variables.

En resumen, los punteros nos permiten manejar la memoria de forma eficiente y directa, accediendo y modificando datos en sus ubicaciones reales. Aunque al principio pueden parecer complicados y propensos a errores, con práctica y cuidado se convierten en una herramienta esencial para programar en C de manera efectiva.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos