En esta página
42 Piscine — Rush 00 (explicado desde cero)
Vas a hacer que un programa de C dibuje un rectángulo en la pantalla:
o---o
| |
o---o
Ese dibujo lo produce rush(5, 3): 5 caracteres de ancho y 3 de alto.
Este documento explica cada línea de los tres archivos, asumiendo que
llevas un día programando. Si algo no se entiende, es culpa de este README,
no tuya.
Hecho con amor para Laura, por Bogdan.
¿Acabas de empezar?
CONCEPTOS.mdexplica, por separado, qué es una variable, una función, unreturntemprano, un bucle, una matriz, y qué hacegcccon sus flags. Este README asume que ya leíste eso.
Qué tienes que entregar
| Dato | Valor |
|---|---|
| Carpeta | ex00/ |
| Archivos | main.c, ft_putchar.c, rush00.c |
| Funciones de librería permitidas | solo write |
| Función obligatoria | void rush(int x, int y) |
Tres archivos, no más. El enunciado dice que ft_putchar.c debe contener
únicamente la función ft_putchar, así que no agregues nada ahí.
Modelo mental: qué hace un compilador
Antes de leer código, necesitas una imagen mental. Programar en C es escribir
un archivo de texto (.c) con instrucciones que una persona puede leer.
El ordenador no ejecuta ese texto: primero hay que traducirlo.
rush00.c ──┐
main.c ──┼──► gcc ──► rush00 (programa ejecutable)
ft_putchar.c┘
Tres ideas que van a aparecer todo el tiempo:
- Compilar: traducir cada
.ca instrucciones de máquina. - Enlazar (link): unir las piezas ya compiladas en un solo ejecutable.
- Ejecutar: correr ese ejecutable.
Si te olvidas de un archivo en el comando de gcc, el enlazador se queja de
que no encuentra una función. Es el error más común y lo vas a ver más abajo.
El problema: filas, columnas y bordes
rush recibe dos números enteros:
x= ancho, cuántos caracteres entran en una fila.y= alto, cuántas filas hay.
Si x = 5 e y = 3, la salida es una grilla de 5 columnas por 3 filas:
columna: 0 1 2 3 4
┌───┬───┬───┬───┬───┐
fila 0 │ o │ - │ - │ - │ o │
├───┼───┼───┼───┼───┤
fila 1 │ | │ │ │ │ | │
├───┼───┼───┼───┼───┤
fila 2 │ o │ - │ - │ - │ o │
└───┴───┴───┴───┴───┘
Las reglas son tres:
| Posición | Carácter |
|---|---|
| Esquina (primera/última fila y primera/última columna) | o |
| Borde de arriba o de abajo (sin ser esquina) | - |
| Borde izquierdo o derecho (sin ser esquina) | | |
| Todo lo de adentro | espacio ' ' |
Y algo que confunde a todos el primer día: los índices empiezan en 0.
Si hay 5 columnas, sus índices son 0, 1, 2, 3, 4. La última columna es
x - 1, no x. Por eso en el código vas a ver width - 1 todo el tiempo.
Archivo 1: ft_putchar.c
#include <unistd.h>
/*
** Escribe un solo caracter en la salida estandar (file descriptor 1).
** Es la unica funcion permitida por el enunciado para mostrar texto.
*/
void ft_putchar(char c)
{
write(1, &c, 1);
}
#include <unistd.h>
Una línea que empieza con # no es C normal: es una orden para el
preprocesador, un paso previo que copia y pega cosas antes de compilar.
unistd.h es un archivo del sistema ("Unix standard") que contiene la
declaración de write. Es como el índice de un libro: le dice al
compilador "existe una función llamada write que recibe estos parámetros".
Sin esa línea, el compilador no sabe qué es write y con -Werror el
programa ni compila.
void ft_putchar(char c)
Esta línea se lee así, de izquierda a derecha:
| Parte | Significado |
|---|---|
void | la función no devuelve ningún valor |
ft_putchar | el nombre (la ft_ es convención de 42) |
(char c) | recibe un parámetro llamado c, de tipo char |
char es el tipo de dato más chico: guarda un carácter. Cuando main
llama a ft_putchar('o'), la función recibe una copia de ese carácter dentro
de una variable local llamada c. Esa variable vive en una cajita de memoria
que existe solo mientras la función se está ejecutando.
write(1, &c, 1);
write es una función del sistema operativo que escribe bytes en un
descriptor de archivo. Tiene tres parámetros:
| Parámetro | Qué significa en este caso |
|---|---|
1 | dónde escribir. 1 = salida estándar (la pantalla / la terminal) |
&c | qué escribir: la dirección de memoria donde está el carácter |
1 | cuántos bytes escribir: uno solo |
Dos detalles que valen oro:
&significa "la dirección de".writeno recibe el valor del carácter, recibe la ubicación de la cajita donde está guardado. Por eso se escribe&cy noc. Si pasasc, el compilador te va a marcar error de tipos (y con razón).writedevuelve cuántos bytes pudo escribir. Aquí ese número no nos interesa y se descarta. Eso es normal: no todo lo que devuelve una función se usa.
Otros descriptores que existen: 0 es la entrada (teclado), 2 es la salida
de errores. 1 es la de siempre.
Nota de estilo: la indentación es con tabs, no con espacios. Es la Norm de 42 y el corrector lo revisa.
Archivo 2: rush00.c
Este archivo tiene una función pública (rush) y dos funciones auxiliares.
Lo leemos de abajo hacia arriba: primero las piezas chicas, después el que las
usa.
La declaración de ft_putchar
void ft_putchar(char c);
Esto es un prototipo: la firma de una función que está en otro archivo.
¿Por qué hace falta? Porque el compilador procesa rush00.c sin ver
ft_putchar.c. El prototipo le avisa "tranquilo, esta función existe en algún
lado, y recibe un char". Gracias a eso puede verificar que la llames bien.
Si lo borras, el compilador asume que existe y te tira un warning de
implicit declaration, que con -Werror se convierte en error.
Fíjate que termina en ;: es una declaración, no una definición. No lleva
cuerpo, no ejecuta nada.
print_horizontal_edge: dibujar un borde
static void print_horizontal_edge(int width)
{
int column;
column = 0;
while (column < width)
{
if (column == 0 || column == width - 1)
ft_putchar('o');
else
ft_putchar('-');
column++;
}
ft_putchar('\n');
}
¿Qué significa static?
Una función static solo existe dentro de este archivo. Es una función
privada. ¿Por qué querrías eso?
- Comunica la intención: "esto es un ayudante interno, no lo uses desde afuera".
- Evita choques de nombres si otro archivo define una función igual.
No es obligatorio para aprobar, pero es buena práctica y es el equivalente en C a marcar un método como privado.
Paso a paso
int column; — declaramos una variable de tipo int (entero). La
declaramos arriba del bloque porque el estilo de 42 (basado en C89) pide todas
las declaraciones al principio.
column = 0; — le damos un valor inicial. Ojo: la declaración por sí sola
no inicializa nada. Si te olvidas de esta línea, column contiene basura
y el while puede comportarse de forma impredecible.
while (column < width) — un bucle. La condición se evalúa antes de
cada vuelta:
"Mientras
columnsea menor quewidth, repite todo lo que está entre llaves."
Con width = 5, el cuerpo se ejecuta con column = 0, 1, 2, 3, 4. Cuando
column llega a 5, la condición 5 < 5 es falsa y el bucle termina.
if (column == 0 || column == width - 1) — una decisión.
==compara (devuelve verdadero o falso). No es lo mismo que=, que asigna. Confundirlos es el error número uno de todo principiante.||significa o. La condición es verdadera si el lado izquierdo o el derecho lo son.column == 0→ ¿es la primera columna?column == width - 1→ ¿es la última columna? (width - 1es una resta de enteros: conwidth = 5da4.)
Si es primera o última columna escribimos 'o'; si no, '-'.
'o'(comillas simples) es un carácter."o"(comillas dobles) es un string, que en C ocupa dos bytes (laoy un terminador invisible).ft_putcharespera unchar, así que siempre comillas simples.
column++; — column++ es la forma corta de column = column + 1;.
Está dentro del while, al final del cuerpo. Es lo que hace avanzar el
bucle. Si te olvidas de esta línea, el programa nunca termina: column
queda en 0 para siempre y la condición siempre es verdadera. El enunciado
prohíbe explícitamente el bucle infinito, así que revisa esto.
ft_putchar('\n'); — después de completar la fila, escribimos un salto de
línea. '\n' es un solo carácter (el salto de línea), no dos. Sin esto,
todas las filas saldrían pegadas en la misma línea:
o---o| |o---o
Traza de print_horizontal_edge(5)
| Vuelta | column | column == 0 | column == 4 | ¿Qué imprime? | Salida acumulada |
|---|---|---|---|---|---|
| 1 | 0 | sí | no se evalúa | o | o |
| 2 | 1 | no | no | - | o- |
| 3 | 2 | no | no | - | o-- |
| 4 | 3 | no | no | - | o--- |
| 5 | 4 | no | sí | o | o---o |
| — | 5 | el bucle termina (5 < 5 es falso) | \n | o---o\n |
print_vertical_edge: dibujar una fila del medio
static void print_vertical_edge(int width)
{
int column;
column = 0;
while (column < width)
{
if (column == 0 || column == width - 1)
ft_putchar('|');
else
ft_putchar(' ');
column++;
}
ft_putchar('\n');
}
Es la misma estructura que la anterior. Solo cambian dos cosas: los bordes son
'|' y el relleno es un espacio ' ' (que también es un carácter, y para
el ordenador es tan real como una letra).
Con width = 5 imprime: |, espacio, espacio, espacio, |, salto de línea.
Eso es exactamente | |.
rush: el director de orquesta
void rush(int x, int y)
{
int row;
if (x <= 0 || y <= 0)
return ;
row = 0;
while (row < y)
{
if (row == 0 || row == y - 1)
print_horizontal_edge(x);
else
print_vertical_edge(x);
row++;
}
}
void rush(int x, int y) — la función que pide el enunciado, con esa firma
exacta. Recibe ancho (x) y alto (y).
if (x <= 0 || y <= 0) + return ; — esto se llama guard clause
(cláusula de guardia): resolver el caso inválido primero y salir.
<=significa "menor o igual". Cubre el cero y los negativos con una sola comparación.return ;sin valor corta la función en el acto. Comorushesvoid, no hay nada que devolver.- Sin esta guardia, con
y = 0elwhileno entraría (bien), pero cony = -3la lógica de "primera o última fila" se vuelve absurda y podrías entrar en un bucle raro. Además, el enunciado aclara que no se debe fallar con negativos. Aquí elegimos no dibujar nada; está permitido siempre que lo sepas explicar en la defensa.
row = 0; y while (row < y) — recorremos las filas, de la 0 a la
y - 1. Si y = 3, son las filas 0, 1, 2.
if (row == 0 || row == y - 1) — la pregunta clave de todo el ejercicio:
¿Esta fila es la primera o la última?
- Si sí → es un borde horizontal →
print_horizontal_edge(x). - Si no → es una fila del medio →
print_vertical_edge(x).
Fíjate que rush no sabe cómo se dibuja un borde. No usa 'o' ni '-'.
Solo decide qué fila toca y delega el cómo. Esa separación es la que hace
el código fácil de leer.
row++; — avanzamos a la siguiente fila. Mismo aviso que antes: sin esto,
bucle infinito.
Traza completa: qué pasa con rush(5, 3)
| Vuelta | row | row == 0 | row == 2 | Función llamada | Qué imprime |
|---|---|---|---|---|---|
| 1 | 0 | sí | no se evalúa | print_horizontal_edge(5) | o---o\n |
| 2 | 1 | no | no | print_vertical_edge(5) | | |\n |
| 3 | 2 | no | sí | print_horizontal_edge(5) | o---o\n |
| — | 3 | termina (3 < 3 es falso) |
Resultado en pantalla:
o---o
| |
o---o
Nada mágico: tres llamadas a funciones auxiliares, una por fila.
Casos límite
El enunciado pide admitir cualquier entero, incluidos cero y negativos.
| Caso | Qué pasa | Por qué funciona sola |
|---|---|---|
x = 0 | no imprime nada | la guardia corta antes de dibujar |
y = 0 | no imprime nada | idem |
x o y negativos | no imprime nada | <= 0 los atrapa a todos |
x = 1, y = 5 | una columna: o, |, |, |, o | con width = 1, width - 1 = 0, así que la misma columna es primera y última |
y = 1 | una sola fila: o---o | en la vuelta row = 0 se cumple row == 0 y row == y - 1; se dibuja un solo borde |
x = 2 | oo arriba y abajo, || al medio | no queda lugar para - ni para espacios; es correcto |
El caso x = 1 es el más lindo: no necesita ningún if extra. La
condición column == 0 || column == width - 1 ya contempla que ambos lados
sean el mismo índice. Por eso rush(1, 1) imprime un único o.
Archivo 3: main.c
/* Prototipo de la funcion que vive en rush00.c. */
void rush(int x, int y);
int main(void)
{
rush(5, 3);
return (0);
}
int main(void) — main es el punto de entrada: por aquí arranca
siempre el programa, lo llame quien lo llame. Devuelve un int al sistema
operativo: 0 significa "terminó bien". Cualquier otro número indica error.
void entre paréntesis significa "no recibe argumentos" (no confundir con el
void de ft_putchar, que ahí significaba "no devuelve nada"). Sí, C reusa
la misma palabra para dos cosas distintas. Bienvenido.
rush(5, 3); — una llamada a función. 5 y 3 son los argumentos;
x e y son los parámetros que los reciben. Es el ejemplo del enunciado.
Durante la defensa, el corrector va a reemplazar estos números por otros
(incluso negativos) para ver si tu función aguanta. Por eso main.c es tan
corto: no es el ejercicio, es solo el botón de arranque.
Compilar y ejecutar
cd ex00
gcc -Wall -Wextra -Werror main.c ft_putchar.c rush00.c -o rush00
./rush00
Qué hace cada parte
| Parte | Significado |
|---|---|
gcc | el compilador de C |
main.c ft_putchar.c rush00.c | los tres archivos; si falta uno, el enlazador falla |
-Wall | muestra los warnings más comunes |
-Wextra | muestra warnings adicionales |
-Werror | convierte cada warning en error: si algo está mal, no compila |
-o rush00 | nombre del ejecutable de salida |
-Werror parece cruel, y lo es a propósito: 42 no acepta código que compile
"con advertencias". Mejor que el error te lo diga el compilador y no el
corrector.
¿Qué es
gccy por qué esos flags? Está explicado a fondo, con las cuatro etapas de la compilación y un flag por flag, enCONCEPTOS.md.
Ejecutar
./rush00
El ./ significa "busca el ejecutable en el directorio actual". No es
decoración: por seguridad, el directorio actual no está en el PATH, así que
escribir solo rush00 no lo encuentra.
Deberías ver:
o---o
| |
o---o
Probar otros tamaños
Editá el main.c, cambia los números y vuelve a compilar:
rush(4, 4);
o--o
| |
| |
o--o
rush(1, 5);
o
|
|
|
o
No hace falta adivinar: compila, mira la salida y compárala con el enunciado.
Errores típicos del primer día
| Mensaje / síntoma | Causa | Solución |
|---|---|---|
implicit declaration of function 'write' | falta #include <unistd.h> | agrégalo arriba |
implicit declaration of function 'ft_putchar' | falta el prototipo en rush00.c o main.c | agrega void ft_putchar(char c); |
undefined reference to 'ft_putchar' | no incluiste ft_putchar.c en el comando de gcc | pasa los tres archivos |
expected ';' before ... | falta un punto y coma | busca la línea anterior a la que marca |
| El programa no termina nunca | te olvidaste column++; o row++; | agrégalo dentro del while |
| Todo sale en una sola línea | te olvidaste ft_putchar('\n'); | agrégalo al final de cada fila |
warning: unused parameter | declaraste un parámetro que no usas | úsalo o quita el nombre |
| No aparece nada y antes andaba | x o y en 0/negativos | es el comportamiento esperado |
permission denied al ejecutar | falta el ./ | ./rush00 |
Por qué el código está ordenado así (SOLID)
SOLID es un conjunto de principios pensados para diseño orientado a objetos: hablan de clases, interfaces y herencia. En C de piscina no hay clases, así que no aplica entero. Lo que sí se traslada, y es lo que usamos aquí:
| Principio | Cómo se ve en este código |
|---|---|
| S — Single Responsibility | print_horizontal_edge dibuja bordes; print_vertical_edge dibuja el medio; rush decide el orden. Cada una hace una cosa. |
| O — Open/Closed | Para otro dibujo (por ejemplo un triángulo) agregas funciones nuevas; no reescribes rush. |
| L / I / D | No tienen equivalente útil en un ejercicio de 40 líneas. Forzarlos sería sobreingeniería. |
La regla práctica que importa: una función, una tarea, un nombre honesto. Si una función necesita la palabra "y" para describirse ("dibuja el borde y decide la fila"), son dos funciones.
Lo que no hay que hacer aquí: crear structs, punteros a función o un
sistema de "renderers" para dibujar un rectángulo. La Norm de 42 no premia la
complejidad: premia que se entienda.
Glosario
| Término | Significado |
|---|---|
| compilador | programa que traduce C a instrucciones de máquina (gcc) |
| enlazador (linker) | une los archivos ya compilados en un ejecutable |
| preprocesador | paso previo que procesa los #include |
| prototipo | la firma de una función, sin su cuerpo |
| parámetro / argumento | la variable que recibe la función / el valor que le pasas |
| bloque | todo lo que está entre { y } |
| iteración | una vuelta de un bucle |
| descriptor de archivo | número que identifica dónde leer/escribir (1 = pantalla) |
| guard clause | resolver el caso inválido al principio y salir con return |
| índice | posición de un elemento, empezando en 0 |
| bucle infinito | un while cuya condición nunca se vuelve falsa |
| Norm | reglas de estilo de 42 (tabs, nombres, cantidad de líneas) |
Siguiente paso
Compila, cambia los números del main.c y predice la salida antes de
ejecutar. Si aciertas los cinco ejemplos del enunciado, entendiste el
ejercicio. Si no, vuelve a la traza de rush(5, 3): ahí está todo.
Después del rush: el módulo C 00
El rectángulo es el Rush 00, pero el módulo C 00 de la piscina tiene nueve ejercicios. Cada uno tiene su apartado propio, con el código explicado línea por línea, la salida exacta y los errores típicos:
| Ejercicio | Qué practica |
|---|---|
| 00 · ft_putchar | write, direcciones y char |
| 01 · ft_print_alphabet | un bucle y la consecutividad de ASCII |
| 02 · ft_print_reverse_alphabet | el mismo bucle al revés |
| 03 · ft_print_numbers | convertir número en carácter |
| 04 · ft_is_negative | decisiones y el caso del cero |
| 05 · ft_print_comb | tres bucles y separadores |
| 06 · ft_print_comb2 | dos cifras con / y % |
| 07 · ft_putnbr | recursión e INT_MIN |
| 08 · ft_print_combn | arrays, topes y recursión |
Los nueve se comprueban de una vez con:
./scripts/check-exercises.sh
Compila cada carpeta con -Wall -Wextra -Werror, ejecuta el resultado con un
main temporal y compara la salida byte a byte. Si algo falla, imprime el
primer byte distinto.