Structs y cadenas en C — ejercicios con la cartelera, tus propias funciones de string.h y fichas de espectadores que llegan rotas (y la entrada VIP que siempre costaba 8 euros)
Preparando el parcial de IC2 hice un programita de taquilla: si el cliente pedía entrada «VIP», costaba 12 euros; si no, 8. Lo probé con precio("VIP") y daba 12. Perfecto. Después cambié el programa para guardar lo que elegía el cliente en un array y, de repente, todas las entradas VIP costaban 8. El código de la comparación era el mismo. Tardé una tarde en entender que nunca había comparado letras: estaba comparando direcciones, y la primera vez acerté por pura suerte. Lo vas a ver en directo en el fragmento 2 de «¿Qué falla aquí?».
Estos ejercicios son la tercera parte del tema, después de la teoría y la práctica de structs y cadenas en C. Aquí ya no te doy el código antes: primero lo intentas tú.
Tabla de Contenidos
Cómo usar estos ejercicios
- Lee el enunciado y la salida esperada. Tu programa tiene que dar exactamente esa salida con esos datos.
- Intenta resolverlo sin mirar la solución. Si te atascas más de 10 minutos, abre la pista.
- Compila con
gcc -Wall -std=c11y no des el ejercicio por bueno si hay avisos. - Después haz «¿Qué falla aquí?»: intenta predecir la salida antes de leer la explicación.
- Compara con las soluciones: no hay una única forma buena, pero fíjate en las decisiones (por qué
>=y no>, por qué un puntero y no una copia…).
Al final tienes el chuletario con todo lo del tema en una página.
Nivel 1: la cartelera
Declara un struct pelicula con un título de hasta 29 caracteres, la duración en minutos y la sala (una letra). Crea un array con esta cartelera: Coco (105 min, sala A), Interstellar (169 min, sala B), Up (96 min, sala A) y La sociedad de la nieve (144 min, sala C). El programa tiene que:
- Imprimir cada película con su sala y la duración en horas y minutos (los minutos siempre con dos cifras).
- Decir cuál es la película más larga.
- Decir cuál tiene el título más largo y cuántos caracteres tiene.
Salida esperada:
Sala A Coco 1 h 45 min Sala B Interstellar 2 h 49 min Sala A Up 1 h 36 min Sala C La sociedad de la nieve 2 h 24 min La más larga: Interstellar (169 min) Título más largo: La sociedad de la nieve (23 caracteres)
Las horas son minutos / 60 (división entera) y los minutos que sobran, minutos % 60. Para el título más largo no uses sizeof(titulo): daría 30 en todas.
Nivel 2: tus propias funciones de string.h
Escribe sin usar string.h (salvo para comparar resultados al final) estas tres funciones, recorriendo las cadenas con punteros:
size_t mi_strlen(const char *s): cuántos caracteres hay hasta el'\0'.char *mi_strcpy(char *destino, const char *origen): copiaorigenendestino, nulo incluido, y devuelvedestino.int mi_strcmp(const char *a, const char *b): 0 si son iguales, negativo siava antes y positivo si va después. Devuelve la resta de los códigos del primer carácter distinto.
Pruébalas con las parejas («Ana», «Ana»), («Ana», «Anabel»), («Bruno», «Ana») y («ana», «Ana»), comparando el signo de tu mi_strcmp con el de strcmp, y copia «Pepito» en un array de 10 con tu mi_strcpy.
Salida esperada:
Ana Ana mi_strcmp = 0 (signo 0) | strcmp signo 0 Ana Anabel mi_strcmp = -98 (signo -1) | strcmp signo -1 Bruno Ana mi_strcmp = 1 (signo 1) | strcmp signo 1 ana Ana mi_strcmp = 32 (signo 1) | strcmp signo 1 mi_strcpy -> Pepito, mi_strlen = 6, strlen = 6
En «Ana» contra «Anabel» no hay ninguna letra distinta en las 3 primeras posiciones. La diferencia está en la cuarta: el '\0' de «Ana» contra la ‘b’ de «Anabel». Por eso el bucle tiene que parar cuando se acaba la primera cadena, no solo cuando las letras difieren.
Nivel 3: fichas de espectadores que llegan rotas
El sistema del cine exporta cada espectador como una línea de texto con el formato DNI;Nombre;Butaca, por ejemplo 42345678;Pepito;A3. Escribe int leer_ficha(const char *linea, struct ficha *f) que separe la línea y rellene un struct ficha (DNI como int, nombre de hasta 11 caracteres y butaca de hasta 3), devolviendo 0 si todo va bien o un código de error:
| Código | Error |
|---|---|
| 1 | Faltan los ; |
| 2 | El DNI no tiene exactamente 8 cifras |
| 3 | El nombre está vacío o no cabe |
| 4 | La butaca tiene menos de 2 caracteres o no cabe |
Procesa estas seis líneas, guarda solo las válidas, informa de cada error y busca quién está en la C12:
42345678;Pepito;A3 12343278;Juan;C12 1234567;Ana;B1 33333333;Maximiliano Ruiz;C6 44444444 Lucia A4 55555555;Ivan;
Salida esperada:
Línea 3 (1234567;Ana;B1): DNI no válido Línea 4 (33333333;Maximiliano Ruiz;C6): nombre vacío o demasiado largo Línea 5 (44444444 Lucia A4): faltan los ';' Línea 6 (55555555;Ivan;): butaca no válida Fichas válidas: 2 de 6 A3 42345678 Pepito C12 12343278 Juan En la C12 está Juan
strchr(linea, ';') te da un puntero al primer ; y strchr(p1 + 1, ';') al segundo. Restando punteros sabes cuánto mide cada campo antes de copiar nada. El nombre no termina en '\0' dentro de la línea: cópialo con memcpy y pon tú el nulo.
¿Qué falla aquí?
Tres fragmentos con un error típico del tema. Lee cada uno, predice qué imprime y después mira la salida real y la explicación.
Fragmento 1: la etiqueta que «sobraba»
#include <stdio.h>
#include <string.h>
// Imprime la etiqueta que se pega en la butaca
void etiqueta(const char *nombre) {
// "Para nombres cortos sobra"
char corto[6];
strcpy(corto, nombre);
printf("Etiqueta: %s\n", corto);
}
int main(void) {
etiqueta("Ana");
etiqueta("Pepito");
return 0;
}
¿Qué imprime? Piénsalo antes de seguir. Lo compilé con AddressSanitizer (-g -fsanitize=address) y esto es lo que salió:
Etiqueta: Ana
==386==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f258f300066
WRITE of size 7 at 0x7f258f300066 thread T0
#1 in etiqueta fragmento.c:8
#2 in main fragmento.c:14
Address 0x7f258f300066 is located in stack of thread T0 at offset 38 in frame
#0 in etiqueta fragmento.c:5
This frame has 1 object(s):
[32, 38) 'corto' (line 7) <== Memory access at offset 38 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/asan/asan_interceptors.cpp:563 in strcpy
La primera etiqueta sale bien: «Ana» son 3 letras + '\0' = 4 bytes, y caben en 6. La segunda revienta: «Pepito» son 6 letras, que es justo el tamaño de corto… pero strcpy copia también el nulo, 7 bytes. AddressSanitizer lo dice con todas las letras: WRITE of size 7, en la línea 8 (el strcpy), y el acceso cae en el offset 38, justo después de los 6 bytes de corto (que van del 32 al 37).
Sin AddressSanitizer, este programa podría imprimir «Etiqueta: Pepito» y parecer correcto: el byte de más cae en memoria de la pila que nadie está usando en ese momento. Por eso estos errores son tan traicioneros. La corrección:
#include <stdio.h>
void etiqueta(const char *nombre) {
char corto[12];
// snprintf no escribe nunca más de sizeof corto bytes, '\0' incluido
snprintf(corto, sizeof corto, "%s", nombre);
printf("Etiqueta: %s\n", corto);
}
int main(void) {
etiqueta("Ana");
etiqueta("Pepito");
return 0;
}
Con snprintf(corto, sizeof corto, "%s", nombre) es imposible pasarse: si el nombre no cupiera, se recortaría. Y el array ahora tiene 12 bytes para nombres normales. Salida corregida:
Etiqueta: Ana Etiqueta: Pepito
Fragmento 2: la entrada VIP
#include <stdio.h>
double precio(const char *tipo) {
if (tipo == "VIP")
return 12.0;
return 8.0;
}
int main(void) {
// Lo que eligió el cliente, guardado en un array propio
char elegido[] = "VIP";
printf("Entrada %s: %.2f euros\n", elegido, precio(elegido));
return 0;
}
¿Cuánto cuesta la entrada? Al compilar, gcc ya avisa:
fragmento.c: In function 'precio': fragmento.c:4:14: warning: comparison with string literal results in unspecified behavior [-Waddress]
Y la salida:
Entrada VIP: 8.00 euros
El cliente eligió «VIP» y paga 8 euros. tipo == "VIP" compara dos direcciones: la del array elegido (en la pila de main) y la del literal «VIP» (en la memoria de solo lectura). Nunca son la misma, así que la condición es siempre falsa.
¿Y por qué en mi anécdota funcionaba al principio? Porque llamaba a precio("VIP") con un literal, y gcc suele guardar los literales idénticos una sola vez: los dos «VIP» eran el mismo puntero. Funcionaba por casualidad, que es lo que el aviso llama unspecified behavior. La corrección es comparar letras:
#include <stdio.h>
#include <string.h>
double precio(const char *tipo) {
// strcmp compara letra a letra; == solo compararía direcciones
if (strcmp(tipo, "VIP") == 0)
return 12.0;
return 8.0;
}
int main(void) {
char elegido[] = "VIP";
printf("Entrada %s: %.2f euros\n", elegido, precio(elegido));
return 0;
}
Ahora el aviso desaparece y el cliente VIP paga lo que toca:
Entrada VIP: 12.00 euros
Fragmento 3: la butaca que no se ocupaba
#include <stdio.h>
struct butaca {
char fila;
int numero;
int ocupada;
};
void ocupar(struct butaca b) {
b.ocupada = 1;
printf("Dentro de ocupar: %c%d ocupada=%d\n", b.fila, b.numero, b.ocupada);
}
int main(void) {
struct butaca b = {'B', 4, 0};
ocupar(b);
printf("En main: %c%d ocupada=%d\n", b.fila, b.numero, b.ocupada);
return 0;
}
¿Qué valor tiene ocupada en main? La salida:
Dentro de ocupar: B4 ocupada=1 En main: B4 ocupada=0
Dentro de ocupar, la butaca está ocupada; en main, no. ocupar recibe el struct por valor: C copia los 12 bytes de b en el parámetro, la función cambia la copia y la copia desaparece al volver. Que el parámetro se llame igual que la variable de main no cambia nada: son dos butacas distintas. La corrección es pasar la dirección y usar la flecha:
#include <stdio.h>
struct butaca {
char fila;
int numero;
int ocupada;
};
// Recibe la DIRECCIÓN de la butaca: los cambios llegan a la de main
void ocupar(struct butaca *b) {
b->ocupada = 1;
printf("Dentro de ocupar: %c%d ocupada=%d\n", b->fila, b->numero, b->ocupada);
}
int main(void) {
struct butaca b = {'B', 4, 0};
ocupar(&b);
printf("En main: %c%d ocupada=%d\n", b.fila, b.numero, b.ocupada);
return 0;
}
Con la dirección, el cambio llega a main:
Dentro de ocupar: B4 ocupada=1 En main: B4 ocupada=1
«arreglar» el fragmento 3 haciendo que ocupar devuelva la butaca (b = ocupar(b);). Funciona, pero copia el struct dos veces en cada llamada. Con un puntero no se copia nada.
Soluciones
Solución del nivel 1
#include <stdio.h>
#include <string.h>
struct pelicula {
char titulo[30]; // 29 caracteres como mucho + '\0'
int minutos;
char sala;
};
int main(void) {
struct pelicula cartelera[] = {
{"Coco", 105, 'A'},
{"Interstellar", 169, 'B'},
{"Up", 96, 'A'},
{"La sociedad de la nieve", 144, 'C'},
};
int n = sizeof cartelera / sizeof cartelera[0];
// Guardamos POSICIONES, no copias: así luego accedemos al struct original
int mas_largo = 0, mas_titulo = 0;
for (int i = 0; i < n; i++) {
// Puntero para escribir p->campo en lugar de cartelera[i].campo
struct pelicula *p = &cartelera[i];
// División entera: 169 / 60 = 2 horas; el resto, 169 % 60 = 49 minutos
printf("Sala %c %-24s %d h %02d min\n", p->sala, p->titulo, p->minutos / 60, p->minutos % 60);
if (p->minutos > cartelera[mas_largo].minutos)
mas_largo = i;
// strlen cuenta caracteres hasta el '\0'; sizeof daría siempre 30
if (strlen(p->titulo) > strlen(cartelera[mas_titulo].titulo))
mas_titulo = i;
}
printf("La más larga: %s (%d min)\n", cartelera[mas_largo].titulo, cartelera[mas_largo].minutos);
printf("Título más largo: %s (%zu caracteres)\n", cartelera[mas_titulo].titulo, strlen(cartelera[mas_titulo].titulo));
return 0;
}
Las decisiones:
- **Guardo posiciones (
mas_largo,mas_titulo), no copias.** Empiezan en 0 (la primera película) y cambian cuando aparece una mejor. Al final,cartelera[mas_largo]es el struct original. Copiar el struct entero cada vez también funcionaría, pero son 40 bytes (30 del título + 4 delint+ 1 de la sala + 3 de relleno) en cada cambio, para nada. - **
struct pelicula *p = &cartelera[i];** solo es comodidad:p->minutosse lee mejor quecartelera[i].minutosrepetido cuatro veces. - **
>y no>=.** Con>=, si hubiera dos películas igual de largas, ganaría la última; con>, la primera. Aquí no hay empate, pero conviene decidirlo a propósito. - **
%02d** rellena con un cero a la izquierda: 1 h 05 min en lugar de 1 h 5 min.
Las cuentas de la salida: 105 / 60 = 1 y 105 % 60 = 45 → 1 h 45 min; 169 / 60 = 2 y 169 − 120 = 49 → 2 h 49 min; 96 → 1 h 36 min; 144 → 2 h 24 min. La más larga es Interstellar (169 > 144 > 105 > 96). En títulos: Coco 4, Interstellar 12, Up 2 y «La sociedad de la nieve» 23 (con sus 4 espacios, que también son caracteres).
Solución del nivel 2
#include <stdio.h>
#include <string.h>
// Cuenta hasta el '\0'
size_t mi_strlen(const char *s) {
const char *p = s;
while (*p != '\0')
p++;
// La distancia entre dos punteros es el número de caracteres recorridos
return p - s;
}
// Copia también el '\0': por eso el bucle mira el carácter DESPUÉS de copiarlo
char *mi_strcpy(char *destino, const char *origen) {
char *d = destino;
while ((*d++ = *origen++) != '\0')
;
// Como la de verdad, devuelve destino
return destino;
}
// Avanza mientras sean iguales y no se acabe la primera; devuelve la diferencia del primer carácter distinto
int mi_strcmp(const char *a, const char *b) {
while (*a != '\0' && *a == *b) {
a++;
b++;
}
// unsigned char: así 'ñ' u otros bytes altos no salen negativos
return (unsigned char)*a - (unsigned char)*b;
}
// Solo nos importa el signo: la de string.h puede devolver otro número con el mismo signo
int signo(int x) {
return (x > 0) - (x < 0);
}
int main(void) {
const char *pruebas[][2] = {
{"Ana", "Ana"}, {"Ana", "Anabel"}, {"Bruno", "Ana"}, {"ana", "Ana"},
};
for (int i = 0; i < 4; i++) {
const char *a = pruebas[i][0], *b = pruebas[i][1];
printf("%-6s %-7s mi_strcmp = %4d (signo %2d) | strcmp signo %2d\n",
a, b, mi_strcmp(a, b), signo(mi_strcmp(a, b)), signo(strcmp(a, b)));
}
char copia[10];
mi_strcpy(copia, "Pepito");
printf("mi_strcpy -> %s, mi_strlen = %zu, strlen = %zu\n", copia, mi_strlen(copia), strlen(copia));
return 0;
}
Vamos función a función:
// Cuenta hasta el '\0'
size_t mi_strlen(const char *s) {
const char *p = s;
while (*p != '\0')
p++;
// La distancia entre dos punteros es el número de caracteres recorridos
return p - s;
}
mi_strlen avanza un segundo puntero p hasta el nulo y devuelve p - s: restar dos punteros a char da cuántos caracteres hay entre ellos. Para «Pepito», p acaba en la posición 6 y 6 − 0 = 6.
// Copia también el '\0': por eso el bucle mira el carácter DESPUÉS de copiarlo
char *mi_strcpy(char *destino, const char *origen) {
char *d = destino;
while ((*d++ = *origen++) != '\0')
;
// Como la de verdad, devuelve destino
return destino;
}
mi_strcpy es la línea más compacta del artículo: *d++ = *origen++ copia un carácter y avanza los dos punteros, y la comparación con '\0' se hace después de copiar. Así, cuando llega el nulo, primero se copia y luego el bucle para: el destino queda terminado. El cuerpo del while está vacío (el ; solo en su línea) porque todo el trabajo está en la condición. Para «Pepito»:
| Vuelta | Carácter copiado | ¿Era ‘\0’? | destino hasta ahora |
|---|---|---|---|
| 1 | P | no | P |
| 2 | e | no | Pe |
| 3-6 | p, i, t, o | no | Pepito |
| 7 | ‘\0’ | sí → para | Pepito (terminado) |
// Avanza mientras sean iguales y no se acabe la primera; devuelve la diferencia del primer carácter distinto
int mi_strcmp(const char *a, const char *b) {
while (*a != '\0' && *a == *b) {
a++;
b++;
}
// unsigned char: así 'ñ' u otros bytes altos no salen negativos
return (unsigned char)*a - (unsigned char)*b;
}
mi_strcmp avanza mientras los caracteres sean iguales y la primera cadena no se haya acabado. Si a se acaba, *a es '\0'; si b se acaba antes, *a == *b falla (una letra contra el 0). En los dos casos el bucle para y la resta da el resultado. Las cuentas de la salida:
| a | b | Primer carácter distinto | Resta | Signo |
|---|---|---|---|---|
| Ana | Ana | ninguno (los dos '\0') | 0 − 0 = 0 | 0 |
| Ana | Anabel | posición 3: '\0' y ‘b’ | 0 − 98 = −98 | −1 |
| Bruno | Ana | posición 0: ‘B’ y ‘A’ | 66 − 65 = 1 | 1 |
| ana | Ana | posición 0: ‘a’ y ‘A’ | 97 − 65 = 32 | 1 |
Los signos coinciden con los de strcmp en las cuatro parejas. Los números no tienen por qué coincidir (el estándar solo promete el signo); por eso main compara signos con la función signo, que usa un truco: (x > 0) - (x < 0) vale 1, 0 o −1 porque cada comparación vale 1 o 0.
El cast a unsigned char en la resta no es decoración. Los caracteres de más de 127 (como los bytes de una ‘ñ’ en UTF-8) son negativos en un char con signo, y la comparación saldría al revés. strcmp compara siempre como unsigned char.
Solución del nivel 3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
struct ficha {
int dni;
char nombre[12]; // 11 caracteres + '\0'
char butaca[4]; // 3 caracteres + '\0'
};
// Lee "DNI;Nombre;Butaca". Devuelve 0 si va bien o el número del error
int leer_ficha(const char *linea, struct ficha *f) {
// strchr devuelve un puntero al ';' (o NULL): así sabemos dónde acaba cada campo
const char *p1 = strchr(linea, ';');
if (p1 == NULL)
return 1;
const char *p2 = strchr(p1 + 1, ';');
if (p2 == NULL)
return 1;
// Restar punteros da cuántos caracteres hay entre ellos
size_t largo_dni = p1 - linea;
if (largo_dni != 8)
return 2;
for (const char *c = linea; c < p1; c++)
if (!isdigit((unsigned char)*c))
return 2;
size_t largo_nombre = p2 - (p1 + 1);
// >= y no >: hay que dejar sitio para el '\0'
if (largo_nombre == 0 || largo_nombre >= sizeof f->nombre)
return 3;
size_t largo_butaca = strlen(p2 + 1);
if (largo_butaca < 2 || largo_butaca >= sizeof f->butaca)
return 4;
// Solo escribimos en f cuando todo está comprobado
// atoi lee cifras y se para sola en el ';'
f->dni = atoi(linea);
// El nombre no acaba en '\0' dentro de la línea: copiamos largo_nombre bytes y lo ponemos nosotros
memcpy(f->nombre, p1 + 1, largo_nombre);
f->nombre[largo_nombre] = '\0';
// La butaca es el final de la línea: ya trae su '\0'
strcpy(f->butaca, p2 + 1);
return 0;
}
int main(void) {
const char *lineas[] = {
"42345678;Pepito;A3",
"12343278;Juan;C12",
"1234567;Ana;B1",
"33333333;Maximiliano Ruiz;C6",
"44444444 Lucia A4",
"55555555;Ivan;",
};
const char *errores[] = {"", "faltan los ';'", "DNI no válido", "nombre vacío o demasiado largo", "butaca no válida"};
int n = sizeof lineas / sizeof lineas[0];
struct ficha fichas[6];
int validas = 0;
for (int i = 0; i < n; i++) {
// &fichas[validas]: la siguiente casilla libre; si la línea falla, se reutiliza
int e = leer_ficha(lineas[i], &fichas[validas]);
if (e == 0)
validas++;
else
printf("Línea %d (%s): %s\n", i + 1, lineas[i], errores[e]);
}
printf("Fichas válidas: %d de %d\n", validas, n);
for (int i = 0; i < validas; i++)
printf(" %-3s %08d %s\n", fichas[i].butaca, fichas[i].dni, fichas[i].nombre);
// ¿Quién está en la C12?
for (int i = 0; i < validas; i++)
if (strcmp(fichas[i].butaca, "C12") == 0)
printf("En la C12 está %s\n", fichas[i].nombre);
return 0;
}
La función clave, con zoom:
// Lee "DNI;Nombre;Butaca". Devuelve 0 si va bien o el número del error
int leer_ficha(const char *linea, struct ficha *f) {
// strchr devuelve un puntero al ';' (o NULL): así sabemos dónde acaba cada campo
const char *p1 = strchr(linea, ';');
if (p1 == NULL)
return 1;
const char *p2 = strchr(p1 + 1, ';');
if (p2 == NULL)
return 1;
// Restar punteros da cuántos caracteres hay entre ellos
size_t largo_dni = p1 - linea;
if (largo_dni != 8)
return 2;
for (const char *c = linea; c < p1; c++)
if (!isdigit((unsigned char)*c))
return 2;
size_t largo_nombre = p2 - (p1 + 1);
// >= y no >: hay que dejar sitio para el '\0'
if (largo_nombre == 0 || largo_nombre >= sizeof f->nombre)
return 3;
size_t largo_butaca = strlen(p2 + 1);
if (largo_butaca < 2 || largo_butaca >= sizeof f->butaca)
return 4;
// Solo escribimos en f cuando todo está comprobado
// atoi lee cifras y se para sola en el ';'
f->dni = atoi(linea);
// El nombre no acaba en '\0' dentro de la línea: copiamos largo_nombre bytes y lo ponemos nosotros
memcpy(f->nombre, p1 + 1, largo_nombre);
f->nombre[largo_nombre] = '\0';
// La butaca es el final de la línea: ya trae su '\0'
strcpy(f->butaca, p2 + 1);
return 0;
}
La idea es medir antes de copiar. Primero se localizan los dos ; con strchr y se calculan las longitudes restando punteros; solo si todo es válido se escribe en f. Así, si una línea falla, el struct queda sin tocar y la siguiente línea puede reutilizar la misma casilla (por eso main pasa &fichas[validas]).
Las decisiones:
- **
>=en las longitudes.**nombretiene 12 bytes: caben 11 letras y el nulo. Un nombre de 12 letras ya no cabe, por eso eslargo_nombre >= sizeof f->nombrey no>. Lo mismo con la butaca: «C12» (3) cabe en 4; «C123» no. - **
memcpy+ nulo para el nombre.** Dentro de la línea, el nombre va seguido de;, no de'\0', así questrcpycopiaría hasta el final de la línea.memcpycopia exactamentelargo_nombrebytes y después ponemos el nulo nosotros. - **
strcpypara la butaca.** La butaca es lo último de la línea y ya acaba en'\0', y su longitud está comprobada: aquístrcpyes seguro. - **
atoi(linea)** convierte las cifras del principio y se para sola en el primer carácter que no es cifra, el;. Ya hemos comprobado que son 8 cifras, así que el número es correcto. - **
isdigit((unsigned char)*c)**, con el mismo cast quetoloweren la práctica.
Línea a línea:
| Línea | p1 en | p2 en | Comprobación que falla | Resultado |
|---|---|---|---|---|
| 42345678;Pepito;A3 | 8 | 15 | ninguna (8 cifras, 6 letras, 2 de butaca) | válida |
| 12343278;Juan;C12 | 8 | 13 | ninguna (4 letras, 3 de butaca < 4) | válida |
| 1234567;Ana;B1 | 7 | 11 | largo_dni = 7 ≠ 8 | error 2 |
| 33333333;Maximiliano Ruiz;C6 | 8 | 25 | largo_nombre = 25 − 9 = 16 ≥ 12 | error 3 |
| 44444444 Lucia A4 | NULL | — | no hay ; | error 1 |
| 55555555;Ivan; | 8 | 13 | strlen(«») = 0 < 2 | error 4 |
2 válidas de 6, como dice la salida. El %08d imprime el DNI con 8 cifras rellenando con ceros (aquí no hace falta, pero un DNI como 01234567 perdería su cero sin él). La búsqueda final compara la butaca con strcmp(..., "C12") == 0 y encuentra a Juan.
Usar strtok para partir la línea. Funciona, pero modifica la cadena (cambia cada ; por '\0'), así que no sirve con una línea const ni con literales, y se salta los campos vacíos: con «55555555;Ivan;» no verías que falta la butaca.
Míralo paso a paso en Python Tutor
En Python Tutor elige C y pega este programa, que junta el fragmento 3 y su corrección:
#include <stdio.h>
struct butaca {
char fila;
int numero;
int ocupada;
};
void ocupar_copia(struct butaca b) {
b.ocupada = 1;
}
void ocupar(struct butaca *b) {
b->ocupada = 1;
}
int main(void) {
struct butaca b = {'B', 4, 0};
ocupar_copia(b);
printf("Tras ocupar_copia: %d\n", b.ocupada);
ocupar(&b);
printf("Tras ocupar: %d\n", b.ocupada);
return 0;
}
Su salida:
Tras ocupar_copia: 0 Tras ocupar: 1
Qué observar paso a paso:
- En
main, la butacabaparece como una caja confila,numeroyocupadaa 0. - Al entrar en
ocupar_copia, aparece un segundo marco con su propia cajab, con los mismos valores: es la copia. - Al ejecutar
b.ocupada = 1;cambia la caja del marco deocupar_copia; la demainsigue a 0. Al volver, ese marco desaparece y el cambio con él. - Al entrar en
ocupar, el parámetrobya no es una caja: es una flecha que apunta a la butaca demain. b->ocupada = 1;cambia directamente la caja demain. Por eso la segunda línea de la salida dice 1.
Chuletario: structs y cadenas en C
Lo esencial en 30 segundos
- Un
structagrupa campos de tipos distintos; el tipo esstruct nombre. - Punto con el struct (
s.campo), flecha con su dirección (p->campo). - Para modificar un struct en una función, pásale su dirección.
- Una cadena es un array de
charque acaba en'\0'; enchar x[N]caben N − 1 letras. - Texto: se copia con
strcpy/snprintfy se compara constrcmp, nunca con=ni==.
Structs
| Quiero… | Código | Resultado |
|---|---|---|
| Declarar el tipo | struct butaca { char fila; int numero; }; | Plantilla (no ocupa memoria) |
| Crear e inicializar | struct butaca b = {'C', 7}; | b.fila = ‘C’, b.numero = 7 |
| Inicializar por nombre | struct butaca b = {.numero = 7}; | fila = 0, numero = 7 |
| Todo a cero | struct butaca sala[3][6] = {0}; | Libres y nombres vacíos |
| Copiar entero | d = b; | Copia todos los campos, arrays incluidos |
| Alias del tipo | typedef struct butaca Butaca; | Butaca b; |
| Tamaño real | sizeof(struct butaca) | Con relleno incluido |
| Puntero y campo | struct butaca *p = &b; p->numero = 8; | Cambia b |
| En el heap | malloc(n * sizeof *v) | n structs seguidos |
Cadenas y string.h
| Quiero… | Código | Resultado |
|---|---|---|
| Longitud | strlen("Ana") | 3 |
| Bytes reservados | sizeof(char[10]) | 10 |
| Copiar | strcpy(dst, "Ana") | dst = «Ana» (sin comprobar tamaño) |
| Copiar sin desbordar | snprintf(dst, sizeof dst, "%s", src) | Recorta si no cabe y pone '\0' |
| Añadir al final | strcat(saludo, "Lucia") | «Hola, Lucia» |
| Comparar | strcmp(a, b) == 0 | Iguales |
| ¿Empieza por? | strncmp(s, "ana", 3) == 0 | Compara 3 caracteres |
| Buscar un carácter | strchr("42345678;Pepito", ';') | Puntero a la posición 8 |
| Copiar n bytes | memcpy(dst, src, n); dst[n] = '\0'; | Trozo de una cadena |
| Duplicar en el heap | malloc(strlen(s) + 1) + strcpy | Copia propia del texto |
Plantillas de los ejercicios
| Ejercicio | Patrón | Resultado con los datos del artículo |
|---|---|---|
| Nivel 1 | Recorrer un array de structs guardando el índice del mejor | Interstellar (169 min); título: La sociedad de la nieve (23) |
| Nivel 2 | while (*p) p++; y while ((*d++ = *s++) != '\0'); | mi_strlen(«Pepito») = 6; mi_strcmp(«ana», «Ana») = 32 |
| Nivel 3 | strchr para los ; + restar punteros + memcpy + '\0' | 2 fichas válidas de 6; en la C12 está Juan |
Síntoma y causa probable
| Síntoma | Causa probable |
|---|---|
assignment to expression with array type | Has hecho s.nombre = "...": usa strcpy o snprintf |
'p' is a pointer; did you mean to use '->'? | Punto con un puntero: usa p->campo |
invalid type argument of '->' | Flecha con un struct: usa s.campo |
comparison with string literal results in unspecified behavior | Comparas cadenas con ==: usa strcmp |
| Un dato de otro campo o de otra butaca cambia solo | Desbordamiento de un array (falta sitio para el '\0') |
| Basura al final al imprimir una cadena | Falta el '\0' (por ejemplo, tras strncpy) |
La función cambia el struct, pero en main no | Pasaste el struct por valor: pasa su dirección |
stack-buffer-overflow o heap-buffer-overflow (ASan) | Escribes más bytes de los reservados; revisa el + 1 del nulo |
Antes de entregar, pregúntate
- ¿Cada array de
chartiene sitio para el'\0'? - ¿Las funciones que modifican un struct reciben su dirección?
- ¿Comparo las cadenas con
strcmpy copio constrcpy/snprintfsabiendo que caben? - ¿Compila sin avisos con
-Wally pasa limpio con-fsanitize=address?
Con esto cierras structs y cadenas en C. El siguiente tema de IC2 es el preprocesador: #define, macros y compilación condicional. Mientras tanto, si quieres repasar de dónde viene todo esto, vuelve a punteros en C y a memoria dinámica en C.
¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
