Curso de C · Módulo 7 de 12

Punteros en C explicados desde cero

Qué aprenderás en este módulo

dominar la herramienta más potente (y temida) de C. Si entiendes este módulo, entiendes C.

Incluye 4 ejercicios con pistas y solución y un quiz de 6 preguntas. Tiempo estimado: 100 minutos.

1. La memoria es una fila de casillas numeradas

Cada byte tiene una dirección. Una variable int x = 42; ocupa 4 bytes en alguna dirección.

Un puntero es una variable cuyo valor es una dirección.

int x = 42;
int *p = &x;      // p guarda la dirección de x   ("p apunta a x")

printf("%d\n", *p);   // 42   -> *p = "el valor al que apunta p"  (desreferenciar)
*p = 100;             // cambia x a 100 a través de p
printf("%d\n", x);    // 100
   x  [ 100 ]  dirección 0x1000
   p  [0x1000]  ──────▶ apunta a x
  • &x → "dirección de x".
  • *p → "lo que hay en la dirección p". (El * de la declaración int *p solo significa "p es puntero a int".)
  • NULL = "no apunta a nada". Desreferenciarlo → segmentation fault. Inicializa los punteros: int *p = NULL;.

2. Por qué existen: modificar variables ajenas

Como los argumentos se copian, para que una función cambie algo del llamador le das su dirección:

void intercambiar(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}
...
intercambiar(&x, &y);

¡Ahora sabes qué hace el & de scanf("%d", &n)! Le pasas dónde escribir. También permite devolver varios valores (ejercicio 7.2).

3. Punteros y arrays: son casi lo mismo

El nombre de un array "decae" a un puntero a su primer elemento:

int v[4] = {10, 20, 30, 40};
int *p = v;          // = &v[0]
p[2]  == v[2]        // 30
*(p + 2) == v[2]     // 30   (p + 2 avanza 2 ENTEROS, no 2 bytes)

v[i] es, literalmente, azúcar para *(v + i). Aritmética de punteros: p + n avanza n * sizeof(*p) bytes; p2 - p1 da el nº de elementos entre ambos.

Una cadena es un char * que recorres hasta '\0':

int longitud(const char *s) { const char *p = s; while (*p) p++; return p - s; }

4. const con punteros

const int *p;        // no puedes cambiar *p (lectura); sí cambiar p
int *const q = &x;   // no puedes cambiar q; sí *q

Usa const char * / const int * en los parámetros que solo lees: documenta intención y el compilador te protege.

5. Los 4 pecados capitales

Error Qué pasa
Usar un puntero sin inicializar apunta a cualquier sitio: crash o corrupción
Desreferenciar NULL segmentation fault
Devolver la dirección de una variable local puntero colgante: la variable ya murió
Salirte del array con aritmética comportamiento indefinido
int *mal(void) { int x = 5; return &x; }   // ❌ x desaparece al salir

6. Punteros a punteros y a funciones (avance)

  • char **argv es un array de cadenas: lo verás en main(int argc, char **argv).
  • Un puntero a función permite pasar comportamiento como parámetro: así funciona qsort (módulo 9).

Truco mental

Dibuja cajas y flechas. Siempre. Cada variable es una caja con su valor; cada puntero es una caja con una flecha. *p = seguir la flecha. &x = dibujar una flecha hacia x.

Prueba esto

  1. Imprime x, &x, p, *p, &p (con %p para direcciones: printf("%p", (void*)&x)).
  2. Recorre un array con for (int *p = v; p < v + n; p++).
  3. Escribe void a_mayusculas(char *s) que modifique la cadena en su sitio con un puntero.

Ejercicios prácticos del módulo 7

Programar se aprende programando: intenta cada ejercicio antes de mirar las pistas, y usa la solución solo para comparar. Marca los que resuelvas y tu progreso se guarda en este navegador.

7.1 swap con punteros
Implementa void intercambiar(int *a, int *b) que intercambie los valores de dos variables del llamador. El main lee x e y, los intercambia e imprime "x=.. y=..".

Entrada de ejemplo

3 8

Salida esperada

x=8 y=3

Tu punto de partida

#include <stdio.h>

void intercambiar(int *a, int *b) {
    // TODO
}

