diff --git a/book/13-entrada-salida-estandar.md b/book/13-entrada-salida-estandar.md new file mode 100644 index 0000000..4f8730a --- /dev/null +++ b/book/13-entrada-salida-estandar.md @@ -0,0 +1,1556 @@ +# Capítulo 13 — Entrada y salida estándar (sin archivos) + +> Capítulo intermedio, pero **escrito para que se entienda de verdad**. Se apoya en variables (`02`), +> operadores (`03`), `if` y bucles (`04`), funciones (`05`) y arreglos y cadenas (`06`). +> +> Es el **puente** hacia el capítulo de archivos (`14-archivos`). Todo lo que aprendas acá sobre +> `stdin`, búferes y `EOF` lo vas a reusar tal cual para leer archivos. La única diferencia será que, +> en vez de leer del teclado, vas a leer de un archivo. + +--- + +## Cómo leer este capítulo + +Bienvenido al capítulo que separa a los que "hacen que ande" de los que **entienden qué pasa**. +Acá no vas a memorizar tablas: vas a **mirar**, **probar** y **romper** cosas hasta que los conceptos +te queden pegados. + +Está dividido en **ocho partes**. Después de cada idea importante hay una **práctica** —uno o varios +ejercicios cortos— para que la uses ya mismo, antes de avanzar. No saltees las prácticas: son la mitad +del capítulo. + +| Parte | De qué habla | La idea en una frase | +|---|---|---| +| **1** | Los tres flujos | Tu programa tiene tres tuberías: una de entrada y dos de salida. | +| **2** | Leer byte a byte | `getchar` saca de a uno, y `EOF` avisa cuándo se acabó. | +| **3** | Leer líneas | `fgets` saca una línea entera sin desbordarse. | +| **4** | El búfer | Por qué un `Enter` cambia todo, y por qué `scanf` deja basura. | +| **5** | Leer con formato | `scanf` y su mini-lenguaje de formatos. | +| **6** | Caracteres | Por qué una `á` pesa dos bytes. | +| **7** | Robustez | Que tu programa no se rompa nunca. | +| **8** | Proyecto final | Construís tu propia consola de comandos. | + +Los **ejercicios** se numeran en orden: `13.1`, `13.2`, `13.3`, y así. Cada uno trae su **escenario de +prueba** (qué entra y qué tiene que salir) para que puedas **comprobar** tu solución. + +> **Regla de oro del libro:** el capítulo explica y muestra **cómo funciona** cada cosa, pero **los +> ejercicios no traen la solución**. El enunciado te da el problema; la respuesta la escribís vos. + +--- + +# PARTE 1 — Tu programa tiene tres tuberías + +Antes de tocar una sola función, entendamos el terreno. Es más simple de lo que parece. + +## 1.1 El día que descubrí que mi programa no sabe de dónde lee + +Imaginate que tu programa es una **fábrica**. La fábrica no sabe si la materia prima llega en camión, en +tren o por un caño: solo sabe que **algo entra** por una entrada, se procesa, y **algo sale** por las +salidas. En C, esa entrada y esas salidas son **tuberías** (en inglés, *streams*, "flujos"). + +Todo programa en C nace con **tres tuberías ya conectadas**: + +```text + (de dónde vengan los datos) + │ + ▼ + ┌───────────┐ + │ stdin │ ← ENTRADA ESTÁNDAR (por acá entran los datos) + └─────┬─────┘ + │ + ▼ + ╔══════════════╗ + ║ TU PROGRAMA ║ + ╚══╤═══════╤═══╝ + │ │ + ┌──────▼──┐ ┌─▼────────┐ + │ stdout │ │ stderr │ ← DOS salidas + └────┬────┘ └────┬─────┘ + │ │ + ▼ ▼ + (salida normal) (errores) +``` + +- **`stdin`** es la tubería de **entrada**. Por ahí entran los datos. +- **`stdout`** es la tubería de **salida normal**. Por ahí sale el resultado del programa. +- **`stderr`** es la tubería de **errores**. Por ahí salen los mensajes de problema. + +Esas tres tienen un nombre con `st` adelante porque viene de *standard*: **estándar**. No las abrís vos. +Existen desde que arranca `main`, como la luz de tu casa: no la instalás, ya está. + +## 1.2 `FILE *`: la ficha de la tubería + +Cada tubería, en el código, se maneja con algo que se llama **`FILE *`**. Podés pensarlo como la +**factura** o la **ficha** de esa tubería: un papelito que dice "esta es la tubería de entrada, así +está configurada, así se va leyendo". No es la tubería en sí: es su **identificación**. + +```c +#include + +int main(void) { + /* stdin, stdout y stderr ya vienen declarados en . + Son de tipo FILE *. No hay que abrirlos ni cerrarlos. */ + (void)stdout; + return 0; +} +``` + +> **¿Por qué `FILE *` y no simplemente "el teclado"?** Porque tu programa **no habla con el teclado**: +> habla con la **tubería**. Eso es lo que lo hace genial. La misma `stdin` puede estar conectada al +> teclado, a un archivo o a otro programa, y tu código **no cambia ni una línea**. Eso lo vas a ver +> ya mismo, en la parte más divertida. + +## 1.3 El truco más lindo de la consola: la misma tubería, conectada a cualquier cosa + +Acá está la magia. Vos escribís **un** programa, y el **shell** (la terminal, la ventanita donde +escribís comandos) decide **a qué** conecta sus tuberías. Mirá cómo cambia todo sin tocar el programa. + +Vamos a usar `cat`, un programa clásico que simplemente copia lo que entra a lo que sale. Es como un +**espejo**: lo que le des, te lo devuelve. + +**Forma 1: entrada desde el teclado.** El shell conecta la `stdin` de `cat` a tu teclado. + +```bash +$ cat +hola mundo ← lo que escribís vos +hola mundo ← lo que devuelve cat (el eco) +esto es una prueba +esto es una prueba +(Ctrl+Z y Enter en Windows / Ctrl+D en Linux y macOS para terminar) +``` + +**Forma 2: entrada desde una tubería.** Con el símbolo `|` (la "tubería"), conectás la **salida** de +un programa a la **entrada** de otro. Fijate que `echo` **escribe** y `cat` **lee**, y ninguno de los +dos sabe del otro. + +```bash +$ echo "hola mundo" | cat +hola mundo +``` + +**Forma 3: entrada desde un archivo.** Con el símbolo `<`, conectás la `stdin` de `cat` a un archivo. + +```bash +$ cat < datos.txt +primera línea del archivo +segunda línea del archivo +``` + +> **Momento "ajá".** En los tres casos, `cat` hizo lo mismo internamente: leer de `stdin` y escribir en +> `stdout`. **Nunca supo** si leía del teclado, de otro programa o de un archivo. El shell cambió **a +> qué estaba conectada la tubería**, nada más. + +## 1.4 Y las salidas también se pueden desviar + +Igual que la entrada, podés desviar las **salidas**. El símbolo `>` manda la salida (normal) a un +archivo en vez de la pantalla: + +```bash +$ cat < datos.txt > copia.txt # leo de datos.txt y escribo en copia.txt +$ cat copia.txt +primera línea del archivo +segunda línea del archivo +``` + +Ahora, la parte que casi nadie sabe al principio: **`>` desvía `stdout`, pero NO `stderr`**. Los +errores **siguen saliendo por su propio canal**. Probémoslo pidiendo un archivo que no existe: + +```bash +$ ls archivo_que_no_existe +ls: cannot access 'archivo_que_no_existe': No such file or directory + +$ ls archivo_que_no_existe > salida.txt +ls: cannot access 'archivo_que_no_existe': No such file or directory ← ¡sigue en pantalla! +$ cat salida.txt + ← salida.txt está VACÍO: eso fue un error +``` + +El mensaje de error **no** fue a `salida.txt`, porque pertenece a `stderr`. Eso es **exactamente** para +lo que existe `stderr`: así podés guardar la salida útil en un archivo **sin perder** los errores de +vista. + +| Símbolo | Qué desvía | Ejemplo | +|---|---|---| +| `<` | La **entrada** (`stdin`) desde un archivo | `prog < datos.txt` | +| `>` | La **salida normal** (`stdout`) a un archivo | `prog > out.txt` | +| `2>` | La **salida de error** (`stderr`) a un archivo | `prog 2> err.txt` | +| `\|` | La **salida** de uno a la **entrada** de otro | `echo hola \| prog` | +| `2>&1` | Manda `stderr` **al mismo lugar** que `stdout` | `prog > todo.txt 2>&1` | + +> **Truco de laboratorio.** Para ver la diferencia en vivo, corré cualquier comando tres veces: +> `cmd`, `cmd > out.txt 2> err.txt`, y `cmd > todo.txt 2>&1`. Después mirá qué cayó en cada archivo. +> Es la mejor forma de **sentir** los canales separados en la mano. + +## 1.5 Los tres flujos vistos desde el código + +Desde C, los tres son `FILE *`, y los tres se pueden usar tanto para leer como para escribir (aunque +en la práctica `stdin` se lee y las otras dos se escriben). `printf` escribe en `stdout`; +`fprintf(stderr, ...)` escribe en `stderr`: + +```c +#include + +int main(void) { + /* fprintf: escribe con formato en un flujo cualquiera. + Primer argumento: el flujo (acá stdout, que es igual que usar printf). + El resto: el formato y los datos, exactamente como printf. */ + fprintf(stdout, "Esto es la salida normal\n"); + + /* Mismo fprintf, pero apuntando a stderr: esto es un mensaje de error. */ + fprintf(stderr, "Esto es un mensaje de error\n"); + + return 0; +} +``` + +Si lo corrés y desviás solo la salida normal, el mensaje de error **queda en pantalla**: + +```bash +$ ./hola > out.txt +Esto es un mensaje de error ← quedó en pantalla (stderr) +$ cat out.txt +Esto es la salida normal ← esto sí fue al archivo +``` + +## 1.6 Un detalle que sorprende: el orden no siempre es el que escribiste + +`stderr` normalmente **no espera**: envía cada mensaje al instante. `stdout`, en cambio, suele +**acumular** (guardar en un búfer) y enviar todo junto, sobre todo cuando la salida va a un archivo o a +un caño. Resultado: si mezclás escrituras a `stdout` y a `stderr`, el orden en pantalla puede no ser el +orden en que las programaste. Es un motivo más para **no mezclar** la salida normal con los errores. + +## 1.7 "Estándar" y "multiplataforma": dos palabras que valen oro + +Cuando decimos que `getchar` o `fgets` son **estándar**, queremos decir que están **descritas en el +contrato del lenguaje C** (el estándar ISO C). Ese contrato define el **nombre**, los **parámetros** y +**qué hacen**, y **cualquier** implementación que cumpla el estándar (GCC, Clang, MSVC…) las ofrece con +el mismo significado. Por eso el **mismo código** corre en Windows, Linux y macOS: eso es ser +**multiplataforma**. + +Pero atención: el estándar garantiza el **contrato**, no los **detalles** de cada máquina. Mirá la +diferencia: + +| Garantizado por el estándar (vale en todos lados) | Depende del entorno (cambia según la máquina) | +|---|---| +| Los nombres y parámetros de las funciones de `` | Qué tecla produce `EOF`: Ctrl+Z (Windows) o Ctrl+D (Linux/macOS) | +| Cuántas asignaciones hizo `scanf`, qué devuelve `fgets`… | Si el teclado muestra o no lo que escribís (el "eco") | +| Que `EOF` es **negativo**, distinto de todo byte (0–255) | La **codificación** de los caracteres (UTF-8, Latin-1, CP1252…) | +| Que las cadenas terminan en `'\0'` | El tamaño de los búferes y el momento en que se vuelcan | + +> **Para guardar.** Tu programa lee y escribe **tuberías**. El **shell** decide a qué está conectada +> cada una. Tu código escribe **una vez** y funciona en teclado, caño y archivo… siempre que respetes +> el **contrato** de cada función. Lo que cambia de máquina a máquina son los detalles, no el contrato. + +## Práctica de la Parte 1 + +### Ejercicio 13.1 — Separar la salida útil de los avisos + +**Conceptos** · `stdout`, `stderr`, `fprintf`, redirección de salidas + +**Contexto** · Las herramientas de línea de comandos separan la salida **útil** (`stdout`) de los +**diagnósticos** (`stderr`), para que puedas guardar una sin perder los otros. Simplificación: un +programa que "cierra un trabajo" y tiene que avisar de un problema sin ensuciar el resultado. + +**Consigna** · Escribí un programa que simule el cierre de un reporte: manda las líneas informativas +(dos o tres, por ejemplo "Iniciando reporte...", "Procesando 128 registros...", "Reporte generado +correctamente.") a **`stdout`**, y el aviso de problema (por ejemplo "Aviso: 3 registros tenían fecha +inválida.") a **`stderr`**. Nunca al revés. + +**Entrada** · Ninguna: el programa no lee nada. +**Salida** · Las líneas informativas por `stdout`; el aviso por `stderr`. + +**Escenario de prueba** + +```text +$ ./reporte > salida.txt 2> errores.txt +$ cat salida.txt +Iniciando reporte... +Procesando 128 registros... +Reporte generado correctamente. + +$ cat errores.txt +Aviso: 3 registros tenían fecha inválida. +``` + +**Criterios de aceptación** +- [ ] Las líneas informativas salen **solo** por `stdout`. +- [ ] El aviso de problema sale **solo** por `stderr`. +- [ ] Al correr con `>` y `2>`, cada mensaje cae en su archivo y ninguno aparece en el otro. +- [ ] Usa `fprintf` indicando el flujo (`stdout`/`stderr`) en cada escritura. + +### Ejercicio 13.2 — Detective de flujos + +**Conceptos** · `stdout`, `stderr`, redirección (`>`, `2>`, `2>&1`), tuberías + +**Contexto** · Antes de creer que entendiste los flujos, hay que **verlos**. Vas a conectar el mismo +programa de varias maneras y comparar qué termina dónde. + +**Consigna** · Tomá el programa de 13.1 (o escribí uno mínimo que mande una línea a `stdout` y otra a +`stderr`) y corrélo de estas formas, anotando para cada una **qué queda en pantalla** y **qué en cada +archivo**: + +1. sin redirección; +2. `./programa > out.txt`; +3. `./programa 2> err.txt`; +4. `./programa > out.txt 2> err.txt`; +5. `./programa > todo.txt 2>&1`. + +Al final, respondé: ¿cambió el **programa**? ¿cambió el **resultado**? ¿quién decidió a dónde va cada +línea? + +**Entrada** · Ninguna: el programa no lee nada. +**Salida** · Dos líneas (una por `stdout`, una por `stderr`), que caen en distintos destinos según cómo +las redirijas. + +**Escenario de prueba** + +```text +$ ./dos > out.txt 2> err.txt +$ cat out.txt +salida normal + +$ cat err.txt +mensaje de error +``` + +```text +$ ./dos > todo.txt 2>&1 +$ cat todo.txt +salida normal +mensaje de error +``` + +**Criterios de aceptación** +- [ ] El mismo binario se comporta distinto **solo** por cómo el shell conecta sus salidas (sin + recompilar). +- [ ] Con `> out.txt 2> err.txt`, cada mensaje cae en su archivo. +- [ ] Con `2>&1`, los dos mensajes terminan en el mismo archivo. +- [ ] Sabés explicar qué cambió (la conexión) y qué **no** cambió (el programa). + +--- + +# PARTE 2 — Leer byte a byte: `getchar` y el famoso `EOF` + +Ya sabemos de dónde vienen los datos. Ahora empecemos a **sacarlos**, de a poquito. + +## 2.1 `getchar`: el caramelito de a uno + +`getchar()` hace una sola cosa: **saca un byte** de `stdin`. Nada más. Cada vez que la llamás, te da el +próximo byte de la entrada. Como sacar caramelos de un frasco, **de a uno por vez**. + +```c +#include + +int main(void) { + /* getchar: lee UN byte de stdin y lo devuelve. + Lo guardamos en un int (¡no en un char!, ver 2.4). */ + int c = getchar(); + (void)c; + return 0; +} +``` + +Si tecleás `hola` y apretás `Enter`, no hay **un** byte esperando: hay **cinco** (`h`, `o`, `l`, `a` y +el `'\n'` del Enter). `getchar` te dará uno por llamada: + +```text +1ª llamada a getchar() → 'h' +2ª llamada a getchar() → 'o' +3ª llamada a getchar() → 'l' +4ª llamada a getchar() → 'a' +5ª llamada a getchar() → '\n' (el Enter que apretaste) +6ª llamada a getchar() → EOF (ya no hay más) +``` + +Lo mismo pasa con los **números**, y ahí está la trampa más grande de todas: para `getchar`, un número +**no existe**. Solo existen sus **dígitos**, uno por uno. Si tecleás `304` y apretás `Enter`, no hay un +"trescientos cuatro" esperando: hay **cuatro** bytes (`'3'`, `'0'`, `'4'` y el `'\n'`). + +```text +Tecleás: 304 + +1ª llamada a getchar() → '3' (el byte del dígito tres) +2ª llamada a getchar() → '0' (el byte del dígito cero) +3ª llamada a getchar() → '4' (el byte del dígito cuatro) +4ª llamada a getchar() → '\n' (el Enter que apretaste) +5ª llamada a getchar() → EOF (ya no hay más) +``` + +> **Ojo con esto.** `getchar` te devuelve el **byte del carácter**, no el valor del número. Cuando lee +> `'3'`, te da un byte (cuyo valor es `51`), no el número `3`. Armar el número `304` a partir de esos +> dígitos es un trabajo **tuyo**, no de `getchar`. Por eso, para leer números "de verdad", más adelante +> vas a usar `scanf` (Parte 5). + +## 2.2 El patrón que vas a repetir mil veces + +Casi todos los programas que leen byte a byte usan el mismo esqueleto: leer y **procesar**, en un +bucle, hasta que se acabe la entrada. + +```c +#include + +int main(void) { + int c; + + /* Bucle clásico: leo un byte; si no es EOF, lo proceso y repito. + getchar() devuelve EOF cuando ya no queda nada por leer. */ + while ((c = getchar()) != EOF) { + putchar(c); /* putchar: escribe UN byte en stdout (el espejo de getchar) */ + } + + return 0; +} +``` + +Este programita es un espejo: copia la entrada a la salida. Es, básicamente, nuestro `cat`. + +## 2.3 `EOF`: el timbre que dice "se acabó la fila" + +`EOF` significa *End Of File* (fin de archivo), pero no lo interpretes como "fin de un archivo de +disco". Es más general: significa **"no hay más datos para leer"**, venga de donde venga. + +- Si leés de un **archivo** y llegaste al final → `EOF`. +- Si leés de una **tubería** y el otro programa terminó → `EOF`. +- Si leés del **teclado** y apretás **Ctrl+Z + Enter** (Windows) o **Ctrl+D** (Linux/macOS) → `EOF`. + +Pensalo como el **timbre del recreo**: no es un alumno más en la fila, es la **señal** de que la fila +se terminó. `getchar` devuelve ese timbre como un valor **especial**: `EOF`. + +```text +Byte 'h' → valor 104 (es un dato) +Byte '\n' → valor 10 (es un dato) +EOF → valor -1 (¡no es un dato! es "no hay más") +``` + +## 2.4 La trampa N.º 1: guardar `getchar` en un `char` + +Fijate en algo raro: `getchar` **no** devuelve un `char`, devuelve un **`int`**. ¿Por qué? + +Porque tiene que poder representar **256 bytes posibles** (de `0` a `255`) **y además** un valor extra: +`EOF` (`-1`). Un `char` no tiene lugar para ese "valor extra" sin pisar un byte real. Si guardás el +resultado en un `char`, pasan cosas feas: + +- El byte **`0xFF`** (que en algunos teclados/enviros es la letra `ÿ`) puede convertirse en **`-1`**, + ¡y `-1` es `EOF`! Entonces un **dato real** se confunde con "se acabó". +- O al revés: comparás contra `EOF` y la comparación **nunca** da verdadera, aunque la entrada terminó. + +**La regla, entonces:** el resultado de `getchar` (y de `getc`/`fgetc`) se guarda **siempre en un +`int`**, y contra `EOF` se compara **en ese `int`**. + +```c +int c; /* int, NO char */ +while ((c = getchar()) != EOF) { + /* acá c es un byte válido entre 0 y 255 */ +} +``` + +## 2.5 `getchar`, `getc` y `fgetc`: tres nombres, casi lo mismo + +Existen tres primos que leen **un byte**, y hacen lo mismo: + +```c +int getchar(void); /* lee SIEMPRE de stdin */ +int getc(FILE *stream); /* lee de CUALQUIER flujo (ej.: getc(stdin)) */ +int fgetc(FILE *stream); /* igual que getc, pero siempre es función */ +``` + +- **`getchar()`** es la versión fija a `stdin`: comodísima para la consola. +- **`getc(stdin)`** y **`fgetc(stdin)`** hacen lo mismo que `getchar()`, pero sirven para **cualquier** + flujo (muy útil cuando llegues a los archivos). +- La única diferencia fina: `getc` puede estar implementada como **macro** y `fgetc` **siempre** es + función. En la práctica, usá `getchar` para la consola y `fgetc(fp)` para un archivo. + +## Práctica de la Parte 2 + +### Ejercicio 13.3 — Contador de caracteres seguro + +**Conceptos** · `getchar`, `int` vs `char`, `EOF`, bytes altos (`0xFF`) + +**Contexto** · Contar bytes de la entrada es lo que hace la herramienta `wc -c`. Simplificación: contar +**bytes** leídos de `stdin` hasta `EOF`, sin confundir un byte `0xFF` con el fin de archivo. + +**Consigna** · Leer de `stdin` **byte a byte** con `getchar()` hasta `EOF` y contar cuántos bytes se +leyeron. La variable de lectura **y** el contador deben ser `int`. + +**Entrada** · Cualquier secuencia de bytes (texto o no) hasta `EOF`. +**Salida** · Una línea con la cantidad de bytes leídos. + +**Escenario de prueba** + +```text +printf 'hola' | ./contador +Caracteres leídos: 4 +``` + +```text +printf '\xff\xffh' | ./contador (dos bytes 0xFF y una 'h') +Caracteres leídos: 3 +``` + +> **Para comprobar que lo hiciste bien:** el conteo debe dar **3** aunque haya bytes `0xFF`. Si +> guardaste en un `char`, el programa corta en el primer `0xFF` y te da **0**. + +**Criterios de aceptación** +- [ ] Usa `int` para la lectura y para el contador. +- [ ] Cuenta **todos** los bytes, incluidos `0x00` y `0xFF`. +- [ ] Termina limpio en `EOF`, sin bucle infinito. +- [ ] El resultado coincide con el tamaño real de la entrada. + +### Ejercicio 13.4 — ¿Cuántos saltos de línea hay? + +**Conceptos** · `getchar`, comparación de bytes, `'\n'`, `EOF` + +**Contexto** · Los editores cuentan líneas contando fines de línea. Simplificación: contar cuántos +bytes `'\n'` hay en la entrada (que es, ni más ni menos, cuántas líneas terminadas tiene). + +**Consigna** · Leer de `stdin` byte a byte y contar **solo** los bytes que sean `'\n'`. Al terminar, +mostrar el total. + +**Entrada** · Texto hasta `EOF`. +**Salida** · La cantidad de saltos de línea. + +**Escenario de prueba** + +```text +printf 'uno\ndos\ntres\n' | ./lineas +Saltos de línea: 3 +``` + +```text +printf 'sin salto al final' | ./lineas +Saltos de línea: 0 +``` + +**Criterios de aceptación** +- [ ] Lee con `getchar` en un `int` y corta ante `EOF`. +- [ ] Cuenta **solo** `'\n'` (no otros bytes). +- [ ] Los dos escenarios dan el resultado esperado. +- [ ] No hay bucle infinito si la entrada no termina en `'\n'`. + +--- + +# PARTE 3 — Leer líneas enteras: `fgets` + +Leer de a un byte está bien, pero muchas veces querés "una línea entera de una". Para eso existe +`fgets`. + +## 3.1 El cucharón que llena el balde + +`fgets` es como un **cucharón**: saca de la tubería hasta **llenar un balde** (un arreglo de `char`) o +hasta encontrar el fin de la línea, lo que pase primero. + +```c +char *fgets(char *s, int n, FILE *stream); +``` + +- **`s`**: el **balde** (tu arreglo de `char`) donde se guarda lo que leyó. +- **`n`**: el **tamaño** del balde. +- **`stream`**: de qué tubería leer (`stdin`, en la consola). +- **Devuelve**: `s` si leyó algo, o **`NULL`** si llegó al final o hubo error. + +## 3.2 Tres reglas de oro de `fgets` + +1. **Lee como máximo `n - 1` caracteres.** Reserva un lugar para el terminador `'\0'`. Por eso un + balde de 20 guarda **19** letras + el `'\0'`. +2. **Se detiene en `'\n'`… y lo guarda.** A diferencia de lo que haría un humano, `fgets` **deja el + `'\n'` adentro** del balde. Es una fuente clásica de sorpresas (ver Parte 4). +3. **Nunca se desborda si le pasás el tamaño real.** Como sabe cuánto mide el balde, para cuando se + llena. Es la función **segura** para leer por consola. + +## 3.3 Leer líneas en un bucle + +```c +#include + +int main(void) { + char line[100]; + + /* fgets devuelve NULL cuando ya no hay más líneas (EOF) o hay error. + Le paso sizeof line, así nunca escribe fuera del arreglo. */ + while (fgets(line, sizeof line, stdin) != NULL) { + printf("Leí: %s", line); /* line ya incluye el '\n', por eso no lo agrego */ + } + + return 0; +} +``` + +## 3.4 Líneas más largas que el balde + +Si la línea es más larga que el balde, `fgets` **la corta**: llena el balde hasta `n-1` y sigue en la +próxima llamada. No se rompe nada, pero tu programa ve la línea **partida**. Para darte cuenta, el +truco es: si el balde se llenó y **NO** terminó en `'\n'`, la línea era más larga de lo que entra. + +```c +char line[20]; +fgets(line, sizeof line, stdin); +int len = strlen(line); /* strlen necesita */ +if (len > 0 && line[len - 1] != '\n') { + /* el balde se llenó sin encontrar el '\n': la línea venía cortada */ +} +``` + +## 3.5 Cuando la línea no entra: descartar el resto + +Detectar que una línea se cortó es solo la mitad. La otra mitad es **descartar lo que sobró**: si no, +esa parte se va a leer como si fuera una línea nueva (y vas a procesar datos fantasma). + +Después de detectar el corte, "comete" lo que queda de la línea hasta el `'\n'`: + +```c +char line[20]; +fgets(line, sizeof line, stdin); + +int len = strlen(line); +if (len > 0 && line[len - 1] != '\n') { + /* La línea era más larga que el balde: tiro lo que sobra + hasta consumir el '\n' (o llegar a EOF). */ + int c; + while ((c = getchar()) != '\n' && c != EOF) { + /* vacío a propósito: solo descarto */ + } +} +``` + +Ese bucle es un **patrón que vas a reusar**: en la Parte 4 vuelve a aparecer, con la misma forma, para +limpiar los residuos que deja `scanf`. Guardátelo. + +## Práctica de la Parte 3 + +### Ejercicio 13.5 — Protección contra desbordamiento + +**Conceptos** · `fgets` con búfer fijo, truncamiento, detección de líneas largas, descarte del resto + +**Contexto** · Un lector de comandos tiene que aceptar entradas más largas que su búfer **sin** +desbordar la memoria. Simplificación: búfer **estático de 20 bytes** y aviso de recorte. + +**Consigna** · Leer un comando con `fgets` en un búfer estático de **20 bytes**. Si la línea era más +larga que el búfer, avisar que se recortó y **descartar el resto** con el patrón de 3.5, para que no se +lea como una línea nueva. + +**Entrada** · Una línea (posiblemente más larga que el búfer). +**Salida** · El comando leído y, si corresponde, el aviso de recorte. + +**Escenario de prueba** + +```text +Entrada: una orden extremadamente larga +Salida: +Comando: "una orden extremada" +Aviso: la línea era más larga que el búfer (20 bytes) y fue recortada. +``` + +> El búfer de 20 guarda **19** caracteres + `'\0'`: por eso se ve `una orden extremada` (19 letras). +> El resto (`mente larga`) se descarta. + +**Criterios de aceptación** +- [ ] `fgets` recibe el tamaño real del búfer (`sizeof`). +- [ ] Entradas cortas se leen completas. +- [ ] Entradas largas se recortan a 19 caracteres **y** se avisa. +- [ ] El resto de la línea se descarta; no contamina la lectura siguiente. +- [ ] No hay desbordamiento ni comportamiento indefinido. + +### Ejercicio 13.6 — Contador de palabras + +**Conceptos** · `fgets`, recorrido de una cadena, espacios, bucles + +**Contexto** · Contar palabras es el trabajo de `wc -w`. Simplificación: contar cuántas "palabras" +(sucesiones de caracteres que no son espacio) hay en la entrada, leyendo línea por línea. + +**Consigna** · Leer la entrada con `fgets` y contar las palabras. Una palabra empieza cuando pasás de +"espacio" a "no espacio". Contá también la última palabra si la línea no termina en espacio. + +**Entrada** · Texto hasta `EOF`. +**Salida** · La cantidad de palabras. + +**Escenario de prueba** + +```text +printf 'hola mundo\nesto es una prueba\n' | ./palabras +Palabras: 6 +``` + +```text +printf ' muchos espacios \n' | ./palabras +Palabras: 2 +``` + +**Criterios de aceptación** +- [ ] Lee con `fgets` y comprueba que no devolvió `NULL`. +- [ ] Cuenta las palabras correctamente, sin importar cuántos espacios haya de más. +- [ ] Los dos escenarios dan el resultado esperado. +- [ ] No asume que toda línea termina en espacio. + +--- + +# PARTE 4 — El búfer: el villano de la película + +Esta es **la parte más importante** del capítulo. Si entendés el búfer, se te acaban los misterios. + +## 4.1 ¿Por qué `getchar` parece quedarse dormido? + +Escribí este programa y corrélo. Va a leer un byte y mostrarlo: + +```c +#include + +int main(void) { + printf("Escribí algo: "); + int c = getchar(); + printf("Leí el byte: '%c'\n", c); + return 0; +} +``` + +Si lo corrés y escribís `hola`, **no pasa nada** hasta que apretás `Enter`. Recién ahí el programa +continúa. ¿Por qué? Porque hay **dos búferes** en el medio, y entenderlos es entender todo. + +## 4.2 Los dos búferes (y por qué existen) + +Un **búfer** es una **zona de memoria temporal** que junta datos antes de pasarlos, para no estar +yendo y viniendo al dispositivo en cada byte (que sería lentísimo). Hay **dos** búferes en serie: + +```text +tus teclas → [ búfer de la TERMINAL ] → [ búfer de la LIBRERÍA C ] → tu getchar() + (lo maneja el sistema) (lo maneja tu programa) + se vacía con ENTER entrega byte a byte +``` + +- **Búfer de la terminal:** lo maneja el **sistema operativo**. Acumula lo que tecleás y **solo lo + entrega cuando apretás `Enter`**. Por eso `getchar` "espera". +- **Búfer de la librería C:** lo maneja **tu programa**. Recibe lo que entregó el sistema y lo entrega + a tus `getchar`, **de a un byte**. + +## 4.3 Modo "canónico": el teclado entrega por líneas + +Ese comportamiento —"entregá todo junto recién con `Enter`"— se llama **modo canónico** y es el +**modo por defecto** de las terminales. ¿Se puede cambiar para que cada tecla se entregue al instante? +Sí, se llama **modo no canónico** (o *raw*), y lo usan los juegos de texto y los editores de pantalla +completa. + +> **Pero ojo:** el modo **no lo elige `getchar`**. Lo elige la **terminal**, y se configura con APIs +> del sistema (*termios* en Linux, `SetConsoleMode` en Windows) que **no son estándar de C**. Por eso +> "leer una tecla sin apretar `Enter`" **no es portable**. Con las funciones de este capítulo, siempre +> vas a necesitar el `Enter`. + +## 4.4 El bug más famoso del principiante: `scanf` deja el `\n` pendiente + +Ahora sí, el villano. Mirá este clásico: + +```c +int age; +char name[50]; + +scanf("%d", &age); /* el usuario escribe: 30 */ +fgets(name, sizeof name, stdin); /* ¡lee una línea vacía! */ +``` + +¿Qué pasó? `scanf("%d", ...)` lee los dígitos `3` y `0`, y **para** en el primer carácter que no es +dígito: el `'\n'` del Enter. Ese `\n` **queda en el búfer**. Después, `fgets` ve que "hay una línea +pendiente" (que solo contiene ese `\n`) y la lee: `name` queda **vacío**. El nombre nunca se leyó. + +> **No es un bug de `scanf`:** es su **contrato**. `%d` lee **el número**, no "la línea". Lo que sobra +> queda ahí para la próxima lectura. Y la próxima lectura sos vos. + +## 4.5 La solución: "comerse" el resto de la línea + +Antes de leer una línea con `fgets`, hay que **descartar** lo que quedó pendiente. El patrón es: + +```c +/* Descarta lo que quede en la línea actual, hasta consumir el '\n'. */ +int c; +while ((c = getchar()) != '\n' && c != EOF) { + /* vacío a propósito: solo tiramos los caracteres */ +} +``` + +El bucle lee y **descarta** hasta encontrar el `'\n'` (lo consume) o hasta `EOF`. Después de esto, el +búfer queda limpio y "alineado" al comienzo de la línea siguiente. + +## 4.6 Por qué `fflush(stdin)` NO se usa (aunque te lo recomienden) + +Mucha gente "limpia" el búfer con `fflush(stdin)`. **No lo hagas.** `fflush` está definido por el +estándar **solo para flujos de salida** (para vaciar lo que escribiste). Aplicado a la **entrada**, el +comportamiento es **indefinido**: + +```c +fflush(stdin); /* ¡NO PORTABLE! El estándar no define qué hace */ +``` + +Algunos compiladores (MSVC, en Windows) le dan un sentido útil, y por eso el código con `fflush(stdin)` +"funciona" en esa máquina. Pero **no es estándar** y puede fallar en otra. La forma **portable** de +limpiar es el bucle de 4.5. + +## Práctica de la Parte 4 + +### Ejercicio 13.7 — Corrección de búfer tras `scanf` + +**Conceptos** · `scanf`, `fgets`, `\n` pendiente, limpieza del búfer + +**Contexto** · El error más común al mezclar `scanf` y `fgets`: el `\n` que `scanf` deja pendiente +"come" la línea siguiente. Simplificación: leer un número y **después** un nombre, sin perderlo. + +**Consigna** · Pedir un número entero con `scanf` y luego una cadena corta con `fgets`. Implantá la +**limpieza del búfer** intermedia (la de 4.5) para que el `\n` pendiente no arruine el nombre. + +**Entrada** · Dos líneas: un número y un texto. +**Salida** · El número y el nombre, cada uno en su línea. + +**Escenario de prueba** + +```text +Entrada: +42 +Ana +Salida: +Número: 42 +Nombre: Ana +``` + +> **Para comprobar que lo hiciste bien:** sin la limpieza, la salida sería `Nombre: ` (vacío), porque +> `fgets` leería el `'\n'` pendiente en vez de `Ana`. + +**Criterios de aceptación** +- [ ] Comprueba el retorno de `scanf`. +- [ ] Limpia el búfer pendiente de forma **portable** (bucle de lectura), **no** con `fflush`. +- [ ] `fgets` obtiene el nombre, **no** una línea vacía. +- [ ] Muestra número y nombre correctos. + +### Ejercicio 13.8 — Cuestionario de datos mixtos + +**Conceptos** · `scanf`, `getchar`, `fgets`, saltear blancos, orden de lectura, residuos + +**Contexto** · Un alta de usuario mezcla un ID numérico, una marca sí/no y un nombre completo (con +espacios). Simplificación: capturar los tres desde la misma entrada, sin residuos entre lecturas. + +**Consigna** · Leer, en este orden y desde la misma entrada: + +1. el **ID** numérico con `scanf("%d", ...)`; +2. **saltar los blancos** que quedaron después del número (espacios y tabs), leyendo con `getchar()`; +3. la **marca** `s`/`n` (un solo carácter) con `getchar()`; +4. **descartar el resto de la línea** con el patrón de 4.5, para no arrastrar residuos; +5. el **nombre completo** con `fgets()` (puede tener espacios). + +Garantizá que **no** queden caracteres residuales entre una lectura y la siguiente. + +**Entrada** · Primera línea: `ID marca` (separados por un espacio); segunda línea: el nombre. +**Salida** · Los tres datos, etiquetados. + +**Escenario de prueba** + +```text +Entrada: +1001 s +Juan Gómez +Salida: +ID: 1001 +Activo: s +Nombre: Juan Gómez +``` + +**Criterios de aceptación** +- [ ] Lee los tres datos en el orden pedido, desde la misma entrada. +- [ ] El nombre conserva los espacios internos (`Juan Gómez`). +- [ ] No quedan residuos: la lectura del nombre **no** está "corrida". +- [ ] Comprueba los valores de retorno. + +### Ejercicio 13.9 — Menú de opciones de longitud fija + +**Conceptos** · `getchar`, consumir el resto de la línea, bucles, validación + +**Contexto** · Los menús de consola leen **una** opción. Si el usuario escribe una cadena larga, hay +que quedarse con el primer carácter y **no** arrastrar el resto a la próxima vuelta. + +**Consigna** · Mostrar un menú (`[A] alta [B] baja [C] consulta [Q] salir`), leer la opción con +`getchar()` y, si el usuario escribió **más de un carácter**, **consumir el resto de la línea** antes +de la iteración siguiente. Repetir hasta `Q` o hasta `EOF`. + +**Entrada** · Líneas de opciones. +**Salida** · El eco de cada opción elegida y, al final, el cierre. + +**Escenario de prueba** + +```text +Entrada: +xyz +C +(Ctrl+Z / Ctrl+D para terminar) + +Salida: +[A] alta [B] baja [C] consulta [Q] salir +> Elegiste: x +[A] alta [B] baja [C] consulta [Q] salir +> Elegiste: C +[A] alta [B] baja [C] consulta [Q] salir +> Fin del menú +``` + +> Fijate que la entrada `xyz` elige `x` y **descarta** `yz`: por eso la vuelta siguiente arranca +> limpia. + +**Criterios de aceptación** +- [ ] Lee un **único** carácter por iteración. +- [ ] Si se escribió más de un carácter, consume el resto de la línea. +- [ ] La opción siguiente **no** está contaminada por residuos. +- [ ] Termina con `Q` y también ante `EOF`, sin bucle infinito. + +--- + +# PARTE 5 — Leer con formato: `scanf` y sus mañas + +`fgets` te da la línea. `scanf` te da **el dato que le pedís**, en el formato que le indiques. Es +potente y cómodo… y traicionero si no respetás sus reglas. + +## 5.1 La idea: un mini-lenguaje de formatos + +`scanf` recibe una **cadena de formato** donde le describís **qué** querés leer. Cada conversión +empieza con `%`: + +```c +int n; +double price; +char letter; + +scanf("%d", &n); /* lee un entero decimal */ +scanf("%lf", &price); /* lee un número con coma (double) */ +scanf(" %c", &letter); /* lee UN carácter (el espacio salta blancos) */ +``` + +**Siempre** se le pasa la **dirección** de la variable (con `&`): `&n`, no `n`. Suena obvio, pero es +el error N.º 1. + +## 5.2 Qué devuelve `scanf`: el dato clave para no romperte + +`scanf` **no** devuelve lo que leyó. Devuelve **cuántas variables llenó**: + +- `scanf("%d", &n)` → devuelve `1` si leyó un número bien. +- `scanf("%d %d", &a, &b)` → devuelve `2` si leyó los dos. +- `0` si el primer dato **no coincidía** con el formato (por ejemplo, pedís `%d` y hay `abc`). +- `EOF` si la entrada terminó **antes** de leer nada. + +Por eso, **siempre** se compara el retorno con lo esperado. Es la única forma de saber si salió bien. + +## 5.3 `%c`, `%s` y `%[...]`: tres formas muy distintas de leer texto + +| Formato | Qué lee | ¿Salta espacios? | Nota | +|---|---|---|---| +| `%c` | **un** carácter | **No** | Si el próximo byte es espacio o `\n`, **ese** te da | +| `%s` | una **palabra** (sin espacios) | **Sí** | **Peligroso**: sin ancho, puede desbordar | +| `%[...]` | los caracteres **de un conjunto** | **No** | `%[^\n]` = "todo hasta el `\n`" | + +Ejemplos de cómo se comportan ante la entrada ` hola mundo`: + +- `scanf("%c", &c)` → te da el **primer espacio**, no la `h`. +- `scanf("%s", word)` → salta el espacio y te da `hola` (se detiene en el espacio). +- `scanf("%[^\n]", line)` → te da ` hola mundo` **completo** (lee todo menos el `\n`). + +> **El espacio en el formato.** Un espacio en la cadena de formato significa "salteen cero o más +> espacios". Por eso `" %c"` (con espacio **antes** de `%c`) salta el `\n` pendiente y recién ahí lee +> el carácter. Es el truco para leer un `%c` después de un `%d`. + +## 5.4 Los dos superpoderes (y una advertencia): ancho y `*` + +**Ancho máximo.** Un número entre `%` y la letra limita cuánto lee: `%19s` lee **como máximo 19** +caracteres (más el `'\0'`), para un búfer de 20. **Nunca** uses `%s` sin ancho: es el peligro de +desbordar. + +```c +char word[20]; +scanf("%19s", word); /* a lo sumo 19 letras + '\0': seguro */ +``` + +**Supresión (`*`).** Un `*` después del `%` significa "leé esto, pero **no lo guardes**". Sirve para +saltear prefijos: + +```c +int value; +scanf("%*s %d", &value); /* lee y TIRA una palabra; guarda solo el número */ +/* ante "ID: 12345", descarta "ID:" y guarda 12345 */ +``` + +**La advertencia (`%n`).** `%n` **no lee**: guarda cuántos caracteres se leyeron hasta ahí. Sirve para +saber "por dónde quedó", pero se lo considera riesgoso: no lo uses con formatos que vengan de afuera. + +## 5.5 El peligro del tipo equivocado + +`scanf` **no sabe** qué tipo le pasaste: se guía **solo** por el formato. Si el formato dice `%d` +(espera un `int *`) y le pasás un `double *`, escribe un `int` donde vos esperabas un `double`: +**comportamiento indefinido**. Puede dar basura, corromper memoria, o "andar" por casualidad hasta que +deje de andar. + +> **Regla de oro de `scanf`:** el formato y el puntero **tienen que coincidir exactamente**. El +> compilador no puede revisarlo (por los `...`), así que la responsabilidad es toda tuya. Ojo con el +> clásico: en **`scanf`**, para un `double` se usa **`%lf`** (¡no `%f`, como en `printf`!). + +## 5.6 Falta `%i`, `%u`, `%x`… y `gets` ya no existe + +- `%i` adivina la base (decimal, `0x` hex, `0` octal); `%d` solo decimal. +- `%u` es para enteros **sin signo**; `%f` para `float`; `%lf` para `double`. +- **`gets` fue eliminada** del estándar (C11) porque leía una línea **sin límite** de tamaño: + no podía saber cuándo parar y **desbordaba** el búfer. Su reemplazo seguro es `fgets` **con tamaño**. + +## 5.7 `sscanf`: el mismo `scanf`, pero sobre una cadena + +`sscanf` es idéntico a `scanf`, salvo que **no lee de un flujo** (`stdin`), sino de una **cadena** que +ya tenés en memoria. Es el compañero natural de `fgets`: leés la línea **una vez** y después la +interpretás sin volver a tocar `stdin`. + +```c +#include + +int main(void) { + char line[100]; + if (fgets(line, sizeof line, stdin) == NULL) { + return 0; + } + + int a, b; + /* sscanf(cadena, formato, direcciones...): + igual que scanf, pero la "fuente" es `line` en vez de stdin. + Devuelve cuántos valores asignó (2 si leyó los dos enteros). */ + if (sscanf(line, "%d %d", &a, &b) == 2) { + printf("suma: %d\n", a + b); + } + + return 0; +} +``` + +> **Por qué conviene.** Leer **una vez** con `fgets` y después analizar con `sscanf` es más seguro que +> encadenar `scanf` sobre `stdin`: controlás el tamaño de la línea, la ves completa y, si el formato no +> cierra, **no** dejaste residuos en el flujo. + +## Práctica de la Parte 5 + +### Ejercicio 13.10 — Validador de edad numérica + +**Conceptos** · `scanf`, valor de retorno, fallo de conversión, descarte de entrada inválida + +**Contexto** · Los formularios tienen que sobrevivir a que alguien escriba texto donde se pide un +número. Simplificación: pedir una edad y detectar el error sin comportarse de forma errática. + +**Consigna** · Pedir la edad con `scanf("%d", &age)`. Si la conversión **falla** (el retorno **no** es +`1`), mostrar un mensaje de entrada inválida y **no** usar `age`. El programa **no** debe bloquearse ni +leer datos basura. + +**Entrada** · Una línea con un número (caso válido) o con texto (caso inválido). +**Salida** · La edad registrada, o el mensaje de error. + +**Escenario de prueba** + +```text +Entrada: 34 +Salida: Edad registrada: 34 + +Entrada: treinta +Salida: Entrada inválida: se esperaba un número entero. +``` + +**Criterios de aceptación** +- [ ] Comprueba `scanf(...) == 1`. +- [ ] Con texto, informa el error y **no** usa un valor no inicializado. +- [ ] Con un número válido, lo muestra correctamente. +- [ ] No deja la entrada "sucia" para una lectura posterior. + +### Ejercicio 13.11 — Extracción de tokens con formato avanzado + +**Conceptos** · `scanf` con ancho máximo (`%19s`), supresión (`*`), valor de retorno + +**Contexto** · Muchas líneas traen etiquetas (`ID: 12345`) y solo interesa el valor. Simplificación: +extraer el número descartando el prefijo, con un solo `scanf`. + +**Consigna** · Leer una línea con etiqueta y **extraer solo el valor numérico**, descartando el +prefijo textual. Usá un **ancho máximo** para el token del prefijo y el modificador `*` para que +`scanf` **no lo guarde**. + +**Entrada** · Una línea con formato `ETIQUETA: número`. +**Salida** · El valor numérico con una etiqueta clara. + +**Escenario de prueba** + +```text +Entrada: ID: 12345 +Salida: valor = 12345 + +Entrada: REF: 00042 +Salida: valor = 42 +``` + +**Criterios de aceptación** +- [ ] Usa el ancho máximo en el token descartado (`%19s` o similar). +- [ ] Usa `*` para **no** asignar la etiqueta. +- [ ] Extrae el número correctamente en ambos escenarios. +- [ ] Comprueba el retorno de `scanf` (`== 1`). + +### Ejercicio 13.12 — La calculadora de una línea + +**Conceptos** · `scanf` con formato mixto (`%d %c %d`), valor de retorno + +**Contexto** · Una calculadora de bolsillo acepta `número operador número`. Simplificación: leer una +cuenta de suma o resta con un solo `scanf`. + +**Consigna** · Leer una línea con el formato `número operador número` (por ejemplo `8 + 5`) usando +`scanf("%d %c %d", &a, &op, &b)`. Comprobar que se asignaron **3** valores. Si el operador es `+`, +mostrar la suma; si es `-`, la resta; si es otro, avisar que no se reconoce. + +**Entrada** · Una cuenta de una línea. +**Salida** · El resultado, o el aviso de operador no reconocido. + +**Escenario de prueba** + +```text +Entrada: 8 + 5 +Salida: Resultado: 13 + +Entrada: 10 - 4 +Salida: Resultado: 6 + +Entrada: 3 x 4 +Salida: Operador no reconocido: 'x' +``` + +**Criterios de aceptación** +- [ ] Usa `scanf("%d %c %d", ...)` y comprueba que el retorno sea `3`. +- [ ] Resuelve suma y resta correctamente. +- [ ] Avisa en el caso de operador no reconocido. +- [ ] No se rompe si la línea no trae los tres datos. + +--- + +# PARTE 6 — Caracteres: cuando "una letra" pesa más de un byte + +Hasta acá contamos bytes. Pero, ¿un byte es siempre "una letra"? Spoiler: no. + +## 6.1 Byte no es lo mismo que carácter + +- Un **byte** es una unidad de **almacenamiento**: `sizeof(char)` es siempre `1`, y en la práctica son + 8 bits. Es una medida de **cuánto ocupa**. +- Un **carácter** es una noción **humana**: "la letra `á`", "el símbolo `€`". Es una medida de + **significado**. + +La relación entre ambos la define la **codificación**: cuántos bytes (y con qué valores) representan +cada carácter. + +## 6.2 `EOF`, `'\0'`, `'\n'` y `'\r'`: cuatro parecidos que no se parecen + +Antes de seguir, despejemos cuatro nombres que se confunden siempre: + +| Símbolo | Qué es | Valor | ¿Es un byte de dato? | +|---|---|---|---| +| `EOF` | "se acabó la entrada" | `-1` | **No** (es una señal) | +| `'\0'` | carácter **nulo**, terminador de cadenas | `0` | Sí | +| `'\n'` | fin de línea (**LF**) | `10` (`0x0A`) | Sí | +| `'\r'` | retorno de carro (**CR**) | `13` (`0x0D`) | Sí | + +## 6.3 ASCII, UTF-8 y la `á` de dos bytes + +**ASCII** es la codificación clásica: 128 caracteres (letras inglesas, dígitos, signos), **todos de +1 byte**. `'A'` es `65`, `'\n'` es `10`. + +**UTF-8** es la codificación de hoy (Linux, macOS, la web) y es de **longitud variable**: + +- Los caracteres **ASCII** ocupan **1 byte** (compatibilidad total). +- Los demás ocupan **2, 3 o 4 bytes**. El primer byte "avisa" cuántos siguen. + +| Carácter | Bytes (hex) | ¿Cuántos? | +|---|---|---| +| `A` | `41` | 1 byte | +| `ñ` | `C3 B1` | 2 bytes | +| `á` | `C3 A1` | 2 bytes | +| `€` | `E2 82 AC` | 3 bytes | +| `😀` | `F0 9F 98 80` | 4 bytes | + +## 6.4 Por qué esto te importa de verdad + +Como `getchar` entrega **bytes** (no glifos), leer un texto con acentos tiene consecuencias concretas: + +- Si tecleás `á`, `getchar` te va a dar **dos** bytes (`0xC3` y luego `0xA1`). Dos llamadas, **un** + símbolo. +- `strlen("á")` da **2**, no `1`. +- Si recorrés "carácter por carácter" con `getchar`, vas a **partir** los multibyte al medio. + +Reglas prácticas: + +1. **Separá las capas.** ¿Querés contar **bytes** o **símbolos**? No es lo mismo. +2. **Cuando examinés bytes crudos, usá `unsigned char`.** Comparar `0xC3` contra un `char` con signo + trae resultados confusos. +3. **Un `char` no alcanza para todo símbolo.** Para eso existen `wchar_t` y `char32_t` (capítulo + `15-tipos-stddef`). +4. **No adivines la codificación.** Si los acentos salen "raros", casi siempre es que la codificación + del texto no coincide con la que espera la terminal. + +## Práctica de la Parte 6 + +### Ejercicio 13.13 — Filtro de terminadores de línea + +**Conceptos** · `getchar`, `EOF`, `'\n'`, `'\r'`, `'\0'`, distinción de símbolos + +**Contexto** · Los analizadores de texto necesitan distinguir los símbolos de control de los +caracteres comunes. Simplificación: etiquetar cada símbolo especial. + +**Consigna** · Leer de `stdin` byte a byte y, por cada símbolo: + +- `'\n'` → imprimir ``; +- `'\r'` → imprimir ``; +- `'\0'` → imprimir ``; +- cualquier otro → imprimir el carácter tal cual; +- al llegar a `EOF` → imprimir `` y terminar. + +**Entrada** · Secuencia de bytes hasta `EOF`. +**Salida** · El carácter (o su etiqueta) por cada símbolo; `` al final. + +**Escenario de prueba** + +```text +printf 'a\nb\rc\0d' | ./filtro +a + +b + +c + +d + +``` + +> `'\0'` solo aparece si la entrada es un flujo con bytes `0x00` (por ejemplo, una tubería); no se +> puede teclear en una terminal. + +**Criterios de aceptación** +- [ ] Distingue `'\n'`, `'\r'`, `'\0'` y `EOF` correctamente. +- [ ] Imprime el resto de los caracteres sin alterarlos. +- [ ] Detecta `'\0'` como byte real `0x00` (no lo confunde con `EOF`). +- [ ] Termina en el primer `EOF`, con la etiqueta `` incluida. + +### Ejercicio 13.14 — Radiografía de un acento + +**Conceptos** · `getchar`, bytes altos, UTF-8 multibyte, `unsigned char` + +**Contexto** · Ver "por dentro" cómo se guarda una letra acentuada es la mejor forma de entender +multibyte. Simplificación: mostrar cada byte de la entrada en hexadecimal. + +**Consigna** · Leer de `stdin` byte a byte y mostrar cada uno en **hexadecimal** (por ejemplo `C3 A1`), +separados por espacio, hasta `EOF`. Al final, mostrar también el **total de bytes**. + +**Entrada** · Cualquier texto hasta `EOF` (probá con `á`, `ñ`, `€`). +**Salida** · La seguidilla de bytes en hex y el total. + +**Escenario de prueba** + +```text +printf 'á' | ./radio +Bytes: C3 A1 +Total: 2 bytes +``` + +```text +printf 'A' | ./radio +Bytes: 41 +Total: 1 byte +``` + +> **Lo que tenés que ver:** `á` son **dos** bytes (`C3 A1`), `A` es **uno** (`41`). Ahí está el +> multibyte, en la mano. + +**Criterios de aceptación** +- [ ] Lee con `getchar` en un `int` y corta ante `EOF`. +- [ ] Muestra cada byte en hexadecimal, con dos dígitos (por ejemplo `0A`, no `A`). +- [ ] Informa el total de bytes leídos. +- [ ] El acento da **2** bytes y la `A` da **1** byte. + +--- + +# PARTE 7 — Robustez: que no se rompa nunca + +Un programa frágil **asume** que la entrada es perfecta. Un programa robusto **asume lo contrario**. +Esta parte es corta pero salvavidas. + +## 7.1 Comprobá siempre los retornos + +Cada lectura te **avisa** cómo salió. Ignorar el aviso es pedir un desastre. Este es el reflejo que +tenés que automatizar: + +```c +int c; +while ((c = getchar()) != EOF) { /* ... */ } /* getchar contra EOF */ + +char line[100]; +if (fgets(line, sizeof line, stdin) == NULL) { /* fgets contra NULL */ + /* fin de entrada o error */ +} + +int n; +if (scanf("%d", &n) != 1) { /* scanf contra lo esperado */ + /* no se leyó un número */ +} +``` + +## 7.2 `feof` y `ferror`: ¿se acabó o se rompió? + +Cuando una lectura falla, puede ser por **dos** motivos distintos: se acabó la entrada (**normal**) o +hubo un **error** (falla real). Dos funciones los separan: + +```c +int feof(FILE *stream); /* ¿se llegó al final de la entrada? */ +int ferror(FILE *stream); /* ¿hubo un error de lectura/escritura? */ +``` + +Y `perror` imprime un mensaje legible del último error, en `stderr`: + +```c +if (ferror(stdin)) { + perror("stdin"); /* imprime algo como: stdin: */ +} +``` + +`clearerr(stream)` **borra** los indicadores de error y de fin de archivo. Se usa si querés **seguir +leyendo** después de haber tocado el final (si no, el flujo "queda pegado" en `EOF`). + +## 7.3 El bug estrella: el bucle infinito + +Si la entrada **termina** y tu bucle **no** corta, vuelve a leer, recibe `EOF` de nuevo, y **gira para +siempre**. El patrón correcto es **siempre** tener una condición de corte: + +```c +int c; +while ((c = getchar()) != EOF) { /* procesar c */ } + +/* Con scanf: cortar si no asignó lo esperado. */ +int n; +while (scanf("%d", &n) == 1) { /* procesar n */ } +``` + +> **Caso especial.** Si `scanf` falla porque el **formato no coincide**, deja el carácter culpable +> **sin consumir**. Si no lo descartás, el bucle lo vuelve a encontrar y **no avanza**: bucle infinito. +> La solución es descartar el carácter problemático (o el resto de la línea) o abandonar la lectura. + +## 7.4 La entrada puede ser maliciosa + +Si la entrada viene de cualquiera, no confíes: + +- **Acotá siempre** los búferes (nunca `%s` sin ancho, nunca `gets`). +- **Comprobá rangos**: que un entero entre en el tipo, que un índice sea válido. +- **Convertí con control de error**, en vez de confiar en conversiones "optimistas". + +## 7.5 `strtol` y `strtod`: convertir texto a número sin sorpresas + +`atoi` y `atof` **no avisan** cuando la conversión falla: devuelven `0` y listo. Para validar en serio, +`strtol` (texto → `long`) y `strtod` (texto → `double`) te dan **tres** señales en una sola llamada: + +```c +#include /* strtol, strtod */ +#include /* errno */ + +/* strtol(cadena, endptr, base): + - Devuelve el valor convertido. + - endptr (puntero al puntero): queda apuntando al primer carácter NO convertido. + · si endptr == inicio → no se convirtió nada (no era un número); + · si *endptr == '\0' → se consumió toda la cadena (todo era el número); + · si *endptr apunta a otra cosa → sobra basura después del número. + - errno se pone en ERANGE si el valor no entra en un long. + Por eso se limpia errno = 0 ANTES de llamar. */ +char *endptr; +errno = 0; +long value = strtol(text, &endptr, 10); +``` + +Con esas tres señales (¿convirtió algo?, ¿sobró basura?, ¿hubo desborde?) tenés todo para decidir si la +entrada es válida. `strtod` funciona igual, pero para `double`. + +## Práctica de la Parte 7 + +### Ejercicio 13.15 — Bucle robusto ante cierre de terminal + +**Conceptos** · `getchar`, `EOF`, corte de bucles, fin de entrada por teclado + +**Contexto** · Un programa interactivo tiene que terminar **limpio** cuando el usuario cierra la +entrada, sin quedar girando. Simplificación: contar caracteres y líneas hasta `EOF`. + +**Consigna** · Leer de `stdin` con `getchar()` hasta `EOF`. Contar los caracteres y las líneas +(cantidad de `'\n'`). Al detectar `EOF`, mostrar el resumen y un mensaje de cierre. **No** debe haber +bucle infinito. + +**Entrada** · Líneas de texto, cerradas con **Ctrl+Z** (Windows) o **Ctrl+D** (Linux/macOS). +**Salida** · Conteo de caracteres y líneas, y mensaje de fin. + +**Escenario de prueba** + +```text +Entrada: +hola +mundo +(Ctrl+Z + Enter / Ctrl+D) + +Salida: +Leídos 11 caracteres en 2 líneas. +Fin de la entrada (EOF). Chau. +``` + +> `hola\n` son 5 caracteres y `mundo\n` son 6: **11** en total y **2** saltos de línea. + +**Criterios de aceptación** +- [ ] El bucle corta ante `EOF` (no hay giro infinito). +- [ ] Cuenta caracteres y líneas correctamente. +- [ ] Con la entrada del ejemplo, informa **11** caracteres y **2** líneas. +- [ ] Muestra un mensaje de cierre y termina. + +### Ejercicio 13.16 — Validador de número a prueba de balas + +**Conceptos** · `fgets`, `strtol`, validación de rango, detección de basura + +**Contexto** · Un dato confiable no es solo "un número": es un número **dentro de un rango** y **sin +basura alrededor**. Simplificación: leer un puerto de red (0 a 65535) y validarlo del todo. + +**Consigna** · Leer una línea con `fgets` y convertirla con `strtol`. La entrada es válida **solo si**: +se convirtió algo, **no** sobra nada (salvo el `'\n'`), y el valor está entre `0` y `65535`. Si algo +falla, informar el motivo exacto. + +**Entrada** · Una línea de texto. +**Salida** · El puerto validado, o el motivo del rechazo. + +**Escenario de prueba** + +```text +Entrada: 8080 +Salida: Puerto válido: 8080 + +Entrada: 8080x +Salida: Error: hay caracteres extra después del número ('x') + +Entrada: 70000 +Salida: Error: 70000 está fuera del rango 0..65535 + +Entrada: puerto +Salida: Error: 'puerto' no es un número +``` + +**Criterios de aceptación** +- [ ] Lee la línea con `fgets` y comprueba el retorno. +- [ ] Usa `strtol` con el puntero de fin (`endptr`) para saber hasta dónde convirtió. +- [ ] Detecta: basura antes, basura después, vacío y fuera de rango. +- [ ] Un valor válido se acepta; los cuatro escenarios de error coinciden. + +--- + +# PARTE 8 — Proyecto final: tu propia consola de comandos + +Llegó la hora de juntar **todo**. Vas a construir un intérprete de comandos **mínimo**: una versión de +juguete de tu terminal. + +## 8.1 Qué vas a construir + +Un programa que muestre un **prompt** `> `, espere una orden y reaccione. No va a ejecutar programas +reales: solo va a entender unos pocos comandos. Pero **todo** lo que aprendiste va a estar adentro: +leer líneas acotadas, interpretar el comando, validar números, manejar residuos del búfer y cerrar con +`EOF` o con `salir`. + +## 8.2 Los comandos a entender + +| Orden | Qué hace | +|---|---| +| `eco ` | Imprime `` tal cual (puede tener espacios). | +| `suma ` | Imprime la suma de dos enteros. | +| `salir` | Termina el programa. | +| (otra cosa) | Error de comando desconocido. | + +## Ejercicio final + +### Ejercicio 13.17 — (JEFE) Simulador de consola de comandos interactiva + +**Conceptos** · `fgets`, `sscanf`, `stdin`/`stdout`/`stderr`, limpieza de búfer, validación numérica, +`EOF`, corte de bucles, comandos con formato. + +**Contexto** · Un intérprete de comandos (un "shell" mínimo) es el ejercicio que reúne **todo** el +capítulo: lee órdenes, interpreta argumentos, valida errores sin bloquearse y termina limpio. Es una +**simplificación** de una consola real: no ejecuta programas, solo reconoce unos pocos comandos. + +**Consigna** · Desarrollar una aplicación de línea de comandos que: + +1. Muestre un **prompt** `> ` y lea órdenes del usuario combinando **`fgets`** y **`sscanf`** según el + comando. +2. Reconozca los comandos de la tabla de 8.2 (`eco`, `suma`, `salir`). +3. Maneje correctamente los **residuos de búfer** cuando la entrada **excede el tamaño esperado** + (comando demasiado largo, o una línea con más datos de los que consume el formato). +4. **Valide** errores de formato numérico (por ejemplo, `suma 4 abc`) **sin bloquearse**. +5. Termine de forma limpia al detectar **`EOF`** o al recibir `salir`, **sin** bucles infinitos ni + comportamiento indefinido. + +**Entrada** · Órdenes por `stdin`, una por línea; la sesión cierra con `salir` o `EOF`. +**Salida** · El resultado de cada comando por `stdout`; los errores por `stderr`; un cierre al final. + +**Escenario de prueba** + +```text +Entrada: +eco hola mundo +suma 4 7 +suma 4 abc +xyz +salir + +stdout: +> hola mundo +> 11 +> Error: 'abc' no es un número válido +> Error: comando desconocido: xyz +> Chau. +``` + +```text +Entrada: +(Ctrl+Z + Enter / Ctrl+D) + +stdout: +> Fin de entrada (EOF). Chau. +``` + +Caso de orden demasiado larga (con búfer de 128 bytes): + +```text +Entrada: eco <200 caracteres> +Salida: Error: la orden supera el tamaño máximo del búfer. +``` + +**Identificadores sugeridos** · `line`, `command`, `arg1`, `arg2`, `LINE_SIZE`, `matched`, `running` + +**Restricciones/pistas** +- Leer cada orden con `fgets` en un búfer **acotado** y detectar truncamiento (3.4). +- Para `suma`, interpretar los dos enteros con `sscanf` sobre la línea ya leída (más seguro que + `scanf` directo sobre `stdin`) y **comprobar que devolvió `2`**; si no, error de formato. +- Descartar el resto de la línea cuando haga falta (4.5). **No** usar `fflush(stdin)`. +- Cortar ante `EOF`: `fgets` devuelve `NULL`. +- Separar la salida normal (`stdout`) de los errores (`stderr`). + +**Criterios de aceptación** +- [ ] Muestra el prompt y lee órdenes con `fgets` acotado. +- [ ] `eco` imprime el texto conservando los espacios internos. +- [ ] `suma` con dos enteros válidos imprime la suma correcta. +- [ ] `suma` con un argumento no numérico informa el error y **continúa** (no se bloquea). +- [ ] Un comando desconocido informa el error y **continúa**. +- [ ] Una orden que excede el búfer se detecta y no corrompe la memoria ni la lectura siguiente. +- [ ] `salir` y el `EOF` terminan el programa limpiamente (con mensaje de cierre). +- [ ] No hay bucles infinitos ni comportamiento indefinido ante entradas inesperadas. +- [ ] Errores por `stderr`, resultados por `stdout`. + +--- + +## Cierre: lo que te llevás + +Si tuvieras que quedarte con **una sola idea**, que sea esta: **leer de la consola es leer de una +tubería con búfer**. Todo lo demás se deduce de ahí. + +- Tus programas tienen **tres tuberías** (`stdin`, `stdout`, `stderr`), y el **shell** decide a qué + está conectada cada una. Tu código no cambia. +- **Elegí la función por su trabajo**: `getchar` (un byte), `fgets` (una línea, seguro), `scanf` (con + formato, cómodo pero resbaladizo). +- **`EOF` es una señal** (un `int` negativo), no un carácter. Nunca lo guardes en un `char`. +- **El búfer causa los misterios**: `scanf` deja el `\n` pendiente; por eso existe la limpieza de + línea, y por eso `fflush(stdin)` está prohibido. +- **La robustez es una decisión**: comprobar retornos, acotar búferes, validar datos y cortar ante + `EOF`. +- **Un carácter no siempre es un byte**: una `á` son dos bytes. + +Y el puente hacia lo que viene: **para C, un archivo es un flujo más**. En `14-archivos` vas a reusar +**exactamente** estas ideas —`fgetc`, `fgets`, `fscanf`, `feof`, los búferes, `EOF`— solo que sobre un +flujo abierto con `fopen` en lugar de sobre `stdin`. Si dominaste este capítulo, el de archivos es +casi el mismo capítulo con una apertura y un cierre. diff --git a/book/14-archivos.md b/book/14-archivos.md index a6eb684..646b2cc 100644 --- a/book/14-archivos.md +++ b/book/14-archivos.md @@ -1,11 +1,12 @@ -# Capítulo 13 — Archivos +# Capítulo 14 — Archivos > Capítulo intermedio/avanzado. Se apoya en lo aprendido sobre arreglos (`06`), punteros (`07`), -> `struct` (`08`), memoria dinámica (`09-memoria-dinamica`) y arenas (`12-arenas`). -> Se recomienda leerlo después de tener claros `malloc`, `free`, el uso de punteros y -> la idea de reservar memoria en bloques (arenas). +> `struct` (`08`), memoria dinámica (`09-memoria-dinamica`), entrada/salida estándar +> (`13-entrada-salida-estandar`) y arenas (`12-arenas`). +> Se recomienda leerlo después de tener claros `malloc`, `free`, el uso de punteros, +> el manejo de `stdin`/`stdout`/`stderr` y la idea de reservar memoria en bloques (arenas). -## 13.1 ¿Por qué leer y escribir archivos? +## 14.1 ¿Por qué leer y escribir archivos? Hasta ahora, nuestros programas intercambiaban información únicamente a través de la consola (entrada por teclado y salida por pantalla). Esto está bien para ejercicios cortos, pero @@ -16,7 +17,7 @@ archivos compactados. Un archivo nos permite guardar información en disco para usarla más tarde, compartirla entre programas o procesarla por lotes. El desafío es hacerlo de forma **clara, segura y correcta**. -### 13.1.1 Texto vs Binario +### 14.1.1 Texto vs Binario En C, los archivos se dividen en dos grandes categorías: @@ -31,7 +32,7 @@ binario como si fuera texto corrompe su estructura. Por eso, al abrir un archivo usamos modos distintos: `r`, `w`, `a` para texto y `rb`, `wb`, `ab` (más `+` cuando corresponde) para binario. -### 13.1.2 Recursos: abrir y cerrar +### 14.1.2 Recursos: abrir y cerrar Todo archivo abierto consume un recurso del sistema. Por eso, **siempre hay que cerrarlo** cuando terminamos de usarlo. El par básico es: @@ -46,13 +47,13 @@ Un error común es olvidar `fclose()` o no cerrarlo en todos los caminos posible si ocurre un error a mitad del proceso). Más adelante veremos cómo mitigar esto usando distintas estrategias de gestión de memoria/buffers. -## 13.2 Lectura y escritura de archivos de texto +## 14.2 Lectura y escritura de archivos de texto La librería estándar de C (`stdio.h`) ofrece un conjunto de funciones para trabajar con archivos. Antes de usarlas, veamos **cada una**: cómo se llama, qué recibe y qué devuelve. Casi todas giran en torno al tipo `FILE *`, que es el **manejador** (handle) del archivo abierto. -### 13.2.1 Modos de apertura +### 14.2.1 Modos de apertura `fopen` recibe la ruta y un **modo** que decide qué se puede hacer con el archivo: @@ -78,16 +79,16 @@ Agregando una `b` (`"rb"`, `"wb"`, `"ab"`, ...) se abre en **modo binario**. > > Esa es toda la diferencia (y solo importa en Windows; en Linux/macOS no hay traducción). Por eso los > archivos cuyos bytes deben leerse **exactos** —imágenes, audio, o la cabecera ASCII de un PPM -> mezclada con píxeles— se abren con `"b"`. Ojo: el `\n` de la cabecera PPM del ejemplo (13.5) es un +> mezclada con píxeles— se abren con `"b"`. Ojo: el `\n` de la cabecera PPM del ejemplo (14.5) es un > **byte real del archivo** (`0x0A`), no una traducción del modo texto. -### 13.2.2 Referencia: funciones de texto +### 14.2.2 Referencia: funciones de texto #### `fopen` — abrir un archivo ```c FILE *fopen(const char *path, const char *mode); ``` -- **Recibe:** `path`, la ruta del archivo; `mode`, el modo de apertura (ver 13.2.1). +- **Recibe:** `path`, la ruta del archivo; `mode`, el modo de apertura (ver 14.2.1). - **Devuelve:** un `FILE *` (el manejador del archivo abierto) o `NULL` si no se pudo abrir. - **Ojo:** hay que verificar siempre `NULL` antes de usar el manejador. @@ -190,7 +191,7 @@ Y leído paso a paso: > Fijate en el gráfico: el cursor termina en la posición 9, **justo antes del `\n`**. `fscanf` consume > todos los dígitos de cada número, pero **nunca** el `\n` que sigue al último: ese separador queda > **sin leer**. Si tu programa necesita leer lo que viene inmediatamente después, tenés que "comerte" -> ese separador con `fgetc()`. Es exactamente lo que hace el ejemplo de la sección 13.5.2 antes de leer +> ese separador con `fgetc()`. Es exactamente lo que hace el ejemplo de la sección 14.5.2 antes de leer > los píxeles. #### `feof` — ¿se llegó al final? @@ -229,7 +230,7 @@ int tolower(int c); > `printf`, `scanf`, `malloc`, `free` y `sizeof` ya se vieron en capítulos anteriores; acá nos > concentramos en lo específico de archivos. -### 13.2.3 Ejemplo: leer un archivo carácter por carácter +### 14.2.3 Ejemplo: leer un archivo carácter por carácter Con `fgetc()` leés **un byte por vez**. El valor de retorno es un `int`, no un `char`, para que pueda distinguirse el valor `EOF` (fin de archivo, normalmente `-1`). Este es el patrón clásico @@ -265,7 +266,7 @@ Observá tres detalles que se repiten en **todos** los programas con archivos: 2. El bucle corta cuando `fgetc()` devuelve `EOF`. 3. Se cierran **los dos** archivos, en orden inverso al que se abrieron. -### 13.2.4 Ejemplo: leer línea por línea con `fgets` +### 14.2.4 Ejemplo: leer línea por línea con `fgets` Para trabajar línea a línea, `fgets()` es más cómodo que `fgetc()`. Lee como máximo `n - 1` caracteres, o hasta encontrar un `\n` (que **deja incluido** en el buffer), o hasta el fin del @@ -297,7 +298,7 @@ int main(void) { > buffer, `fgets()` la entrega **cortada** en varios pedazos. Para este capítulo, asumimos líneas > razonables. -### 13.2.5 Ejemplo: escribir con formato (`fprintf`) +### 14.2.5 Ejemplo: escribir con formato (`fprintf`) `fprintf()` es a un archivo lo que `printf()` es a la pantalla: acepta el mismo formato. @@ -324,15 +325,15 @@ int main(void) { Con modo `"w"` el archivo se **trunca** (si ya existía, se borra su contenido). Para **agregar** al final sin borrar lo anterior, se usa el modo `"a"`. -## 13.3 Ejercicios: archivos de texto +## 14.3 Ejercicios: archivos de texto > **Regla fundamental:** ningún ejercicio incluye código de solución. Solo se describe el problema, > entrada/salida, conceptos y criterios de aceptación. Se permiten fragmentos de entrada/salida > esperada como ejemplos. -Estos ejercicios trabajan sobre lo visto en la sección 13.2: `fgetc`, `fgets`, `fprintf` y `feof`. +Estos ejercicios trabajan sobre lo visto en la sección 14.2: `fgetc`, `fgets`, `fprintf` y `feof`. -### Ejercicio 13.1 — Contador de líneas y caracteres (texto, básico) +### Ejercicio 14.1 — Contador de líneas y caracteres (texto, básico) **Conceptos** · `FILE*`, `fopen`, `fclose`, `fgetc`, `feof` @@ -375,7 +376,7 @@ incrementar al encontrar `\n`). Siempre validar que el archivo se abrió correct - [ ] Cierra el archivo con `fclose()` antes de terminar. - [ ] Muestra salida con formato claro. -### Ejercicio 13.2 — Copia de archivo de texto (texto, básico) +### Ejercicio 14.2 — Copia de archivo de texto (texto, básico) **Conceptos** · `FILE*`, `fopen`, `fclose`, `fgetc`, `fputc` @@ -413,7 +414,7 @@ y escribir con `fputc()`. Verificar aperturas y cierres. No modificar el conteni - [ ] Reporta errores de forma clara. - [ ] Cierra ambos archivos. -### Ejercicio 13.3 — Leer líneas y guardar filtradas (texto, intermedio) +### Ejercicio 14.3 — Leer líneas y guardar filtradas (texto, intermedio) **Conceptos** · `fgets`, buffers, cadenas, condicionales @@ -463,7 +464,7 @@ Abrir archivos en modo texto. - [ ] Informa cantidad de líneas copiadas. - [ ] Maneja errores de apertura correctamente. -## 13.4 Lectura y escritura de archivos binarios +## 14.4 Lectura y escritura de archivos binarios Cuando trabajamos con datos estructurados o con formatos como imágenes, necesitamos leer/escribir **bytes exactos**, sin que el sistema modifique nada (ni convierta finales de línea). Para eso @@ -472,10 +473,10 @@ usamos `fread()` y `fwrite()`, junto con funciones para ubicarse dentro del arch > **Antes de leer, mirá.** Un archivo binario no se puede abrir con un editor de texto: vas a ver > basura, y encima el editor puede corromperlo. Para *ver* los bytes sin instalar nada, usá > **ImHex Web** (): un editor hexadecimal que corre entero en el -> navegador. Abrí ahí el BMP de la sección 13.8.6 o el PPM de la 13.5.1, ubicá los offsets y +> navegador. Abrí ahí el BMP de la sección 14.8.6 o el PPM de la 14.5.1, ubicá los offsets y > contrastá campo por campo lo que muestra la herramienta con lo que escribe tu programa. -### 13.4.1 Referencia: funciones binarias +### 14.4.1 Referencia: funciones binarias #### `fread` — leer un bloque ```c @@ -530,7 +531,7 @@ dependiendo del formato y de la plataforma. En este capítulo nos enfocamos en * la estructura byte a byte del archivo** y leerlo correctamente según su especificación, sin asumir conversiones mágicas. -### 13.4.2 Ejemplo: averiguar el tamaño de un archivo +### 14.4.2 Ejemplo: averiguar el tamaño de un archivo Antes de leer un archivo binario completo conviene saber **cuántos bytes tiene**. El truco es moverse al final con `fseek()`, preguntar la posición con `ftell()` y volver al principio con @@ -560,7 +561,7 @@ int main(void) { > `long` alcanza para archivos de tamaño normal. Para archivos gigantes existen `fseeko()` y > `ftello()` (con `off_t`), pero no hacen falta en este capítulo. -### 13.4.3 Ejemplo: leer el archivo completo a memoria +### 14.4.3 Ejemplo: leer el archivo completo a memoria El patrón es: medir el tamaño, reservar un bloque con `malloc()`, leer todo de una sola vez con `fread()` y **siempre** liberar al terminar: @@ -599,7 +600,7 @@ int main(void) { > `fread()` devuelve **cuántos elementos** leyó realmente. Puede leer menos de lo pedido si el > archivo se acorta o si ocurre un error, así que conviene comparar con lo esperado. -### 13.4.4 Ejemplo: leer una cabecera fija a una `struct` +### 14.4.4 Ejemplo: leer una cabecera fija a una `struct` Los archivos binarios suelen empezar con una cabecera de tamaño fijo. Se puede leer de una sola vez dentro de una `struct` que tenga **exactamente** el mismo diseño de bytes: @@ -640,14 +641,14 @@ int main(void) { > `struct` en memoria puede **no coincidir** con el del archivo. En esos casos conviene leer los > campos de a uno con `fread()` a variables sueltas, en lugar de a una `struct` entera. -## 13.5 Ejemplo guiado: otro formato binario — PPM (Netpbm) +## 14.5 Ejemplo guiado: otro formato binario — PPM (Netpbm) Antes del desafío de BMP, practiquemos con un formato binario **más simple todavía**: el **PPM** (Portable PixMap), uno de los formatos **Netpbm**. Su virtud didáctica es que su cabecera es **texto plano** y sus datos son una **matriz de colores sin compresión ni padding**, así que separa muy bien "leer la cabecera" de "leer los píxeles". -### 13.5.1 Descripción del formato PPM (variante P6) +### 14.5.1 Descripción del formato PPM (variante P6) Un archivo `.ppm` binario (variante **P6**) tiene esta estructura: @@ -675,7 +676,7 @@ Algunas diferencias con BMP que conviene notar: | Orden de las filas | Arriba → abajo | Abajo → arriba | | Relleno por fila (padding) | No tiene | Múltiplo de 4 bytes | -### 13.5.2 Cómo leerlo +### 14.5.2 Cómo leerlo El siguiente programa lee la cabecera con `fscanf()`/`fgetc()` (son texto) y luego los píxeles con `fread()` (son binario). El truco clave está en el **paso 3**: después de `maxval` hay @@ -702,7 +703,7 @@ int main(void) { } /* 2. ancho, alto y valor máximo (son números en ASCII). - * Detalle de fscanf: ver la sección 13.2.2. */ + * Detalle de fscanf: ver la sección 14.2.2. */ int width, height, maxval; if (fscanf(fp, "%d %d %d", &width, &height, &maxval) != 3) { fprintf(stderr, "cabecera PPM inválida\n"); @@ -749,14 +750,14 @@ int main(void) { > mismas que necesitarás para BMP: leer y validar la firma, extraer dimensiones, consumir el > separador exacto entre cabecera y datos, y mapear `(x, y)` a un índice del buffer. La diferencia > es que BMP agrega **orden invertido de filas** y **relleno (padding) por fila**, que veremos en la -> sección 13.8. +> sección 14.8. -## 13.6 Gestión de memoria: `malloc/free` vs Arenas +## 14.6 Gestión de memoria: `malloc/free` vs Arenas Al leer archivos, especialmente archivos binarios de tamaño variable o con estructuras anidadas, necesitamos almacenar lo leído en memoria. Tenemos dos enfoques principales: -### 13.6.1 Con `malloc` y `free` +### 14.6.1 Con `malloc` y `free` - Reservamos exactamente lo que necesitamos (`malloc` del tamaño leído). - Liberamos cada bloque cuando ya no lo usamos (`free`). @@ -764,7 +765,7 @@ necesitamos almacenar lo leído en memoria. Tenemos dos enfoques principales: - **Desventaja:** si leemos muchos bloques o estructuras pequeñas, debemos rastrear cada `free`. También complica liberar todo ante un error a mitad del proceso. -### 13.6.2 Con arenas (bump allocator) +### 14.6.2 Con arenas (bump allocator) Recordando el concepto del Capítulo 12: reservamos **un bloque grande** una sola vez, avanzamos un cursor para repartir memoria y **liberamos todo junto** (reiniciamos cursor o @@ -782,7 +783,7 @@ Aplicado a lectura de archivos: a **leer archivos correctamente** y que puedas implementar la lectura **usando malloc/free** y también **usando arenas**, entendiendo cuándo conviene cada uno. -## 13.7 Errores y robustez +## 14.7 Errores y robustez Al trabajar con archivos, hay que considerar fallas posibles: @@ -800,13 +801,13 @@ Al trabajar con archivos, hay que considerar fallas posibles: 4. Cerrar el archivo en todos los caminos (incluso ante errores). Esto puede hacerse con cuidado o estructurando el código para asegurar el cierre. -## 13.8 El formato BMP: documentación de referencia +## 14.8 El formato BMP: documentación de referencia Antes del desafío final, necesitamos entender el formato que vamos a interpretar: **BMP** (Windows Bitmap). Lo elegimos porque es uno de los formatos de imagen **más simples** de leer: guarda los píxeles **sin compresión** y documenta su estructura con cabeceras bien definidas. -### 13.8.1 Estructura general de un BMP +### 14.8.1 Estructura general de un BMP Un archivo `.bmp` se compone de estas partes, en este orden: @@ -815,7 +816,7 @@ Un archivo `.bmp` se compone de estas partes, en este orden: 3. **Paleta de colores** (opcional): solo para profundidades de 1, 4 u 8 bits. 4. **Datos de píxeles** (pixel data): la matriz de píxeles. -### 13.8.2 BITMAPFILEHEADER (14 bytes) +### 14.8.2 BITMAPFILEHEADER (14 bytes) | Offset | Tamaño | Campo | Descripción | |---|---|---|---| @@ -825,7 +826,7 @@ Un archivo `.bmp` se compone de estas partes, en este orden: | 8 | 2 | `bfReserved2` | Reservado, debe ser 0. | | 10 | 4 | `bfOffBits` | Offset en bytes donde comienzan los datos de píxeles. | -### 13.8.3 BITMAPINFOHEADER (40 bytes, versión más común) +### 14.8.3 BITMAPINFOHEADER (40 bytes, versión más común) | Offset | Tamaño | Campo | Descripción | |---|---|---|---| @@ -844,7 +845,7 @@ Un archivo `.bmp` se compone de estas partes, en este orden: **Resumen:** desde el offset 14 hasta el 53 (40 bytes) está toda la información que necesitamos para saber cómo leer los píxeles. -### 13.8.4 Datos de píxeles +### 14.8.4 Datos de píxeles Los píxeles comienzan en el offset indicado por `bfOffBits` (campo del archivo, offset 10). @@ -867,17 +868,17 @@ bytesPorFila = ((biWidth * 3 + 3) / 4) * 4 Las filas reales se leen desde el final hacia el principio si la imagen es bottom-up. -### 13.8.5 Paleta (solo para 1, 4 u 8 bpp) +### 14.8.5 Paleta (solo para 1, 4 u 8 bpp) Para 8 bits por píxel, cada píxel es 1 byte que es un **índice** a una tabla de 256 colores (paleta). Cada entrada de la paleta ocupa 4 bytes: `B`, `G`, `R`, reservado (0). La paleta comienza inmediatamente después de la cabecera de bits (offset `14 + biSize`). -### 13.8.6 Anatomía de un BMP real: ejemplo byte a byte +### 14.8.6 Anatomía de un BMP real: ejemplo byte a byte Las tablas de arriba describen el formato en abstracto. Ahora veamos un **BMP real**, chiquito a propósito (5×3 píxeles, 102 bytes), para seguir cada campo sobre los bytes mismos. Es el -mismo archivo que usamos más adelante para verificar el Ejercicio 13.8. +mismo archivo que usamos más adelante para verificar el Ejercicio 14.8. Render de ejemplo-bmp-5x3.bmp: grilla de 5×3 píxeles con los bordes dibujados en negro coordenada (2, 1) —columna 2, fila 1— y es **verde**: `RGB(34, 177, 76)`. Es una forma > rapidísima de saber si tu programa leyó bien las cabeceras y el padding. -### 13.8.7 Cómo leer un BMP (flujo sugerido) +### 14.8.7 Cómo leer un BMP (flujo sugerido) 1. Abrir el archivo en binario (`rb`). 2. Leer los 14 bytes del **BITMAPFILEHEADER**. @@ -973,7 +974,7 @@ buffer copiá la fila `i` del archivo a la fila `height - 1 - i` de la imagen. > el byte menos significativo va primero. Leerlos con `fread` en un `uint32_t` funciona bien en > máquinas little-endian (x86/x64). -## 13.9 Mostrar la imagen en pantalla (opcional): SDL2 +## 14.9 Mostrar la imagen en pantalla (opcional): SDL2 Hasta acá todo el trabajo fue **leer e interpretar** bytes. Un objetivo opcional y muy motivador es **mostrar la imagen** en una ventana. C no trae esto en su biblioteca estándar: dibujar ventanas es @@ -983,7 +984,7 @@ para empezar es **SDL2**. > Esta sección es **opcional** y va más allá del objetivo central del capítulo (leer archivos). > SDL2 es una **dependencia externa**; si tu entorno no la tiene, podés saltearla sin problema. -### 13.9.1 La idea +### 14.9.1 La idea Ya tenés los píxeles en memoria, con 3 bytes por píxel. SDL2 ofrece una función que **envuelve un buffer de píxeles** en una superficie dibujable, sin copiar los datos: @@ -1012,7 +1013,7 @@ Si en cambio guardaste los píxeles en el orden `B, G, R` tal cual vienen en BMP **intercambian** entre rojo y azul. Este es el error más común: colores correctos pero con rojo y azul cambiados. -### 13.9.2 Programa mínimo de visualización +### 14.9.2 Programa mínimo de visualización Este programa asume que ya cargaste la imagen en `pixels` (un buffer `width * height * 3` en orden `R, G, B`, con la fila `0` **arriba**). Muestra la ventana hasta que la cierres o presiones una @@ -1059,14 +1060,14 @@ int main(void) { } ``` -### 13.9.3 El detalle de las filas: BMP es bottom-up +### 14.9.3 El detalle de las filas: BMP es bottom-up SDL espera la **fila 0 arriba**. BMP, en cambio, guarda la imagen **de abajo hacia arriba**. Si volcás los píxeles tal como los leíste, la imagen va a aparecer **invertida verticalmente**. La solución es copiar cada fila a su posición espejada (`fila destino = height - 1 - fila origen`) al momento de cargar el buffer. -### 13.9.4 Compilar y enlazar +### 14.9.4 Compilar y enlazar SDL2 se enlaza como cualquier biblioteca externa. Con `gcc` y la utilidad `sdl2-config`: @@ -1082,14 +1083,14 @@ distribuciones Linux, el paquete suele llamarse `libsdl2-dev`. > explícito del capítulo. Esta sección es la excepción deliberada y por eso queda marcada como > opcional. -## 13.10 Ejercicios: archivos binarios +## 14.10 Ejercicios: archivos binarios Estos ejercicios ponen a prueba la segunda mitad del capítulo: archivos binarios con `fread` y -`fwrite` (sección 13.4), el formato PPM (13.5), arenas (13.6), errores (13.7) y el formato BMP -(13.8). La regla fundamental de la sección 13.3 sigue en pie: **ningún ejercicio incluye código +`fwrite` (sección 14.4), el formato PPM (14.5), arenas (14.6), errores (14.7) y el formato BMP +(14.8). La regla fundamental de la sección 14.3 sigue en pie: **ningún ejercicio incluye código de solución**. -### Ejercicio 13.4 — Lectura binaria con `fread` (binario, intermedio) +### Ejercicio 14.4 — Lectura binaria con `fread` (binario, intermedio) **Conceptos** · Archivos binarios, `fopen` con `rb`, `fread`, buffers de bytes @@ -1126,21 +1127,21 @@ y leer todo con `fread(buf, 1, size, fp)`. Verificar que `fread` leyó lo espera - [ ] Muestra primeros N bytes en formato legible (hex). - [ ] Libera memoria con `free()` y cierra archivo. -### Ejercicio 13.5 — Lectura binaria usando arena (arenas + binario, intermedio) +### Ejercicio 14.5 — Lectura binaria usando arena (arenas + binario, intermedio) **Conceptos** · Arenas (bump allocator), lectura binaria, bloques grandes -**Consigna** · Repetir el Ejercicio 13.4, pero **reservando toda la memoria necesaria desde una arena** +**Consigna** · Repetir el Ejercicio 14.4, pero **reservando toda la memoria necesaria desde una arena** en lugar de llamar `malloc` directamente para el buffer completo. El programa debe: - Crear una arena con capacidad suficiente para alojar el contenido completo del archivo (o un bloque razonable si se desea leer por chunks; para este ejercicio, leer completo). - Leer el archivo binario completo dentro de esa arena (usando el espacio de la arena). -- Mostrar tamaño y primeros N bytes (igual salida que 13.4). +- Mostrar tamaño y primeros N bytes (igual salida que 14.4). - Liberar **todo junto** al final (reiniciar cursor o liberar el bloque de arena completo). **No** hacer `free` por byte/elemento. **Entrada** · Ruta del archivo binario y `N`. -**Salida** · Igual que Ejercicio 13.4. +**Salida** · Igual que Ejercicio 14.4. **Identificadores sugeridos** · `arena`, `arenaBlock`, `capacity`, `offset` @@ -1157,7 +1158,7 @@ es suficiente para bytes). Leer el archivo completo dentro del espacio asignado - [ ] Libera memoria con **un único `free`** (bloque de arena). No hay frees por partes. - [ ] No hay fugas de memoria ni double-free. -### Ejercicio 13.6 — Escritura binaria estructurada (binario, avanzado) +### Ejercicio 14.6 — Escritura binaria estructurada (binario, avanzado) **Conceptos** · `fwrite`, estructuras, archivos binarios, persistencia @@ -1195,23 +1196,23 @@ Verificar cantidad leída. - [ ] Maneja memoria con `malloc/free` adecuadamente. - [ ] No corrompe datos al escribir/leer. -### Ejercicio 13.7 — Persistencia binaria con arena (binario + arenas, avanzado) +### Ejercicio 14.7 — Persistencia binaria con arena (binario + arenas, avanzado) **Conceptos** · Arenas, `fwrite`/`fread`, buffers desde arena -**Consigna** · Modificar el Ejercicio 13.6 para que **el buffer de lectura** (donde se cargan +**Consigna** · Modificar el Ejercicio 14.6 para que **el buffer de lectura** (donde se cargan los puntos leídos) se reserve **desde una arena**, en lugar de `malloc` directo. También puede reservarse el arreglo original desde arena si se desea. El objetivo es practicar lectura binaria usando espacio de arena. -**Entrada/Salida** · Igual que Ejercicio 13.6. +**Entrada/Salida** · Igual que Ejercicio 14.6. **Criterios de aceptación** - [ ] Buffer de lectura reservado desde arena. - [ ] Lectura con `fread` correcta. - [ ] Verificación OK. - [ ] Liberación con único `free` del bloque de arena (sin frees individuales). -### Ejercicio 13.8 — (JEFE) Leer un archivo BMP pixel a pixel +### Ejercicio 14.8 — (JEFE) Leer un archivo BMP pixel a pixel **Conceptos** · Cabeceras binarias (`fread` de estructuras), little-endian, `fseek`, `ftell`, interpretación de píxeles, `malloc/free` y/o arenas (opcional), validación de formato. @@ -1261,12 +1262,12 @@ Salida: Archivo: imagen.bmp - Podés usar una estructura simple para el conteo de colores (p.ej. guardar los 8 colores más frecuentes o un arreglo de conteos). - Opción extra: repetir la lectura usando una **arena** para los buffers de píxeles. -- **Archivo de prueba ideal**: el BMP de 5×3 de la sección 13.8.6 (102 bytes, tres filas de +- **Archivo de prueba ideal**: el BMP de 5×3 de la sección 14.8.6 (102 bytes, tres filas de color sólido). Es tan chico que verificás todo a mano: `bfOffBits = 54`, `rowSize = 16` (15 bytes de color + 1 de relleno), **3 colores × 15 píxeles** y el píxel central (2, 1) debe dar `R=34 G=177 B=76`. El archivo está en `assets/img/ejemplo-bmp-5x3.bmp`. -- La documentación del formato está en la sección 13.8. Si querés mostrar la imagen, mirá la - sección 13.9 (SDL2, opcional). +- La documentación del formato está en la sección 14.8. Si querés mostrar la imagen, mirá la + sección 14.9 (SDL2, opcional). **Criterios de aceptación** - [ ] Valida la firma `BM` antes de continuar. @@ -1278,4 +1279,4 @@ Salida: Archivo: imagen.bmp - [ ] Maneja archivos inexistentes o corruptos con mensajes de error claros. - [ ] Libera memoria correctamente (con `free` o un solo `free` de la arena). - [ ] (Extra) Implementa la versión con arena y verifica que da el mismo resultado. -- [ ] (Extra) Muestra la imagen en una ventana con SDL2 (ver sección 13.9). +- [ ] (Extra) Muestra la imagen en una ventana con SDL2 (ver sección 14.9). diff --git a/book/15-tipos-stddef.md b/book/15-tipos-stddef.md index 2f69c5a..c1653ba 100644 --- a/book/15-tipos-stddef.md +++ b/book/15-tipos-stddef.md @@ -1,4 +1,4 @@ -# Capítulo 14 — Tipos más usados de C +# Capítulo 15 — Tipos más usados de C > Capítulo avanzado. Se apoya en tipos primitivos (`02`), operadores (`03`), arreglos (`06`), > punteros (`07`) y `struct` (`08`). @@ -9,7 +9,7 @@ --- -## 14.1 ¿Por qué no alcanza con un `int`? +## 15.1 ¿Por qué no alcanza con un `int`? Un `int` resuelve muchísimos casos, pero **no responde de forma portable** tres preguntas: @@ -53,7 +53,7 @@ datos** (valores en bits): --- -## 14.2 El mapa de los tipos +## 15.2 El mapa de los tipos Casi ningún tipo "con nombre" es palabra del lenguaje: son **typedefs** de una cabecera. Estos son los que vas a usar todo el tiempo (tamaño típico en un sistema de 64 bits): @@ -78,7 +78,7 @@ los que vas a usar todo el tiempo (tamaño típico en un sistema de 64 bits): --- -## 14.3 `size_t` — los tamaños +## 15.3 `size_t` — los tamaños **Cabecera:** `` @@ -101,7 +101,7 @@ uses en un `for` inverso (`for (size_t i = n - 1; i >= 0; i--)` es **infinito**) --- -## 14.4 `wchar_t` — caracteres anchos +## 15.4 `wchar_t` — caracteres anchos **Cabecera:** `` @@ -135,7 +135,7 @@ clave no es "agrandar" el tipo, sino **elegir el correcto**. **Se imprimen con ` --- -## 14.5 `NULL` — el puntero nulo +## 15.5 `NULL` — el puntero nulo **Cabecera:** `` @@ -148,7 +148,7 @@ conceptual. --- -## 14.6 `bool` — verdadero o falso +## 15.6 `bool` — verdadero o falso **Cabecera:** `` (C99; en C23, palabra clave) @@ -161,7 +161,7 @@ usarlo:** si necesitás más de dos estados, un `enum` expresa mejor la intenci --- -## 14.7 Enteros de ancho exacto +## 15.7 Enteros de ancho exacto **Cabecera:** `` @@ -205,7 +205,7 @@ printf("%llu\n", (unsigned long long)big); --- -## 14.8 ¿Tengo que dejar de usar `int`? +## 15.8 ¿Tengo que dejar de usar `int`? **No.** Estos tipos **no reemplazan** a `int`: lo **complementan** para casos puntuales. El `int` sigue siendo el tipo por defecto para números enteros comunes. @@ -223,7 +223,7 @@ Pasá a un **tipo preciso** solo cuando **exista un motivo**: - **`wchar_t` / `char32_t`** → un carácter que no entra en un `char`. > **Y ojo con el revés:** usar un tipo chico "por las dudas" también trae problemas. `int8_t` llega -> hasta 127 (ver la tabla de 13.7): si lo usás para contar, se desborda enseguida. Y encima la +> hasta 127 (ver la tabla de 15.7): si lo usás para contar, se desborda enseguida. Y encima la > aritmética se hace en `int` igual. ```c @@ -255,7 +255,7 @@ int c = a + b; /* 200: la suma se calcula en int, y al achicar se desborda */ --- -## 14.9 ¿Los tipos chicos ahorran memoria? +## 15.9 ¿Los tipos chicos ahorran memoria? **Depende de dónde.** Guardar **muchos** valores en tipos chicos sí ahorra memoria; una variable suelta, no. @@ -290,7 +290,7 @@ struct Color2 { int r, g, b; }; /* 12 bytes */ --- -## 14.10 Errores comunes +## 15.10 Errores comunes - Comparar un `int` negativo con `sizeof` o `strlen` (el `-1` se vuelve `SIZE_MAX`). - Guardar un tamaño (`sizeof`, tamaño de un archivo) en un `int`. @@ -301,14 +301,14 @@ struct Color2 { int r, g, b; }; /* 12 bytes */ --- -## 14.11 Ejercicios +## 15.11 Ejercicios > De **noob** a **pro**, con dificultad creciente. Cada ejercicio declara sus conceptos objetivo; > resolvelos **usando esos conceptos**. Trae un **escenario de prueba** (entrada/salida esperada, > nunca el algoritmo) para que compares lo que imprimís. Compilá con un estándar moderno (C11 o C23) > y avisos activos (`-Wall -Wextra`). -### Ejercicio 14.1 — Radiografía de tipos +### Ejercicio 15.1 — Radiografía de tipos **Conceptos** · `sizeof`, `size_t`, `printf` (`%zu`), tipos de `` y `` @@ -342,7 +342,7 @@ LP64 (Linux/macOS 64-bit) LLP64 (Windows 64-bit) --- -### Ejercicio 14.2 — Sumar con `size_t` +### Ejercicio 15.2 — Sumar con `size_t` **Conceptos** · `size_t`, arreglos, `for`, casos borde @@ -369,7 +369,7 @@ count = 1 ; [-5] -> -5 --- -### Ejercicio 14.3 — El índice que se convierte en gigante +### Ejercicio 15.3 — El índice que se convierte en gigante **Conceptos** · conversiones con/sin signo, `size_t`, comparaciones @@ -395,7 +395,7 @@ explicación: -1 convertido a size_t = SIZE_MAX (18446744073709551615), y SIZE_M --- -### Ejercicio 14.4 — Texto que no entra en un byte +### Ejercicio 15.4 — Texto que no entra en un byte **Conceptos** · `wchar_t`, `L""`, `%lc`, `%ls`, `` @@ -426,7 +426,7 @@ contraste: guardar 'ñ' en un char y usar %c da basura o un byte suelto. --- -### Ejercicio 14.5 — El entero de ancho exacto (pro) +### Ejercicio 15.5 — El entero de ancho exacto (pro) **Conceptos** · ``, tipos de ancho exacto, portabilidad @@ -457,7 +457,7 @@ total = 123456789012345ULL (uint64_t) -> 123456789012345 --- -## 14.12 Cierre +## 15.12 Cierre Los tipos de C no son un adorno: son la forma de decir **qué querés representar** y **de qué tamaño**. `size_t` para tamaños, `uintN_t`/`intN_t` para ancho exacto, `bool` para verdades, diff --git a/dist/13-entrada-salida-estandar.pdf b/dist/13-entrada-salida-estandar.pdf new file mode 100644 index 0000000..4a576b8 Binary files /dev/null and b/dist/13-entrada-salida-estandar.pdf differ diff --git a/dist/14-archivos.pdf b/dist/14-archivos.pdf index 5f81bde..6487bb9 100644 Binary files a/dist/14-archivos.pdf and b/dist/14-archivos.pdf differ diff --git a/dist/15-tipos-stddef.pdf b/dist/15-tipos-stddef.pdf index 1a330ae..3782c28 100644 Binary files a/dist/15-tipos-stddef.pdf and b/dist/15-tipos-stddef.pdf differ