Semana 2 — Variables y programas secuenciales
Objetivos
- Declarar variables con el tipo adecuado y evaluar expresiones.
- Explicar la división entera y la conversión entre tipos.
- Leer datos con
scanfy escribir resultados conprintf. - Pasar de una fórmula a un programa, primero en pseudocódigo y después en C.
Teoría — Variables, tipos, operadores, entrada y salida
Lectura: Apuntes, semana 2.
Temas:
- Variable: un nombre para un lugar en memoria. Asignar no es igualar:
x = x + 1. - Tipos
int,doubleychar. Cuántos bytes ocupan consizeof. - Operadores y precedencia. El resto con
%. División entera:7 / 2vale 3 y7.0 / 2vale 3.5. - Error común: usar una variable sin darle valor.
printfy sus formatos:%d,%f,%.2f,%c.scanf, el&y revisar lo que devuelve.- Funciones de
math.hy la opción-lm.
Laboratorio — Fórmulas en C
- Escribir un programa que lea una temperatura en Celsius y la escriba en Fahrenheit con dos decimales.
- Predecir y comprobar qué escriben
7 / 2,7 % 2,7.0 / 2y(double) 7 / 2. - Escribir un programa que lea un número de segundos y lo escriba como horas, minutos y segundos.
- Guardar datos en
entrada.txty correr./programa < entrada.txt > salida.txt. - Mostrar cuántos bytes ocupa cada tipo con
sizeof.
Preguntas de repaso
- Sigan a mano:
int a = 5; int b = a; a = a + 2; b = b * a;. ¿Cuánto valenayb? - ¿Por qué
double p = 1 / 3;guarda 0? ¿Cómo se corrige? - ¿Cuánto vale
2 + 3 * 4 % 5? Muestren el orden de las operaciones. - ¿Cómo detecta un programa que el usuario escribió
holacuandoscanf("%d", &n)espera un número? - Escriban en pseudocódigo y en C un programa que calcule el promedio de tres notas.
Código de la semana
Compilar y probar dentro del contenedor del curso con make y make test.
Ejemplos (30)Errores (1)Pruebas (2)Laboratorio (5)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
// Precedencia: primero * / %, después + -. A igual nivel, de izquierda a derecha.
#include <stdio.h>
int main(void) {
printf("10 - 2 * 3 = %d\n", 10 - 2 * 3);
printf("(10 - 2) * 3 = %d\n", (10 - 2) * 3);
printf("20 / 2 * 5 = %d\n", 20 / 2 * 5);
printf("10 - 4 - 3 = %d\n", 10 - 4 - 3);
printf("7 + 8 %% 3 * 2 = %d\n", 7 + 8 % 3 * 2);
return 0;
}// Una variable cambia de valor: la asignación no es una ecuación.
#include <stdio.h>
int main(void) {
int balance = 10; // saldo de la tarjeta, en soles
printf("Saldo inicial: %d\n", balance);
balance = balance - 3; // un pasaje
printf("Después del pasaje: %d\n", balance);
balance = balance + 5; // una recarga
printf("Después de la recarga: %d\n", balance);
return 0;
}// Vuelto con la menor cantidad de monedas, usando / y % en lugar de un ciclo.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int remaining = 0;
printf("Vuelto en céntimos: ");
scanf("%d", &remaining);
int coins_500 = remaining / 500;
remaining = remaining % 500;
int coins_200 = remaining / 200;
remaining = remaining % 200;
int coins_100 = remaining / 100;
remaining = remaining % 100;
int coins_50 = remaining / 50;
remaining = remaining % 50;
int coins_20 = remaining / 20;
remaining = remaining % 20;
int coins_10 = remaining / 10;
remaining = remaining % 10;
printf("5 soles: %d\n", coins_500);
printf("2 soles: %d\n", coins_200);
printf("1 sol: %d\n", coins_100);
printf("50 céntimos: %d\n", coins_50);
printf("20 céntimos: %d\n", coins_20);
printf("10 céntimos: %d\n", coins_10);
printf("Sin cubrir: %d céntimos\n", remaining);
return 0;
}// Un char guarda un número pequeño, el código de un carácter.
#include <stdio.h>
int main(void) {
char letter = 'A';
char digit = '7';
printf("%c tiene el código %d\n", letter, letter);
printf("La letra siguiente es %c\n", letter + 1);
printf("%c tiene el código %d\n", digit, digit);
printf("El valor numérico de %c es %d\n", digit, digit - '0');
return 0;
}// El espacio antes de %c salta los espacios y saltos de línea pendientes.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int quantity = 0;
char size = '?';
printf("Cantidad: ");
scanf("%d", &quantity);
printf("Talla: ");
scanf(" %c", &size);
printf("Leí %d y la talla %c\n", quantity, size);
return 0;
}// Tiene un error a propósito: %c también lee el salto de línea.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int quantity = 0;
char size = '?';
printf("Cantidad: ");
scanf("%d", &quantity);
printf("Talla: ");
scanf("%c", &size);
printf("Leí %d y un carácter de código %d\n", quantity, size);
return 0;
}// Interés compuesto: final = capital * (1 + tasa / 100) elevado a años.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <math.h>
#include <stdio.h>
int main(void) {
double capital = 0.0;
double rate = 0.0;
int years = 0;
printf("Capital en soles: ");
scanf("%lf", &capital);
printf("Tasa anual en %%: ");
scanf("%lf", &rate);
printf("Años: ");
scanf("%d", &years);
double final_amount = capital * pow(1 + rate / 100, years);
printf("Monto final: %.2f soles\n", final_amount);
return 0;
}// Conversiones entre int y double, automáticas y pedidas con un cast.
#include <stdio.h>
int main(void) {
int sum = 7;
int count = 2;
double late = sum / count; // divide enteros y luego convierte
double cast_first = (double) sum / count;
double cast_late = (double) (sum / count);
double from_int = count; // de int a double: 2.0
int truncated = 3.99; // de double a int: se pierden los decimales
int cast_positive = (int) 3.99;
int cast_negative = (int) -3.99;
printf("sum / count = %f\n", late);
printf("(double) sum / count = %f\n", cast_first);
printf("(double) (sum / count) = %f\n", cast_late);
printf("from_int = %f\n", from_int);
printf("truncated = %d\n", truncated);
printf("(int) 3.99 = %d\n", cast_positive);
printf("(int) -3.99 = %d\n", cast_negative);
return 0;
}// Diagonal de un rectángulo, con el teorema de Pitágoras.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <math.h>
#include <stdio.h>
int main(void) {
double width = 0.0;
double height = 0.0;
printf("Ancho y alto en metros: ");
scanf("%lf %lf", &width, &height);
double diagonal = sqrt(width * width + height * height);
printf("Diagonal: %.2f metros\n", diagonal);
return 0;
}// Dividir enteros entre cero es un error de ejecución.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int a = 0;
int b = 0;
printf("Dos enteros: ");
scanf("%d %d", &a, &b);
int quotient = a / b;
printf("Cociente: %d\n", quotient);
return 0;
}// Entre dos enteros, / da un entero. Si uno es double, da un double.
#include <stdio.h>
int main(void) {
printf("17 / 5 = %d\n", 17 / 5);
printf("17 %% 5 = %d\n", 17 % 5);
printf("17.0 / 5 = %f\n", 17.0 / 5);
printf("17 / 5.0 = %f\n", 17 / 5.0);
printf("-17 / 5 = %d\n", -17 / 5);
printf("-17 %% 5 = %d\n", -17 % 5);
printf("17 %% -5 = %d\n", 17 % -5);
return 0;
}// Con double, dividir entre cero no detiene el programa: da inf.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
double a = 0.0;
double b = 0.0;
printf("Dos números: ");
scanf("%lf %lf", &a, &b);
printf("Cociente: %f\n", a / b);
return 0;
}// Promedio final ponderado: (EP + 2 EF + PP) / 4.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int ep = 0;
int ef = 0;
int pp = 0;
printf("Examen parcial: ");
scanf("%d", &ep);
printf("Examen final: ");
scanf("%d", &ef);
printf("Promedio de prácticas: ");
scanf("%d", &pp);
double final_grade = (ep + 2 * ef + pp) / 4.0;
printf("Promedio final: %.2f\n", final_grade);
return 0;
}13 15 14// Precio con IGV. La tasa es una constante: el programa no puede cambiarla.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
const double IGV_RATE = 0.18;
double price = 0.0;
printf("Precio sin IGV: ");
scanf("%lf", &price);
double igv = price * IGV_RATE;
double total = price + igv;
printf("IGV: %.2f\n", igv);
printf("Total: %.2f\n", total);
return 0;
}// Lo que scanf no pudo leer se queda en la entrada, esperando a la siguiente lectura.
#include <stdio.h>
int main(void) {
// Revisar si la lectura funcionó requiere if: llega en la semana 4.
int number = 0;
char next = '?';
printf("Un entero: ");
int count = scanf("%d", &number);
scanf(" %c", &next);
printf("scanf devolvió %d\n", count);
printf("number = %d\n", number);
printf("Lo siguiente en la entrada es %c\n", next);
return 0;
}// Funciones de math.h. Reciben y devuelven double. Se compila con -lm.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <math.h>
#include <stdio.h>
int main(void) {
double x = 0.0;
printf("x: ");
scanf("%lf", &x);
printf("sqrt(x) = %f\n", sqrt(x));
printf("pow(x, 3) = %f\n", pow(x, 3));
printf("fabs(x) = %f\n", fabs(x));
printf("floor(x) = %f\n", floor(x));
printf("ceil(x) = %f\n", ceil(x));
printf("round(x) = %f\n", round(x));
return 0;
}// Una asignación guarda un valor una vez: no deja una fórmula viva.
#include <stdio.h>
int main(void) {
int price = 2;
int total = price * 3; // total recibe 6, no la fórmula
price = 10; // total no se entera de este cambio
printf("price = %d\n", price);
printf("total = %d\n", total);
return 0;
}// Los formatos de printf. Cada % se reemplaza por un valor, en orden.
#include <stdio.h>
int main(void) {
int grade = 15;
double height = 1.685;
char section = 'B';
printf("Nota: %d\n", grade);
printf("Estatura: %f\n", height);
printf("Estatura: %.2f\n", height);
printf("Sección: %c\n", section);
printf("Nota %d, sección %c\n", grade, section);
printf("Avance: %d%%\n", 80);
printf("Columna 1\tColumna 2\n");
printf("2.675 con dos decimales: %.2f\n", 2.675);
return 0;
}// scanf devuelve cuántos datos pudo guardar. Aquí solo lo mostramos:
// actuar según ese número necesita el if de la semana 4.
#include <stdio.h>
int main(void) {
int first = 0;
int second = 0;
printf("Dos enteros: ");
int count = scanf("%d %d", &first, &second);
printf("scanf devolvió %d\n", count);
printf("first = %d, second = %d\n", first, second);
return 0;
}// Primer programa que lee un dato del teclado.
#include <stdio.h>
int main(void) {
int grade = 0;
printf("Nota: ");
scanf("%d", &grade); // aún no revisamos lo que devuelve: ver read_check.c
printf("Leí la nota %d\n", grade);
printf("Con dos puntos más sería %d\n", grade + 2);
return 0;
}// Cuántos bytes ocupa cada tipo. sizeof da un resultado que se escribe con %zu.
#include <stdio.h>
int main(void) {
int age = 19;
printf("int: %zu bytes\n", sizeof(int));
printf("double: %zu bytes\n", sizeof(double));
printf("char: %zu bytes\n", sizeof(char));
printf("la variable age: %zu bytes\n", sizeof(age));
printf("age vale %d\n", age);
return 0;
}// Intercambiar dos variables necesita una tercera.
#include <stdio.h>
int main(void) {
int left = 4;
int right = 9;
int saved = left; // guardamos left antes de perderlo
left = right;
right = saved;
printf("left = %d, right = %d\n", left, right);
return 0;
}// Error a propósito: la ñ no cabe en un char.
#include <stdio.h>
int main(void) {
char letter = 'ñ';
printf("%c\n", letter);
return 0;
}// Error a propósito: falta el & en scanf.
#include <stdio.h>
int main(void) {
int grade = 0;
scanf("%d", grade);
printf("%d\n", grade);
return 0;
}// Error a propósito: % solo funciona con enteros.
#include <stdio.h>
int main(void) {
double x = 7.5;
printf("%f\n", x % 2);
return 0;
}// Error a propósito: %d con un valor double.
#include <stdio.h>
int main(void) {
double average = 14.25;
printf("%d\n", average);
return 0;
}// Error a propósito: para leer un double, scanf necesita %lf.
#include <stdio.h>
int main(void) {
double price = 0.0;
scanf("%f", &price);
printf("%.2f\n", price);
return 0;
}// Error a propósito: usa una variable sin darle valor.
#include <stdio.h>
int main(void) {
int total;
printf("total = %d\n", total);
return 0;
}// Error a propósito: con -std=c17, math.h no define M_PI.
#include <math.h>
#include <stdio.h>
int main(void) {
printf("%f\n", M_PI);
return 0;
}Errores
Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.
// Tiene un error a propósito: compila sin avisos, pero divide entre enteros.
// Aún no revisa lo que devuelve scanf: eso llega con el if, en la semana 4.
#include <stdio.h>
int main(void) {
int ep = 0;
int ef = 0;
int pp = 0;
printf("Examen parcial: ");
scanf("%d", &ep);
printf("Examen final: ");
scanf("%d", &ef);
printf("Promedio de prácticas: ");
scanf("%d", &pp);
double final_grade = (ep + 2 * ef + pp) / 4;
printf("Promedio final: %.2f\n", final_grade);
return 0;
}Pruebas
Comprueban lo que afirman los apuntes; se ejecutan con make test.
"""Pruebas de los ejemplos de los apuntes. Corren cada programa en una terminal de
verdad, comparan su salida y comprueban que los apuntes y las diapositivas muestran
exactamente lo que salió. Si cambia el código o la versión de gcc, estas pruebas lo
avisan."""
import os
import pty
import re
import select
import subprocess
import time
import unittest
from pathlib import Path
HERE = Path(__file__).resolve().parent
COURSE = HERE.parent.parent
NOTES = COURSE / "notes" / "week-02.md"
SLIDES = COURSE / "slides" / "week-02.tex"
GCC = "gcc -Wall -Wextra -std=c17"
def clean(text):
"""Quita colores y lo que cambia entre máquinas: nombres temporales y desplazamientos."""
text = re.sub(r"\x1b\[[0-9;]*m", "", text) # colores de la terminal
text = re.sub(r"/tmp/cc\w+\.o", "/tmp/cc.o", text)
return re.sub(r"\.text\+0x[0-9a-f]+", ".text", text)
def run_in_terminal(command, dialog=(), eof=False):
"""Ejecuta un comando en una terminal: `dialog` son pares (mensaje, respuesta),
y lo escrito aparece en la salida, igual que cuando lo escriben ustedes."""
master, slave = pty.openpty()
env = dict(os.environ, TERM="dumb", LANG="C.UTF-8")
process = subprocess.Popen(["sh", "-c", command], stdin=slave, stdout=slave,
stderr=slave, cwd=HERE, env=env)
os.close(slave)
output = b""
def read_until(done):
nonlocal output
limit = time.time() + 30
while not done():
if time.time() > limit:
raise TimeoutError(output.decode())
ready, _, _ = select.select([master], [], [], 0.05)
if ready:
try:
data = os.read(master, 4096)
except OSError: # el programa ya terminó
return
if not data:
return
output += data
for prompt, answer in dialog:
read_until(lambda: output.endswith(prompt.encode()))
os.write(master, answer.encode() + b"\n")
if eof:
read_until(lambda: output != b"")
os.write(master, b"\x04")
read_until(lambda: False)
process.wait()
os.close(master)
text = output.decode().replace("\r\n", "\n")
return clean(f"$ {command}\n{text}")
# nombre: (comando, diálogo, fin de entrada, lo que debe salir)
GRADES = [("Examen parcial: ", "13"), ("Examen final: ", "15"),
("Promedio de prácticas: ", "14")]
TWO = "Dos enteros: "
SESSIONS = {
"balance": ("build/balance", (), False, "Después de la recarga: 12\n"),
"not_a_formula": ("build/not_a_formula", (), False, "total = 6\n"),
"swap_values": ("build/swap_values", (), False, "left = 9, right = 4\n"),
"sizes": ("build/sizes", (), False,
"int: 4 bytes\ndouble: 8 bytes\nchar: 1 bytes\n"),
"arithmetic": ("build/arithmetic", (), False, "7 + 8 % 3 * 2 = 11\n"),
"division": ("build/division", (), False, "-17 % 5 = -2\n17 % -5 = 2\n"),
"conversions": ("build/conversions", (), False, "(int) -3.99 = -3\n"),
"char_codes": ("build/char_codes", (), False, "A tiene el código 65\n"),
"printf_formats": ("build/printf_formats", (), False,
"2.675 con dos decimales: 2.67\n"),
"read_grade": ("build/read_grade", [("Nota: ", "15")], False, "sería 17\n"),
"check_ok": ("build/read_check", [(TWO, "4 9")],
False, "scanf devolvió 2\n"),
"check_second": ("build/read_check", [(TWO, "4 hola")], False,
"scanf devolvió 1\nfirst = 4, second = 0\n"),
"check_first": ("build/read_check", [(TWO, "hola")], False,
"scanf devolvió 0\nfirst = 0, second = 0\n"),
"check_decimal": ("build/read_check", [(TWO, "3.7 5")], False,
"scanf devolvió 1\nfirst = 3, second = 0\n"),
"check_eof": ("build/read_check", [], True, "scanf devolvió -1\n"),
"leftover": ("build/leftover", [("Un entero: ", "hola")], False,
"Lo siguiente en la entrada es h\n"),
"char_trap": ("build/char_trap", [("Cantidad: ", "3")], False,
"Leí 3 y un carácter de código 10\n"),
"char_fixed": ("build/char_fixed", [("Cantidad: ", "3"), ("Talla: ", "M")], False,
"Leí 3 y la talla M\n"),
"final_grade": ("build/final_grade", GRADES, False, "Promedio final: 14.25\n"),
"final_grade_bug": ("build/final_grade_bug", GRADES, False,
"Promedio final: 14.00\n"),
"igv_hola": ("build/igv_price", [("Precio sin IGV: ", "hola")], False,
"IGV: 0.00\nTotal: 0.00\n"),
"igv_price": ("build/igv_price", [("Precio sin IGV: ", "50")], False,
"IGV: 9.00\nTotal: 59.00\n"),
"change_780": ("build/change", [("Vuelto en céntimos: ", "780")], False,
"Sin cubrir: 0 céntimos\n"),
"change_785": ("build/change", [("Vuelto en céntimos: ", "785")], False,
"Sin cubrir: 5 céntimos\n"),
"math_demo": ("build/math_demo", [("x: ", "2.5")], False,
"sqrt(x) = 1.581139\npow(x, 3) = 15.625000\nfabs(x) = 2.500000\n"
"floor(x) = 2.000000\nceil(x) = 3.000000\nround(x) = 3.000000\n"),
"diagonal": ("build/diagonal", [("Ancho y alto en metros: ", "3 4")], False,
"Diagonal: 5.00 metros\n"),
"compound_interest": ("build/compound_interest",
[("Capital en soles: ", "1000"), ("Tasa anual en %: ", "5"),
("Años: ", "3")], False, "Monto final: 1157.63 soles\n"),
"double_by_zero": ("build/double_by_zero", [("Dos números: ", "5 0")], False,
"Cociente: inf\n"),
"redirect_in": ("build/final_grade < grades.txt", (), False,
"Promedio final: 14.25\n"),
"redirect_out": ("build/final_grade < grades.txt > build/result.txt", (), False, ""),
"redirect_cat": ("cat build/result.txt", (), False, "Promedio final: 14.25\n"),
"uninitialized": (f"{GCC} broken/uninitialized.c -o build/uninitialized", (), False,
"is used uninitialized"),
"missing_ampersand": (f"{GCC} broken/missing_ampersand.c -o build/missing_ampersand",
(), False, "expects argument of type ‘int *’"),
"scanf_float": (f"{GCC} broken/scanf_float_format.c -o build/scanf_float_format",
(), False, "%lf\n"),
"printf_wrong": (f"{GCC} broken/printf_wrong_format.c -o build/printf_wrong_format",
(), False, "%f\n"),
"mod_double": (f"{GCC} broken/mod_double.c -o build/mod_double", (), False,
"invalid operands to binary %"),
"missing_lm": (f"{GCC} diagonal.c -o build/diagonal_no_lm", (), False,
"undefined reference to `sqrt'"),
}
# Los apuntes dan estos resultados en una frase o una tabla, sin la sesión completa.
ONLY_IN_PROSE = {"check_ok", "check_first", "check_decimal", "change_785", "char_fixed",
"mod_double", "double_by_zero", "printf_wrong", "math_demo",
"compound_interest"}
def first_part(transcript, marker):
"""Lo que viene después de `marker` depende del procesador: no se compara."""
end = transcript.index(marker)
return transcript[:transcript.index("\n", end) + 1]
def session_transcripts():
"""Corre todas las sesiones una vez, y las reutilizan las demás pruebas."""
subprocess.run(["mkdir", "-p", "build"], cwd=HERE, check=True)
result = {}
for name in ("redirect_in", "redirect_out", "redirect_cat"):
command, dialog, eof, _ = SESSIONS[name]
result[name] = run_in_terminal(command, dialog, eof)
for name, (command, dialog, eof, _) in SESSIONS.items():
if name not in result:
result[name] = run_in_terminal(command, dialog, eof)
# Por procesador cambian el resto de la salida y el segundo aviso de gcc.
result["division_by_zero"] = first_part(
run_in_terminal("build/division_by_zero", [(TWO, "5 0")]), "runtime error")
accent = run_in_terminal(f"{GCC} broken/accent_char.c -o build/accent_char")
result["accent_char"] = accent[:accent.index("broken/accent_char.c:5:19: warning: ",
accent.index("^~~"))]
return result
TRANSCRIPTS = session_transcripts()
class Sessions(unittest.TestCase):
def test_each_program_prints_what_the_notes_say(self):
for name, (_, _, _, expected) in SESSIONS.items():
with self.subTest(name):
self.assertIn(expected, TRANSCRIPTS[name])
def test_integer_division_by_zero_is_reported(self):
self.assertIn("division_by_zero.c:12:22: runtime error: division by zero\n",
TRANSCRIPTS["division_by_zero"])
def test_accent_char_warns_about_multichar(self):
self.assertIn("multi-character character constant", TRANSCRIPTS["accent_char"])
class Compilation(unittest.TestCase):
def compile(self, source, flags=("-Wall", "-Wextra")):
return subprocess.run(["gcc", *flags, "-std=c17", "-c", str(source),
"-o", "/dev/null"], capture_output=True, text=True, cwd=HERE)
def test_programs_have_no_warnings(self):
sources = [*HERE.glob("*.c"), *HERE.glob("lab/*.c"), *HERE.glob("solution/*.c")]
self.assertTrue(sources)
for source in sources:
with self.subTest(source.name):
self.assertEqual(self.compile(source).stderr, "")
def test_broken_programs_do_warn(self):
for source in HERE.glob("broken/*.c"):
with self.subTest(source.name):
self.assertRegex(self.compile(source).stderr, "warning:|error:")
def test_without_wall_gcc_says_nothing_about_uninitialized(self):
self.assertEqual(self.compile("broken/uninitialized.c", ()).stderr, "")
def test_sanitizers_do_not_detect_uninitialized(self):
subprocess.run(f"{GCC} -w -fsanitize=address,undefined broken/uninitialized.c "
"-o build/uninitialized_san", shell=True, cwd=HERE, check=True)
result = subprocess.run(["build/uninitialized_san"], cwd=HERE, capture_output=True)
self.assertEqual(result.stderr, b"")
def test_m_pi_is_not_available_with_c17(self):
self.assertIn("M_PI", self.compile("broken/uses_m_pi.c").stderr)
def test_assignment_to_const_and_expression_do_not_compile(self):
source = HERE / "build" / "const_check.c"
for body in ("const double RATE = 0.18; RATE = 0.2; return (int) RATE;",
"int b = 1; b - 3 = b; return b;",
"int b = 1; int b = 7; return b;"):
with self.subTest(body):
source.write_text(f"int main(void) {{ {body} }}\n", encoding="utf-8")
self.assertIn("error:", self.compile(source).stderr)
class Crash(unittest.TestCase):
def test_missing_ampersand_crashes_at_run_time(self):
subprocess.run(f"{GCC} -w broken/missing_ampersand.c -o build/missing_ampersand",
shell=True, cwd=HERE, check=True)
result = subprocess.run(["build/missing_ampersand"], input=b"15\n", cwd=HERE,
capture_output=True)
self.assertNotEqual(result.returncode, 0)
@unittest.skipUnless(NOTES.exists(), "no están los apuntes")
class Notes(unittest.TestCase):
def setUp(self):
self.notes = clean(NOTES.read_text(encoding="utf-8"))
def test_notes_show_real_output(self):
for name, transcript in TRANSCRIPTS.items():
if name in ONLY_IN_PROSE:
continue
with self.subTest(name):
self.assertIn(transcript, self.notes)
def test_terminal_blocks_only_show_real_output(self):
for block in re.findall(r"```console\n(.*?)```", self.notes, re.S):
real = [t for t in TRANSCRIPTS.values() if t in block]
rest = block
for transcript in sorted(real, key=len, reverse=True):
rest = rest.replace(transcript, "")
with self.subTest(block[:50]):
self.assertEqual(rest.strip(), "")
def test_programs_in_notes_are_the_real_files(self):
files = {p.read_text(encoding="utf-8") for p in
[*HERE.glob("*.c"), *HERE.glob("broken/*.c")]}
blocks = re.findall(r"^```c\n(.*?)```", self.notes, re.S | re.M)
complete = [b for b in blocks if "int main" in b]
self.assertTrue(complete)
for block in complete:
with self.subTest(block[:50]):
self.assertIn(block, files)
@unittest.skipUnless(SLIDES.exists(), "no están las diapositivas")
class Slides(unittest.TestCase):
def test_terminal_lines_are_real(self):
tex = SLIDES.read_text(encoding="utf-8")
real = {line for t in TRANSCRIPTS.values() for line in t.split("\n")}
for env in ("terminal", "terminalancha"):
for block in re.findall(rf"\\begin\{{{env}\}}(?:\[.*?\])?\n(.*?)\\end\{{{env}\}}",
tex, re.S):
for line in clean(block).strip("\n").split("\n"):
with self.subTest(line):
self.assertIn(line, real)
if __name__ == "__main__":
unittest.main()"""Pruebas del laboratorio. Corren sobre lab/ con `make test-lab`, y sobre
solution/ dentro de `make test`, si esa carpeta existe."""
import os
import subprocess
import unittest
from pathlib import Path
HERE = Path(__file__).resolve().parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
FOLDER = HERE / LAB_DIR
BIN = HERE / "build" / LAB_DIR
def run(name, stdin=""):
result = subprocess.run([str(BIN / name)], input=stdin, capture_output=True,
text=True, timeout=20)
return result.stdout
@unittest.skipUnless(FOLDER.is_dir(), f"no existe {LAB_DIR}/")
class Lab(unittest.TestCase):
def test_1_celsius(self):
# 37 descubre la división entera: 9 / 5 daría 1 y el resultado sería 69.00.
cases = {"100": "212.00", "0": "32.00", "-40": "-40.00", "37": "98.60",
"36.6": "97.88"}
for given, expected in cases.items():
with self.subTest(celsius=given):
self.assertEqual(run("celsius", given + "\n"), expected + "\n")
def test_2_predict_program(self):
self.assertEqual(run("predict"),
"7 / 2 = 3\n7 % 2 = 1\n7.0 / 2 = 3.5\n(double) 7 / 2 = 3.5\n")
def test_2_predictions_match_the_program(self):
predictions = (FOLDER / "predictions.txt").read_text(encoding="utf-8")
self.assertNotIn("?", predictions, "falta completar predictions.txt")
self.assertEqual(predictions, run("predict"))
def test_3_time_split(self):
cases = {"3725": "1 h 2 min 5 s", "0": "0 h 0 min 0 s", "59": "0 h 0 min 59 s",
"3600": "1 h 0 min 0 s", "86399": "23 h 59 min 59 s",
"90061": "25 h 1 min 1 s"}
for given, expected in cases.items():
with self.subTest(seconds=given):
self.assertEqual(run("time_split", given + "\n"), expected + "\n")
def test_4_redirect(self):
entrada = FOLDER / "entrada.txt"
salida = FOLDER / "salida.txt"
self.assertTrue(entrada.exists(), f"falta {LAB_DIR}/entrada.txt")
self.assertTrue(salida.exists(), f"falta {LAB_DIR}/salida.txt")
self.assertEqual(salida.read_text(encoding="utf-8"),
run("time_split", entrada.read_text(encoding="utf-8")))
def test_5_type_sizes(self):
self.assertIn("sizeof", (FOLDER / "type_sizes.c").read_text(encoding="utf-8"))
self.assertEqual(run("type_sizes"),
"int: 4 bytes\ndouble: 8 bytes\nchar: 1 bytes\n")
if __name__ == "__main__":
unittest.main()Laboratorio
Archivos de partida del laboratorio de la semana.
// Laboratorio 2, actividad 1: de Celsius a Fahrenheit.
//
// Lee un número con decimales y escribe solo el resultado, con dos decimales y
// un salto de línea. No escriban un mensaje pidiendo el dato.
// Ejemplo: si la entrada es 100, la salida es 212.00
//
// Fórmula: F = C * 9 / 5 + 32. Antes de escribirla en C, escríbanla en
// pseudocódigo y síganla a mano con 37. Cuidado con la división entera.
#include <stdio.h>
int main(void) {
// Completar aquí.
return 0;
}// Laboratorio 2, actividad 2: predecir y comprobar.
//
// 1. Antes de ejecutar nada, completen predictions.txt con lo que creen que
// vale cada expresión.
// 2. Agreguen aquí las tres líneas que faltan, con el mismo estilo.
// Usen %d para un resultado int y %.1f para un resultado double.
// 3. Ejecuten el programa y comparen. Si fallaron alguna predicción, déjenla
// corregida y anoten en un comentario qué pensaban y por qué no era así.
#include <stdio.h>
int main(void) {
printf("7 / 2 = %d\n", 7 / 2);
// Faltan 7 % 2, 7.0 / 2 y (double) 7 / 2, en ese orden.
return 0;
}7 / 2 = ?
7 % 2 = ?
7.0 / 2 = ?
(double) 7 / 2 = ?// Laboratorio 2, actividad 3: de segundos a horas, minutos y segundos.
//
// Lee un número entero de segundos y escribe una línea con este formato exacto.
// No escriban un mensaje pidiendo el dato.
// Ejemplo: si la entrada es 3725, la salida es
// 1 h 2 min 5 s
//
// Actividad 4: creen entrada.txt en esta carpeta, con un número de segundos, y
// desde code/week-02 ejecuten:
// build/lab/time_split < lab/entrada.txt > lab/salida.txt
#include <stdio.h>
int main(void) {
// Completar aquí.
return 0;
}// Laboratorio 2, actividad 5: cuántos bytes ocupa cada tipo.
//
// Escriban una línea por tipo, usando sizeof, con este formato exacto:
// int: ... bytes
// double: ... bytes
// char: ... bytes
#include <stdio.h>
int main(void) {
// Completar aquí.
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 2: variables y programas secuenciales. Se usa dentro del entorno del
# curso (Codespaces o env/run.sh).
#
# make compila los ejemplos de los apuntes en build/
# make test compila y prueba los ejemplos, y la solución si existe solution/
# make test-lab prueba su laboratorio (lab/); falla hasta que lo terminen
# make clean borra lo generado
#
# Los programas de broken/ tienen errores a propósito: make no los compila.
# Los apuntes muestran qué dice gcc sobre cada uno.
CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
LDLIBS = -lm
BUILD = build
PROGRAMS = balance not_a_formula swap_values sizes arithmetic division \
conversions char_codes printf_formats read_grade final_grade \
final_grade_bug read_check leftover char_trap char_fixed \
igv_price change math_demo diagonal compound_interest \
division_by_zero double_by_zero
LAB_PROGRAMS = celsius predict time_split type_sizes
.PHONY: all lab test test-lab clean
all: $(PROGRAMS:%=$(BUILD)/%)
$(BUILD)/%: %.c | $(BUILD)
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD)/lab/%: lab/%.c | $(BUILD)/lab
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD)/solution/%: solution/%.c | $(BUILD)/solution
$(CC) $(CFLAGS) $< -o $@ $(LDLIBS)
$(BUILD) $(BUILD)/lab $(BUILD)/solution:
mkdir -p $@
lab: $(LAB_PROGRAMS:%=$(BUILD)/lab/%)
# Si existe solution/, también se comprueba la solución del laboratorio.
test: all
@if [ -d solution ]; then $(MAKE) --no-print-directory \
$(LAB_PROGRAMS:%=$(BUILD)/solution/%); fi
python3 -m unittest -v test_examples test_lab
test-lab: all lab
LAB_DIR=lab python3 -m unittest -v test_lab
clean:
rm -rf $(BUILD) __pycache__