int main(void) {
    int x, y;
    scanf("%d %d", &x, &y);
    intercambiar(&x, &y);
    printf("x=%d y=%d\n", x, y);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

*a es 'el valor al que apunta a'. Necesitas una variable temporal.

Pista 2

int t = *a; *a = *b; *b = t;

Ver solución (inténtalo antes)
#include <stdio.h>

void intercambiar(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int main(void) {
    int x, y;
    scanf("%d %d", &x, &y);
    intercambiar(&x, &y);
    printf("x=%d y=%d\n", x, y);
    return 0;
}
7.2 Devolver dos valores
Una funcion solo puede devolver un valor... salvo que escriba en punteros. Implementa void min_max(const int *v, int n, int *min, int *max) que guarde en *min y *max el menor y el mayor de los n elementos de v.

Entrada de ejemplo

5
4 -2 9 0 3

Salida esperada

min=-2 max=9

Tu punto de partida

#include <stdio.h>

void min_max(const int *v, int n, int *min, int *max) {
    // TODO
}

int main(void) {
    int v[100], n, mn, mx;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    min_max(v, n, &mn, &mx);
    printf("min=%d max=%d\n", mn, mx);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

Inicializa *min y *max con v[0] y recorre el resto.

Pista 2

Dentro de la funcion, siempre con *: *min = ..., if (v[i] < *min)

Ver solución (inténtalo antes)
#include <stdio.h>

void min_max(const int *v, int n, int *min, int *max) {
    *min = *max = v[0];
    for (int i = 1; i < n; i++) {
        if (v[i] < *min) *min = v[i];
        if (v[i] > *max) *max = v[i];
    }
}

int main(void) {
    int v[100], n, mn, mx;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    min_max(v, n, &mn, &mx);
    printf("min=%d max=%d\n", mn, mx);
    return 0;
}
7.3 Aritmetica de punteros
Implementa int suma(const int *ini, const int *fin) que sume los elementos desde ini hasta fin (sin incluir fin) recorriendo con un puntero: for (const int *p = ini; p < fin; p++). NO uses indices v[i] dentro de la funcion.

Entrada de ejemplo

6
1 2 3 4 5 6

Salida esperada

total=21
mitad=6

Tu punto de partida

#include <stdio.h>

int suma(const int *ini, const int *fin) {
    // TODO
    return 0;
}

int main(void) {
    int v[100], n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    printf("total=%d\n", suma(v, v + n));
    printf("mitad=%d\n", suma(v, v + n / 2));
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

p++ avanza al SIGUIENTE int (no al siguiente byte): el compilador multiplica por sizeof(int).

Pista 2

*p da el valor al que apunta p.

Ver solución (inténtalo antes)
#include <stdio.h>

int suma(const int *ini, const int *fin) {
    int s = 0;
    for (const int *p = ini; p < fin; p++) s += *p;
    return s;
}

int main(void) {
    int v[100], n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    printf("total=%d\n", suma(v, v + n));
    printf("mitad=%d\n", suma(v, v + n / 2));
    return 0;
}
7.4 Tu propio strlen y strcpy
Reimplementa dos funciones de <string.h> usando solo punteros (sin llamar a string.h): int mi_strlen(const char *s) void mi_strcpy(char *destino, const char *origen) (copia tambien el '\0') El main lee una palabra, la copia y muestra longitud y copia.

Entrada de ejemplo

hola

Salida esperada

longitud=4 copia=hola

Tu punto de partida

#include <stdio.h>

int mi_strlen(const char *s) {
    // TODO
    return 0;
}

void mi_strcpy(char *destino, const char *origen) {
    // TODO
}

int main(void) {
    char a[100], b[100];
    scanf("%99s", a);
    mi_strcpy(b, a);
    printf("longitud=%d copia=%s\n", mi_strlen(a), b);
    return 0;
}

Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.

Pista 1

La cadena acaba en '\0', que vale 0 = falso: while (*p) p++;

Pista 2

La longitud es la distancia entre el final y el inicio: p - s.

Pista 3

strcpy: while ((*d = *o) != '\0') { d++; o++; } y despues *d = '\0'; (o la version compacta con post-incremento).

Ver solución (inténtalo antes)
#include <stdio.h>

int mi_strlen(const char *s) {
    const char *p = s;
    while (*p) p++;
    return (int)(p - s);
}

void mi_strcpy(char *destino, const char *origen) {
    while ((*destino++ = *origen++))
        ;
}

int main(void) {
    char a[100], b[100];
    scanf("%99s", a);
    mi_strcpy(b, a);
    printf("longitud=%d copia=%s\n", mi_strlen(a), b);
    return 0;
}

Quiz del módulo 7

6 preguntas para comprobar que has fijado los conceptos. Las opciones se barajan cada vez.