Semana 1 — De C a C++

Objetivos

  • Explicar de qué trata el curso: elegir cómo organizar los datos según lo que el programa necesita hacer.
  • Traducir lo que ya saben de C a C++: referencias, clases y métodos.
  • Usar std::string y std::vector sin manejar la memoria a mano.
  • Compilar con g++ -std=c++20 y escribir pruebas con assert.

Teoría — Presentación · De C a C++: referencias, clases, std::string, std::vector y pruebas

Lectura: Apuntes, semana 1.

Apoyo: Open Data Structures, secs. 1.1 (The Need for Efficiency) y 1.2 (Interfaces).

Temas:

  • De qué trata el curso. Un mismo programa puede tardar un segundo o un día según cómo guarde sus datos.
  • Lo que no cambia desde C: tipos, control, funciones, punteros, compilar y enlazar.
  • Referencias: int& y const std::string&. En qué se parecen a un puntero y en qué no. Cuándo pasar por valor y cuándo por referencia constante.
  • De struct con funciones sueltas a clase: campos privados, métodos, constructor y métodos const. El tipo opaco de C ahora lo da private.
  • std::string y std::vector como primeros usuarios de lo que construiremos. for por rango y auto.
  • Namespaces. Por qué en este curso se escribe std:: y no using namespace std.
  • Pruebas con assert y compilación con -Wall -Wextra y sanitizers.
  • Error común: copiar sin querer un std::vector grande al pasarlo por valor.

Laboratorio — Portar un módulo de C a C++

Sesión 1:

  1. Abrir el entorno y correr env/check.sh. Compilar y correr un programa corto en C++ con el Makefile dado.
  2. Leer un módulo de C dado, con tipo opaco y su encabezado: un registro de notas de estudiantes.
  3. Portarlo a una clase GradeBook: std::string en vez de char *, std::vector en vez de malloc, métodos en vez de funciones con prefijo.

Sesión 2: 4. Pasar las pruebas assert del módulo original a la clase, y agregar casos borde. 5. Agregar average y best, marcándolos const, y comprobar que el compilador rechaza un método const que modifica un campo. 6. Comparar las dos versiones: qué memoria maneja cada una a mano y cuántas líneas desaparecieron.

Preguntas de repaso

  1. ¿Qué diferencia hay entre void f(std::vector<int> v) y void f(const std::vector<int>& v)? ¿Cuál conviene para leer un arreglo grande, y por qué?
  2. ¿Puede una referencia quedar sin apuntar a nada, como un puntero nulo? ¿Puede cambiar a qué se refiere después de crearse?
  3. ¿Qué garantiza un método marcado const?
  4. ¿Qué parte del tipo opaco de C reemplaza private?

Código de la semana

Compilar y probar dentro del contenedor del curso con make y make test.

Ejemplos (25)Errores (20)Pruebas (4)Laboratorio (12)Compilación (1)

Ejemplos

Los programas que usan los apuntes y la clase.

c_style.cppDescargar c_style.cpp
// Un programa escrito como en C. g++ lo compila sin cambiar casi nada.
#include <cstdio>

struct point {
    int x;
    int y;
};

int manhattan(const point *p) {
    int dx = p->x < 0 ? -p->x : p->x;
    int dy = p->y < 0 ? -p->y : p->y;
    return dx + dy;
}

int main() {
    point origin = {0, 0};
    point target = {3, -4};
    int values[] = {manhattan(&origin), manhattan(&target)};
    for (int i = 0; i < 2; i++) {
        std::printf("%d\n", values[i]);
    }
    return 0;
}
const_forms.cppDescargar const_forms.cpp
// Las formas de const con punteros y referencias. Se leen de derecha a izquierda.
#include <cstddef>
#include <iostream>
#include <string>

std::size_t count_letter(const std::string& text, char letter) {
    std::size_t found = 0;
    for (char c : text) {
        if (c == letter) {
            found++;
        }
    }
    return found;
}

int main() {
    int a = 1;
    int b = 2;
    const int *to_const = &a;    // no se puede escribir *to_const
    to_const = &b;               // sí se puede cambiar el puntero
    int *const fixed = &a;       // no se puede cambiar el puntero
    *fixed = 5;                  // sí se puede escribir a través de él
    const int& view = a;         // no se puede escribir a través de view
    a = 7;                       // a sí cambia, y view lo ve
    std::cout << *to_const << " " << *fixed << " " << view << "\n";

    // Una literal crea una std::string temporal; const& se puede atar a ella.
    std::cout << count_letter("banana", 'a') << "\n";
    return 0;
}
const_shallow.cppDescargar const_shallow.cpp
// const protege los campos del objeto, no lo que hay al otro lado de un puntero.
#include <iostream>

class Remote {
public:
    explicit Remote(int *target) : target_(target) {}

    // Compila: no cambia target_, solo el entero al que apunta.
    void reset() const {
        *target_ = 0;
    }

private:
    int *target_;
};

int main() {
    int counter = 5;
    const Remote remote(&counter);
    remote.reset();
    std::cout << counter << "\n";
    return 0;
}
copy_in_loop.cppDescargar copy_in_loop.cpp
// Suma n elementos llamando a una función en cada vuelta. Uso: copy_in_loop n copy|ref
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include "value_at.h"

int main(int argc, char *argv[]) {
    if (argc != 3) {
        std::cerr << "uso: copy_in_loop n copy|ref\n";
        return 1;
    }
    std::size_t n = std::strtoul(argv[1], nullptr, 10);
    std::string mode = argv[2];
    std::vector<int> values(n, 1);

    long long sum = 0;
    long long copied = 0;
    for (std::size_t i = 0; i < n; i++) {
        if (mode == "copy") {
            sum += value_at_copy(values, i);
            copied += values.size();
        } else {
            sum += value_at_ref(values, i);
        }
    }
    std::cout << "suma = " << sum << ", elementos copiados = " << copied << "\n";
    return 0;
}
copy_or_not.cppDescargar copy_or_not.cpp
// ¿Pasar un std::vector crea otro bloque? Se comprueba comparando direcciones.
#include <iostream>
#include <vector>

void by_value(std::vector<int> values, const int *original) {
    values[0] = 99;
    std::cout << "por valor:      mismo bloque = " << (values.data() == original)
              << ", values[0] = " << values[0] << "\n";
}

void by_const_reference(const std::vector<int>& values, const int *original) {
    std::cout << "por const&:     mismo bloque = " << (values.data() == original)
              << ", values[0] = " << values[0] << "\n";
}

void by_reference(std::vector<int>& values, const int *original) {
    values[0] = 99;
    std::cout << "por referencia: mismo bloque = " << (values.data() == original)
              << ", values[0] = " << values[0] << "\n";
}

int main() {
    std::vector<int> numbers = {1, 2, 3};
    const int *block = numbers.data();

    by_value(numbers, block);
    std::cout << "en main:        numbers[0] = " << numbers[0] << "\n";

    by_const_reference(numbers, block);
    std::cout << "en main:        numbers[0] = " << numbers[0] << "\n";

    by_reference(numbers, block);
    std::cout << "en main:        numbers[0] = " << numbers[0] << "\n";
    return 0;
}
course.cppDescargar course.cpp
// Una clase con constructor, campos privados, métodos const y una referencia devuelta.
#include <cassert>
#include <iostream>
#include <string>

class Course {
public:
    Course(const std::string& name, int credits) : name_(name), credits_(credits) {
        assert(credits > 0);
    }

    void enroll() {
        enrolled_++;
    }

    int enrolled() const {
        return enrolled_;
    }

    int credits() const {
        return credits_;
    }

    // Devuelve el nombre sin copiarlo. La referencia es del objeto y vive lo que él.
    const std::string& name() const {
        return name_;
    }

    // private es por clase: este método puede ver los campos de other.
    bool same_credits_as(const Course& other) const {
        return credits_ == other.credits_;
    }

private:
    std::string name_;
    int credits_;
    int enrolled_ = 0;
};

void print_summary(const Course& course) {
    std::cout << course.name() << ": " << course.credits() << " créditos, "
              << course.enrolled() << " matriculados\n";
}

int main() {
    Course data("Estructuras de Datos", 4);
    Course math("Matemática Discreta", 4);
    data.enroll();
    data.enroll();
    print_summary(data);
    print_summary(math);
    std::cout << "mismos créditos: " << data.same_credits_as(math) << "\n";
    return 0;
}
hello.cppDescargar hello.cpp
// Primer programa en C++: lo mismo que un printf, con std::cout.
#include <iostream>

int main() {
    int students = 32;
    std::cout << "Hola, Estructuras de Datos\n";
    std::cout << "Somos " << students << " estudiantes\n";
    return 0;
}
lookup.cppDescargar lookup.cpp
// Busca los n códigos de una lista de n estudiantes y cuenta las comparaciones.
// Uso: lookup n linear|binary
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

// Devuelve el índice de code, o -1. Suma a comparisons cada elemento que mira.
long long linear_search(const std::vector<int>& codes, int code, long long& comparisons) {
    long long size = static_cast<long long>(codes.size());
    for (long long i = 0; i < size; i++) {
        comparisons++;
        if (codes[i] == code) {
            return i;
        }
    }
    return -1;
}

// Igual, pero exige que codes esté ordenado.
long long binary_search(const std::vector<int>& codes, int code, long long& comparisons) {
    long long low = 0;
    long long high = static_cast<long long>(codes.size()) - 1;
    while (low <= high) {
        long long middle = low + (high - low) / 2;
        comparisons++;
        if (codes[middle] == code) {
            return middle;
        }
        if (codes[middle] < code) {
            low = middle + 1;
        } else {
            high = middle - 1;
        }
    }
    return -1;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        std::cerr << "uso: lookup n linear|binary\n";
        return 1;
    }
    int n = std::atoi(argv[1]);
    std::string mode = argv[2];

    std::vector<int> codes;
    for (int i = 0; i < n; i++) {
        codes.push_back(2 * i);
    }

    long long comparisons = 0;
    long long found = 0;
    for (int code : codes) {
        long long index = mode == "linear" ? linear_search(codes, code, comparisons)
                                           : binary_search(codes, code, comparisons);
        if (index >= 0) {
            found++;
        }
    }
    std::cout << "n = " << n << ", encontrados = " << found
              << ", comparaciones = " << comparisons << "\n";
    return 0;
}
measure.pyDescargar measure.py
"""Mide los programas de la semana: mediana de 5 corridas, compilados con -O2 y sin
sanitizers. Los tiempos dependen de la máquina; lo que importa es cómo crecen."""
import statistics
import subprocess
import time

REPETITIONS = 5


def median_seconds(command):
    times = []
    for _ in range(REPETITIONS):
        start = time.perf_counter()
        subprocess.run(command, check=True, stdout=subprocess.DEVNULL)
        times.append(time.perf_counter() - start)
    return statistics.median(times)


def table(title, program, mode, sizes):
    print(title)
    previous = None
    for n in sizes:
        seconds = median_seconds([program, str(n), mode])
        growth = "" if previous is None else f"  x{seconds / previous:.1f}"
        print(f"{n:>10} {seconds:>9.3f} s{growth}")
        previous = seconds


table("Búsqueda lineal", "build/fast/lookup", "linear", [10000, 20000, 40000, 80000])
table("Búsqueda binaria", "build/fast/lookup", "binary", [5000000, 10000000, 20000000])
table("Por copia", "build/fast/copy_in_loop", "copy", [20000, 40000, 80000])
table("Por referencia constante", "build/fast/copy_in_loop", "ref", [20000, 40000, 80000])
table("Por referencia constante, n mucho mayor", "build/fast/copy_in_loop", "ref",
      [10000000, 20000000, 40000000])
namespaces.cppDescargar namespaces.cpp
// Dos funciones con el mismo nombre, cada una en su namespace.
#include <iostream>

namespace circle {
double area(double radius) {
    return 3.14159 * radius * radius;
}
}  // namespace circle

namespace square {
double area(double side) {
    return side * side;
}
}  // namespace square

int main() {
    std::cout << circle::area(2.0) << "\n";
    std::cout << square::area(2.0) << "\n";
    return 0;
}
range_for.cppDescargar range_for.cpp
// for por rango con copia, con referencia y con auto.
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<int> grades = {10, 15};

    // x es una copia de cada elemento.
    for (int x : grades) {
        x = x + 1;
        std::cout << x << " ";
    }
    std::cout << "/ " << grades[0] << " " << grades[1] << "\n";

    // x es otro nombre del elemento.
    for (int& x : grades) {
        x = x + 1;
    }
    std::cout << grades[0] << " " << grades[1] << "\n";

    std::vector<std::string> names = {"Ana", "Luis"};
    for (const std::string& name : names) {
        std::cout << name << " ";
    }
    std::cout << "\n";

    // auto deduce el tipo; sin & sigue siendo una copia.
    for (auto name : names) {
        name += "!";
    }
    std::cout << names[0] << "\n";
    for (auto& name : names) {
        name += "!";
    }
    std::cout << names[0] << "\n";

    auto size = names.size();
    const auto& first = names[0];
    std::cout << size << " " << first << "\n";
    return 0;
}
reference_basics.cppDescargar reference_basics.cpp
// Una referencia es otro nombre para un objeto que ya existe.
#include <iostream>

int main() {
    int grade = 14;
    int& alias = grade;

    alias = 15;
    std::cout << "grade = " << grade << "\n";
    std::cout << "misma dirección: " << (&alias == &grade) << "\n";

    // Esto no cambia a quién se refiere alias: copia 20 dentro de grade.
    int other = 20;
    alias = other;
    other = 0;
    std::cout << "grade = " << grade << ", other = " << other << "\n";
    return 0;
}
sizes.cppDescargar sizes.cpp
// Quien usa una clase conoce su tamaño, aunque los campos sean privados.
#include <iostream>

#include "tally.h"

int main() {
    Tally local;
    local.add(4);
    std::cout << "sizeof(Tally) = " << sizeof(Tally) << "\n";
    std::cout << "cuenta = " << local.count() << "\n";
    return 0;
}
small_changes.cppDescargar small_changes.cpp
// Diferencias pequeñas con C: bool, nullptr, static_cast y división entera.
#include <iostream>

bool is_passing(int grade) {
    return grade >= 11;
}

int main() {
    int total = 29;
    int count = 2;
    const int *missing = nullptr;
    std::cout << is_passing(14) << " " << is_passing(9) << "\n";
    std::cout << (missing == nullptr) << "\n";
    std::cout << total / count << "\n";
    std::cout << static_cast<double>(total) / count << "\n";
    return 0;
}
strings.cppDescargar strings.cpp
// std::string: lo que en C hacían strlen, strcpy, strcat y strcmp.
#include <cstddef>
#include <cstdio>
#include <iostream>
#include <string>

int main() {
    std::string first = "Ana";
    std::string full = first + " Quispe";
    std::string copy = full;
    copy[0] = 'E';

    std::cout << full << " / " << copy << "\n";
    std::cout << "largo: " << full.size() << "\n";
    std::cout << "iguales: " << (full == copy) << "\n";
    std::cout << "ordenadas: " << (full < copy) << "\n";

    std::size_t space = full.find(' ');
    std::cout << "apellido: " << full.substr(space + 1) << "\n";
    if (full.find('z') == std::string::npos) {
        std::cout << "no hay z\n";
    }

    full += ", sección A";
    std::cout << full << "\n";

    // Para una función de C que pide const char *.
    std::printf("%s\n", first.c_str());
    return 0;
}
swap_both.cppDescargar swap_both.cpp
// El mismo intercambio con punteros, como en C, y con referencias.
#include <iostream>

void swap_pointer(int *a, int *b) {
    int saved = *a;
    *a = *b;
    *b = saved;
}

void swap_reference(int& a, int& b) {
    int saved = a;
    a = b;
    b = saved;
}

int main() {
    int x = 1;
    int y = 2;
    swap_pointer(&x, &y);
    std::cout << x << " " << y << "\n";
    swap_reference(x, y);
    std::cout << x << " " << y << "\n";
    return 0;
}
tally_demo.cppDescargar tally_demo.cpp
// Uso de la clase: no hay puntero, no se revisa memoria y no se libera a mano.
#include <iostream>

#include "tally.h"

int main() {
    Tally tally;
    tally.add(14);
    tally.add(9);
    tally.add(17);
    std::cout << tally.count() << " valores, suma " << tally.sum() << ", mayor " << tally.max() << "\n";
    return 0;
}
tally.cppDescargar tally.cpp
#include "tally.h"

#include <cassert>

void Tally::add(int value) {
    values_.push_back(value);
}

std::size_t Tally::count() const {
    return values_.size();
}

int Tally::sum() const {
    int total = 0;
    for (int value : values_) {
        total += value;
    }
    return total;
}

int Tally::max() const {
    assert(!values_.empty());
    int largest = values_[0];
    for (int value : values_) {
        if (value > largest) {
            largest = value;
        }
    }
    return largest;
}
tally.hDescargar tally.h
// Clase Tally: acumula enteros y responde cuántos son, cuánto suman y cuál es el mayor.
#ifndef TALLY_H
#define TALLY_H

#include <cstddef>
#include <vector>

class Tally {
public:
    // Agrega un valor.
    void add(int value);

    // Devuelve cuántos valores se agregaron.
    std::size_t count() const;

    // Devuelve la suma de los valores. Con cero valores, es 0.
    int sum() const;

    // Devuelve el mayor valor. Exige: count() > 0.
    int max() const;

private:
    std::vector<int> values_;
};

#endif
value_at.cppDescargar value_at.cpp
#include "value_at.h"

int value_at_copy(std::vector<int> values, std::size_t index) {
    return values[index];
}

int value_at_ref(const std::vector<int>& values, std::size_t index) {
    return values[index];
}
value_at.hDescargar value_at.h
// Dos formas de leer un elemento. Están en otro archivo para que el compilador no
// pueda quitar la copia al optimizar.
#ifndef VALUE_AT_H
#define VALUE_AT_H

#include <cstddef>
#include <vector>

int value_at_copy(std::vector<int> values, std::size_t index);
int value_at_ref(const std::vector<int>& values, std::size_t index);

#endif
vectors.cppDescargar vectors.cpp
// std::vector: lo que en C era un arreglo con malloc y realloc.
#include <iostream>
#include <string>
#include <vector>

struct Score {
    std::string name;
    int points;
};

int main() {
    std::vector<int> grades;
    grades.push_back(14);
    grades.push_back(9);
    grades.push_back(17);
    std::cout << "tamaño: " << grades.size() << "\n";
    std::cout << "último: " << grades.back() << "\n";

    grades[1] = 11;
    grades.erase(grades.begin());
    std::cout << "primero tras borrar: " << grades[0] << "\n";

    std::vector<int> zeros(3, 0);
    std::vector<int> primes = {2, 3, 5};
    std::vector<int> other = primes;
    other[0] = 7;
    std::cout << primes[0] << " " << other[0] << "\n";
    std::cout << "iguales: " << (primes == other) << "\n";
    std::cout << "ceros: " << zeros.size() << ", vacío: " << zeros.empty() << "\n";

    std::vector<Score> table;
    table.push_back({"Ana", 18});
    table.push_back({"Luis", 12});
    std::cout << table[1].name << " tiene " << table[1].points << "\n";

    grades.clear();
    std::cout << "tras clear: " << grades.size() << "\n";
    return 0;
}
tally_c/tally_demo.cDescargar tally_c/tally_demo.c
// Uso del módulo en C: quien lo usa crea, revisa y libera.
#include <stdio.h>

#include "tally.h"

int main(void) {
    Tally *tally = tally_new();
    if (tally == NULL) {
        return 1;
    }
    int ok = tally_add(tally, 14) && tally_add(tally, 9) && tally_add(tally, 17);
    if (!ok) {
        tally_free(tally);
        return 1;
    }
    printf("%d valores, suma %d, mayor %d\n", tally_count(tally), tally_sum(tally), tally_max(tally));
    tally_free(tally);
    return 0;
}
tally_c/tally.cDescargar tally_c/tally.c
// Implementación con un arreglo que crece: guarda todos los valores.
#include "tally.h"

#include <assert.h>
#include <stdlib.h>

struct tally {
    int *values;
    int count;
    int capacity;
};

Tally *tally_new(void) {
    Tally *tally = malloc(sizeof *tally);
    if (tally == NULL) {
        return NULL;
    }
    tally->values = NULL;
    tally->count = 0;
    tally->capacity = 0;
    return tally;
}

void tally_free(Tally *tally) {
    if (tally == NULL) {
        return;
    }
    free(tally->values);
    free(tally);
}

int tally_add(Tally *tally, int value) {
    if (tally->count == tally->capacity) {
        int capacity = tally->capacity == 0 ? 4 : 2 * tally->capacity;
        int *values = realloc(tally->values, capacity * sizeof *values);
        if (values == NULL) {
            return 0;
        }
        tally->values = values;
        tally->capacity = capacity;
    }
    tally->values[tally->count] = value;
    tally->count++;
    return 1;
}

int tally_count(const Tally *tally) {
    return tally->count;
}

int tally_sum(const Tally *tally) {
    int sum = 0;
    for (int i = 0; i < tally->count; i++) {
        sum += tally->values[i];
    }
    return sum;
}

int tally_max(const Tally *tally) {
    assert(tally->count > 0);
    int largest = tally->values[0];
    for (int i = 1; i < tally->count; i++) {
        if (tally->values[i] > largest) {
            largest = tally->values[i];
        }
    }
    return largest;
}
tally_c/tally.hDescargar tally_c/tally.h
// Módulo Tally: acumula enteros y responde cuántos son, cuánto suman y cuál es el mayor.
// Es un tipo opaco: quien lo usa solo tiene punteros y solo llama a estas funciones.
#ifndef TALLY_H
#define TALLY_H

typedef struct tally Tally;

// Crea un acumulador vacío. Devuelve NULL si no hay memoria.
// Owner: quien llama, que debe liberarlo con tally_free.
Tally *tally_new(void);

// Libera el acumulador. Acepta NULL. Después de esto, tally ya no se puede usar.
void tally_free(Tally *tally);

// Agrega un valor. Devuelve 1 si pudo y 0 si no hay memoria; en ese caso, no cambia nada.
int tally_add(Tally *tally, int value);

// Devuelve cuántos valores se agregaron.
int tally_count(const Tally *tally);

// Devuelve la suma de los valores. Con cero valores, es 0.
int tally_sum(const Tally *tally);

// Devuelve el mayor valor. Exige: tally_count(tally) > 0.
int tally_max(const Tally *tally);

#endif

Errores

Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.

errors/assert_side_effect.cppDescargar errors/assert_side_effect.cpp
// Con NDEBUG, assert desaparece, y con él lo que hacía adentro.
#include <cassert>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {1, 2, 3};
    values.pop_back();
    assert((values.push_back(9), values.size() == 3));
    std::cout << values.size() << "\n";
    return 0;
}
errors/const_method_writes.cppDescargar errors/const_method_writes.cpp
// Un método const no puede modificar un campo.
class Counter {
public:
    int next() const {
        value_++;
        return value_;
    }

private:
    int value_ = 0;
};

int main() {
    Counter counter;
    return counter.next();
}
errors/const_object_call.cppDescargar errors/const_object_call.cpp
// A un objeto const solo se le pueden pedir métodos const.
#include "../tally.h"

int total_after_add(const Tally& tally) {
    tally.add(1);
    return tally.sum();
}

int main() {
    Tally tally;
    return total_after_add(tally);
}
errors/const_ref_write.cppDescargar errors/const_ref_write.cpp
// Una referencia constante no permite escribir.
void reset(const int& value) {
    value = 0;
}

int main() {
    int grade = 14;
    reset(grade);
    return grade;
}
errors/init_order.cppDescargar errors/init_order.cpp
// Los campos se inicializan en el orden en que están declarados, no en el de la lista.
#include <iostream>

class Range {
public:
    explicit Range(int width) : high_(low_ + width), low_(0) {}

    int high() const {
        return high_;
    }

private:
    int low_;
    int high_;
};

int main() {
    Range range(5);
    std::cout << range.high() << "\n";
    return 0;
}
errors/int_loop_copy.cppDescargar errors/int_loop_copy.cpp
// Sin & en el for por rango, se modifica una copia.
#include <iostream>
#include <vector>

struct Score {
    int points;
};

int main() {
    std::vector<Score> table = {{10}, {15}};
    for (auto score : table) {
        score.points += 1;
    }
    std::cout << table[0].points << " " << table[1].points << "\n";
    return 0;
}
errors/keyword_name.cppDescargar errors/keyword_name.cpp
// Válido en C, no en C++: class es una palabra reservada.
int main() {
    int class = 3;
    return class;
}
errors/malloc_no_cast.cppDescargar errors/malloc_no_cast.cpp
// En C++, void * no se convierte solo a otro tipo de puntero.
#include <cstdlib>

int main() {
    int *block = std::malloc(4 * sizeof *block);
    std::free(block);
    return 0;
}
errors/no_default_ctor.cppDescargar errors/no_default_ctor.cpp
// Al declarar un constructor, el compilador ya no genera el que no recibe nada.
class Range {
public:
    Range(int low, int high) : low_(low), high_(high) {}

    int width() const {
        return high_ - low_;
    }

private:
    int low_;
    int high_;
};

int main() {
    Range range;
    return range.width();
}
errors/private_access.cppDescargar errors/private_access.cpp
// Quien usa la clase no puede tocar sus campos privados.
#include "../tally.h"

int main() {
    Tally tally;
    tally.values_.clear();
    return 0;
}
errors/ref_to_temporary.cppDescargar errors/ref_to_temporary.cpp
// Una referencia sin const no se ata a un valor temporal.
#include <cstddef>
#include <string>

std::size_t length(std::string& text) {
    return text.size();
}

int main() {
    return static_cast<int>(length("hola"));
}
errors/ref_uninitialized.cppDescargar errors/ref_uninitialized.cpp
// Una referencia debe atarse a un objeto al crearse.
int main() {
    int& alias;
    return 0;
}
errors/return_local_ref.cppDescargar errors/return_local_ref.cpp
// Referencia colgante: la local muere al retornar.
#include <iostream>

int& next_code(int code) {
    int result = code + 1;
    return result;
}

int main() {
    int& code = next_code(10);
    std::cout << code << "\n";
    return 0;
}
errors/sign_compare.cppDescargar errors/sign_compare.cpp
// size() no tiene signo: comparar con int da una advertencia.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {1, 2, 3};
    for (int i = 0; i < values.size(); i++) {
        std::cout << values[i] << "\n";
    }
    return 0;
}
errors/size_minus_one.cppDescargar errors/size_minus_one.cpp
// size() - 1 con un vector vacío da un número enorme, no -1.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> empty;
    std::cout << empty.size() - 1 << "\n";
    return 0;
}
errors/using_std_collision.cppDescargar errors/using_std_collision.cpp
// Con using namespace std, un nombre propio choca con uno de la biblioteca.
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int count = 0;

int main() {
    vector<int> values = {1, 2, 3};
    for (int value : values) {
        count += value;
    }
    cout << count << "\n";
    return 0;
}
errors/vector_at.cppDescargar errors/vector_at.cpp
// at revisa el índice.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {1, 2, 3};
    std::cout << values.at(3) << "\n";
    return 0;
}
errors/vector_index.cppDescargar errors/vector_index.cpp
// operator[] no revisa el índice.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {1, 2, 3};
    std::cout << values[3] << "\n";
    return 0;
}
errors/vector_reserved.cppDescargar errors/vector_reserved.cpp
// Fuera de size(), pero dentro del bloque: ASan no lo ve.
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values;
    values.reserve(8);
    values.push_back(1);
    values[5] = 42;
    std::cout << "size = " << values.size() << ", values[5] = " << values[5] << "\n";
    return 0;
}
errors/vexing_parse.cppDescargar errors/vexing_parse.cpp
// Con paréntesis vacíos, esto declara una función, no un objeto.
#include "../tally.h"

int main() {
    Tally tally();
    tally.add(1);
    return 0;
}

Pruebas

Comprueban lo que afirman los apuntes; se ejecutan con make test.

tally_tests.cppDescargar tally_tests.cpp
// Pruebas de Tally: casos dirigidos y operaciones al azar con semilla fija.
#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <random>
#include <vector>

#include "tally.h"

void test_starts_empty() {
    Tally tally;
    assert(tally.count() == 0);
    assert(tally.sum() == 0);
}

void test_one_value() {
    Tally tally;
    tally.add(-3);
    assert(tally.count() == 1);
    assert(tally.sum() == -3);
    assert(tally.max() == -3);
}

void test_read_through_const_reference(const Tally& tally) {
    assert(tally.count() == 3);
    assert(tally.max() == 17);
}

void test_several_values() {
    Tally tally;
    tally.add(14);
    tally.add(9);
    tally.add(17);
    assert(tally.sum() == 40);
    test_read_through_const_reference(tally);
}

// Compara contra un std::vector, que hace de referencia.
void test_against_reference() {
    std::mt19937 random(2026);
    Tally tally;
    std::vector<int> reference;
    for (int step = 0; step < 2000; step++) {
        int value = static_cast<int>(random() % 201) - 100;
        tally.add(value);
        reference.push_back(value);
        assert(tally.count() == reference.size());
        assert(tally.sum() == std::accumulate(reference.begin(), reference.end(), 0));
        assert(tally.max() == *std::max_element(reference.begin(), reference.end()));
    }
}

int main() {
    test_starts_empty();
    test_one_value();
    test_several_values();
    test_against_reference();
    std::cout << "tally: todas las pruebas pasaron\n";
    return 0;
}
test_examples.pyDescargar test_examples.py
"""Comprueba los programas de la clase y lo que los apuntes dicen de sus errores."""
import pathlib
import re
import subprocess
import tempfile
import unittest

HERE = pathlib.Path(__file__).parent
BUILD = HERE / "build"
CXX = ["g++", "-Wall", "-Wextra", "-std=c++20", "-g", "-fsanitize=address,undefined"]
C_MODULE = HERE / "lab" / "c"

# Programa de errors/ que no compila, y parte del mensaje de g++.
COMPILE_ERRORS = {
    "malloc_no_cast": "invalid conversion from 'void*' to 'int*'",
    "keyword_name": "expected primary-expression before 'int'",
    "using_std_collision": "reference to 'count' is ambiguous",
    "ref_uninitialized": "'alias' declared as reference but not initialized",
    "ref_to_temporary": "cannot bind non-const lvalue reference",
    "const_ref_write": "assignment of read-only reference 'value'",
    "private_access": "is private within this context",
    "const_method_writes": "increment of member 'Counter::value_' in read-only object",
    "const_object_call": "discards qualifiers",
    "no_default_ctor": "no matching function for call to 'Range::Range()'",
    "vexing_parse": "which is of non-class type 'Tally()'",
}
# Programa de errors/ que compila con una advertencia, y su opción.
WARNINGS = {
    "return_local_ref": "-Wreturn-local-addr",
    "init_order": "-Wreorder",
    "sign_compare": "-Wsign-compare",
}
# Programa de errors/ que falla al correr, y lo que debe decir.
RUNTIME_ERRORS = {
    "vector_index": "heap-buffer-overflow",
    "vector_at": "std::out_of_range",
}


def compile_error(name):
    done = subprocess.run(CXX + ["-I.", "-c", f"errors/{name}.cpp", "-o", "/dev/null"], cwd=HERE, text=True,
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120)
    return done.returncode, done.stdout.replace("‘", "'").replace("’", "'")


class Programs(unittest.TestCase):
    def output(self, name, *args):
        done = subprocess.run([str(BUILD / name), *args], cwd=HERE, text=True, capture_output=True, timeout=60)
        self.assertEqual(done.returncode, 0, done.stderr)
        return done.stdout

    def test_every_program_builds_without_warnings(self):
        with tempfile.TemporaryDirectory() as temp:
            for source in sorted(HERE.glob("*.cpp")):
                done = subprocess.run(CXX + ["-c", str(source), "-o", f"{temp}/program.o"], cwd=HERE, text=True,
                                      capture_output=True, timeout=120)
                with self.subTest(source=source.name):
                    self.assertEqual((done.returncode, done.stderr), (0, ""))

    def test_tally_tests_pass(self):
        self.assertIn("todas las pruebas pasaron", self.output("tally_tests"))

    def test_both_tallies_say_the_same(self):
        self.assertEqual(self.output("tally_demo"), self.output("tally_c_demo"))

    def test_lookup_counts(self):
        # Lineal: 1 + 2 + ... + n exactamente.
        for n in (1, 10, 1000):
            text = self.output("lookup", str(n), "linear")
            self.assertIn(f"comparaciones = {n * (n + 1) // 2}", text)
        # Binaria: como mucho floor(log2(n)) + 1 por búsqueda.
        text = self.output("lookup", "1000", "binary")
        count = int(re.search(r"comparaciones = (\d+)", text).group(1))
        self.assertLessEqual(count, 1000 * 10)

    def test_copy_counts(self):
        self.assertIn("elementos copiados = 1000000", self.output("copy_in_loop", "1000", "copy"))
        self.assertIn("elementos copiados = 0", self.output("copy_in_loop", "1000", "ref"))

    def test_only_by_value_makes_another_block(self):
        lines = self.output("copy_or_not").splitlines()
        self.assertIn("mismo bloque = 0", lines[0])
        self.assertIn("mismo bloque = 1", lines[2])
        self.assertIn("mismo bloque = 1", lines[4])


class DeliberateErrors(unittest.TestCase):
    def test_compile_errors(self):
        for name, message in COMPILE_ERRORS.items():
            with self.subTest(program=name):
                code, text = compile_error(name)
                self.assertNotEqual(code, 0)
                self.assertIn(message, text)

    def test_warnings(self):
        for name, option in WARNINGS.items():
            with self.subTest(program=name):
                code, text = compile_error(name)
                self.assertEqual(code, 0)
                self.assertIn(option, text)

    def test_runtime_errors(self):
        with tempfile.TemporaryDirectory() as temp:
            for name, message in RUNTIME_ERRORS.items():
                with self.subTest(program=name):
                    program = f"{temp}/{name}"
                    subprocess.run(CXX + [f"errors/{name}.cpp", "-o", program], cwd=HERE, check=True, timeout=120)
                    done = subprocess.run([program], text=True, capture_output=True, timeout=60)
                    self.assertNotEqual(done.returncode, 0)
                    self.assertIn(message, done.stderr)


class GivenCModule(unittest.TestCase):
    """El módulo en C del laboratorio es de verdad: compila limpio, pasa sus pruebas y no tiene fugas."""

    def test_compiles_and_passes_with_sanitizers(self):
        with tempfile.TemporaryDirectory() as temp:
            program = f"{temp}/tests"
            done = subprocess.run(["gcc", "-Wall", "-Wextra", "-std=c17", "-g", "-fsanitize=address,undefined",
                                   str(C_MODULE / "test_grade_book.c"), str(C_MODULE / "grade_book.c"), "-o", program],
                                  text=True, capture_output=True, timeout=120)
            self.assertEqual((done.returncode, done.stderr), (0, ""))
            run = subprocess.run([program], text=True, capture_output=True, timeout=60)
            self.assertEqual(run.returncode, 0, run.stderr)

    def test_clean_under_valgrind(self):
        with tempfile.TemporaryDirectory() as temp:
            program = f"{temp}/tests"
            subprocess.run(["gcc", "-Wall", "-Wextra", "-std=c17", "-g", "-O0", str(C_MODULE / "test_grade_book.c"),
                            str(C_MODULE / "grade_book.c"), "-o", program], check=True, timeout=120)
            run = subprocess.run(["valgrind", "--leak-check=full", "--error-exitcode=9", program], text=True,
                                 capture_output=True, timeout=120)
            self.assertEqual(run.returncode, 0, run.stderr)
            self.assertIn("All heap blocks were freed", run.stderr)


class SolutionComparison(unittest.TestCase):
    @unittest.skipUnless((HERE / "solution").exists(), "falta la solución")
    def test_numbers_match_the_files(self):
        def lines(path):
            return len((HERE / path).read_text(encoding="utf-8").splitlines())

        text = (HERE / "solution" / "comparison.txt").read_text(encoding="utf-8")
        h_c, c_c = lines("lab/c/grade_book.h"), lines("lab/c/grade_book.c")
        h_cpp, c_cpp = lines("solution/grade_book.h"), lines("solution/grade_book.cpp")
        self.assertIn(f"{h_c + c_c} ({h_c} + {c_c})", text)
        self.assertIn(f"{h_cpp + c_cpp} ({h_cpp} + {c_cpp})", text)


if __name__ == "__main__":
    unittest.main()
test_lab.pyDescargar test_lab.py
"""Pruebas del laboratorio. LAB_DIR dice dónde está la clase: lab o solution.

Cada prueba compila con sanitizers lo que necesita y lo corre: una fuga o un acceso
inválido la hace fallar, aunque el resultado parezca correcto.
"""
import os
import pathlib
import random
import re
import subprocess
import tempfile
import unittest

HERE = pathlib.Path(__file__).parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
SOURCE = HERE / LAB_DIR
GIVEN = HERE / "lab"
CXX_FLAGS = ["g++", "-Wall", "-Wextra", "-std=c++20", "-g", "-fsanitize=address,undefined"]
C_FLAGS = ["gcc", "-Wall", "-Wextra", "-std=c17", "-g", "-fsanitize=address,undefined"]
# Mínimo de assert en sus pruebas: los 38 del módulo en C que se pueden pasar a la clase,
# más al menos tres de casos borde.
MIN_ASSERTS = 41
# Lo que ya no debe aparecer en la clase.
MANUAL_MEMORY = r"\b(malloc|calloc|realloc|free|strdup|strcpy|strcmp|strlen|new|delete)\b|\bchar\s*\*"


def strip_comments(text):
    return re.sub(r"//[^\n]*", "", text)


def build(command, output):
    done = subprocess.run(command + ["-o", str(output)], cwd=HERE, text=True,
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120)
    return done.returncode, done.stdout


def run(program, stdin=""):
    return subprocess.run([str(program)], cwd=HERE, input=stdin, text=True,
                          stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=60)


def random_commands(seed, steps):
    """Una secuencia de órdenes con semilla fija, para comparar las dos versiones."""
    generator = random.Random(seed)
    lines = []
    for _ in range(steps):
        name = f"e{generator.randrange(25)}"
        grade = generator.randrange(-2, 23)
        command = generator.choice(["add", "add", "set", "get", "remove", "list"])
        if command in ("add", "set"):
            lines.append(f"{command} {name} {grade}")
        elif command in ("get", "remove"):
            lines.append(f"{command} {name}")
        else:
            lines.append("list")
    return "\n".join(lines) + "\n"


@unittest.skipUnless(SOURCE.exists(), f"no existe {LAB_DIR}/")
class LabCase(unittest.TestCase):
    def setUp(self):
        self.folder = tempfile.TemporaryDirectory()
        self.addCleanup(self.folder.cleanup)
        self.temp = pathlib.Path(self.folder.name)

    def compile_class(self, main_source):
        """Compila main_source con la clase. Falla la prueba si no compila o si da advertencias."""
        program = self.temp / "program"
        code, messages = build(CXX_FLAGS + [f"-I{SOURCE}", str(main_source),
                                            str(SOURCE / "grade_book.cpp")], program)
        self.assertEqual(code, 0, f"no compila:\n{messages}")
        self.assertEqual(messages.strip(), "", "hay advertencias del compilador")
        return program

    def assert_runs(self, program):
        done = run(program)
        self.assertEqual(done.returncode, 0, done.stdout + done.stderr)


class Step1Environment(LabCase):
    def test_hello(self):
        program = self.temp / "hello"
        code, messages = build(CXX_FLAGS + [str(GIVEN / "hello.cpp")], program)
        self.assertEqual((code, messages.strip()), (0, ""))
        self.assertEqual(run(program).stdout, "entorno: ok\ncompilador: ok\nmake: ok\n")


class Step2GivenModule(LabCase):
    def test_c_module_compiles_and_passes(self):
        program = self.temp / "tests_c"
        code, messages = build(C_FLAGS + [str(GIVEN / "c" / "test_grade_book.c"),
                                          str(GIVEN / "c" / "grade_book.c")], program)
        self.assertEqual((code, messages.strip()), (0, ""))
        self.assert_runs(program)


class Step3Port(LabCase):
    def test_public_tests(self):
        self.assert_runs(self.compile_class(GIVEN / "tests" / "public_port.cpp"))

    def test_name_at_checks_its_contract(self):
        source = self.temp / "out_of_range.cpp"
        source.write_text('#include "grade_book.h"\nint main() {\n    GradeBook book;\n'
                          '    return static_cast<int>(book.name_at(0).size());\n}\n')
        done = run(self.compile_class(source))
        self.assertNotEqual(done.returncode, 0, "name_at fuera de rango debe detenerse con assert")

    def test_same_behavior_as_the_c_module(self):
        driver_c = self.temp / "driver_c"
        code, messages = build(C_FLAGS + [str(GIVEN / "c" / "driver.c"),
                                          str(GIVEN / "c" / "grade_book.c")], driver_c)
        self.assertEqual((code, messages.strip()), (0, ""))
        driver = self.compile_class(GIVEN / "tests" / "driver.cpp")
        for seed in (1, 2, 3):
            commands = random_commands(seed, 2000)
            expected = run(driver_c, commands)
            actual = run(driver, commands)
            self.assertEqual(expected.returncode, 0, expected.stderr)
            self.assertEqual(actual.returncode, 0, actual.stderr)
            self.assertEqual(actual.stdout, expected.stdout, f"semilla {seed}")

    def test_no_manual_memory(self):
        for name in ("grade_book.h", "grade_book.cpp"):
            text = strip_comments((SOURCE / name).read_text(encoding="utf-8"))
            found = re.search(MANUAL_MEMORY, text)
            self.assertIsNone(found, f"{name}: {found.group(0) if found else ''}")

    def test_uses_string_and_vector(self):
        text = strip_comments((SOURCE / "grade_book.h").read_text(encoding="utf-8"))
        self.assertIn("std::vector", text)
        self.assertNotIn("placeholder_", text)


class Step4OwnTests(LabCase):
    def test_own_tests_pass(self):
        self.assert_runs(self.compile_class(SOURCE / "test_grade_book.cpp"))

    def test_own_tests_cover_the_c_tests_and_more(self):
        text = strip_comments((SOURCE / "test_grade_book.cpp").read_text(encoding="utf-8"))
        count = len(re.findall(r"\bassert\(", text))
        self.assertGreaterEqual(count, MIN_ASSERTS, f"hay {count} assert")


class Step5Stats(LabCase):
    def test_public_tests(self):
        self.assert_runs(self.compile_class(GIVEN / "tests" / "public_stats.cpp"))

    def test_add_is_not_const(self):
        source = self.temp / "const_add.cpp"
        source.write_text('#include "grade_book.h"\nvoid f(const GradeBook& book) { book.add("a", 1); }\n')
        command = CXX_FLAGS + [f"-I{SOURCE}", "-c", str(source)]
        code, _ = build(command, self.temp / "const_add.o")
        self.assertNotEqual(code, 0, "un GradeBook const no debe aceptar add")


class Step6Comparison(LabCase):
    def test_comparison_is_filled_in(self):
        text = (SOURCE / "comparison.txt").read_text(encoding="utf-8")
        self.assertNotIn("PENDIENTE", text)
        self.assertRegex(text, "read-only|discards qualifiers", "falta la línea del error del compilador")


if __name__ == "__main__":
    unittest.main()
test_notes.pyDescargar test_notes.py
"""Vuelve a correr los comandos que aparecen en los apuntes y en las diapositivas
y comprueba que el código citado existe de verdad en esta carpeta."""
import os
import pathlib
import platform
import re
import subprocess
import unittest

try:
    import pty
except ImportError:
    pty = None

HERE = pathlib.Path(__file__).parent
ROOT = HERE.parent.parent
NOTES = ROOT / "notes" / "week-01.md"
SLIDES = ROOT / "slides" / "week-01.tex"
# Antes de un bloque cuya salida cambia con el sistema. No se compara.
SYSTEM_DEPENDENT = "<!-- depende del sistema -->"
# Antes de un informe del compilador o de un sanitizer. Puede omitir líneas, pero no
# cambiarlas ni reordenarlas. Las direcciones y los números de proceso no se comparan.
REPORT = "<!-- informe recortado -->"
# Los informes se capturaron en esta arquitectura: en otra cambian líneas de las bibliotecas.
REPORT_MACHINE = "aarch64"


def normalize(text):
    # Según el idioma del entorno, g++ usa comillas tipográficas o rectas.
    return text.replace("‘", "'").replace("’", "'")


def run_shell(command):
    done = subprocess.run(
        ["bash", "-c", command], cwd=HERE, text=True, encoding="utf-8",
        stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120,
    )
    return normalize(done.stdout)


def run_in_terminal(command):
    """Corre el comando en una terminal de verdad: así se intercala la salida del programa
    con el informe, igual que cuando ustedes lo corren."""
    master, slave = pty.openpty()
    process = subprocess.Popen(
        ["bash", "-c", command], cwd=HERE, stdin=subprocess.DEVNULL, stdout=slave, stderr=slave,
    )
    os.close(slave)
    data = b""
    while True:
        try:
            chunk = os.read(master, 4096)
        except OSError:
            break
        if not chunk:
            break
        data += chunk
    os.close(master)
    process.wait(timeout=120)
    text = data.decode("utf-8", errors="replace").replace("\r", "")
    return normalize(re.sub(r"\x1b\[[0-9;]*[mK]", "", text))


def clean_lines(text):
    return [line.rstrip() for line in text.splitlines()]


def stable(line):
    """Quita de una línea de informe lo que cambia en cada corrida."""
    line = re.sub(r"0x[0-9a-f]+", "ADDR", line.strip())
    return re.sub(r"==\d+==", "==N==", line)


def split_commands(lines):
    """Parte un bloque de terminal en pares (comando, líneas de salida)."""
    pairs = []
    for line in lines:
        if line.startswith("$ "):
            pairs.append((line[2:], []))
        elif pairs:
            pairs[-1][1].append(normalize(line.rstrip()))
    return pairs


def note_blocks(language):
    """Devuelve (líneas, marca) de cada bloque de los apuntes."""
    blocks, current, mark, previous = [], None, "", ""
    for raw in NOTES.read_text(encoding="utf-8").splitlines():
        line = re.sub(r"^> ?", "", raw)
        if current is None:
            if line.strip() == "```" + language:
                current, mark = [], previous.strip()
            elif line.strip():
                previous = line
        elif line.strip() == "```":
            blocks.append((current, mark))
            current = None
        else:
            current.append(line)
    return blocks


def slide_blocks(environments):
    text = SLIDES.read_text(encoding="utf-8")
    pattern = r"\\begin\{(?:%s)\}[^\n]*\n(.*?)\\end\{(?:%s)\}" % (environments, environments)
    return [match.group(1).splitlines() for match in re.finditer(pattern, text, re.S)]


def known_code_lines():
    lines = set()
    sources = []
    for folder in (HERE, HERE / "errors", HERE / "tally_c", HERE / "lab", HERE / "lab" / "c", HERE / "lab" / "tests"):
        for pattern in ("*.cpp", "*.h", "*.c"):
            sources += folder.glob(pattern)
    for source in sources:
        lines.update(line.strip() for line in source.read_text(encoding="utf-8").splitlines())
    return lines


class ReportCheck(unittest.TestCase):
    def assert_in_order(self, command, expected, actual):
        """Las líneas esperadas aparecen en la salida real, en orden. '...' marca un recorte."""
        wanted = [stable(line) for line in expected if line.strip() and line.strip() != "..."]
        real = [stable(line) for line in actual]
        position = 0
        for line in wanted:
            with self.subTest(command=command, line=line):
                found = [i for i in range(position, len(real)) if real[i] == line]
                self.assertTrue(found, f"no aparece en la salida real: {line}")
                if found:
                    position = found[0] + 1


class NotesAreTrue(ReportCheck):
    @unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
    def test_terminal_blocks_in_notes(self):
        checked = 0
        for lines, mark in note_blocks("text"):
            if not lines or not lines[0].startswith("$ ") or mark == REPORT:
                continue
            for command, expected in split_commands(lines):
                actual = clean_lines(run_shell(command))
                checked += 1
                if mark != SYSTEM_DEPENDENT:
                    with self.subTest(command=command):
                        self.assertEqual(actual, expected)
        self.assertGreater(checked, 15)

    @unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
    @unittest.skipUnless(pty and platform.machine() == REPORT_MACHINE, "los informes son de aarch64")
    def test_reports_in_notes(self):
        checked = 0
        for lines, mark in note_blocks("text"):
            if mark != REPORT:
                continue
            for command, expected in split_commands(lines):
                actual = clean_lines(run_in_terminal(command))
                self.assert_in_order(command, expected, actual)
                checked += 1
        self.assertGreater(checked, 8)

    @unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
    def test_code_blocks_in_notes_exist(self):
        known = known_code_lines()
        for language in ("c", "cpp"):
            for lines, _ in note_blocks(language):
                for line in lines:
                    if line.strip() and line.strip() != "...":
                        with self.subTest(line=line):
                            self.assertIn(line.strip(), known)


class SlidesAreTrue(ReportCheck):
    @unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
    def test_program_output_in_slides(self):
        for block in slide_blocks("terminal|salida"):
            for command, expected in split_commands(block):
                self.assert_in_order(command, expected, clean_lines(run_shell(command)))

    # Algunos informes usan programas que compiló la prueba de los apuntes, que corre antes.
    @unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
    @unittest.skipUnless(pty and platform.machine() == REPORT_MACHINE, "los informes son de aarch64")
    def test_reports_in_slides(self):
        for block in slide_blocks("informe"):
            for command, expected in split_commands(block):
                self.assert_in_order(command, expected, clean_lines(run_in_terminal(command)))

    @unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
    def test_code_blocks_in_slides_exist(self):
        known = known_code_lines()
        for block in slide_blocks("codigo|codigochico|codigomini"):
            for line in block:
                if line.strip() and line.strip() != "...":
                    with self.subTest(line=line):
                        self.assertIn(line.strip(), known)


if __name__ == "__main__":
    unittest.main()

Laboratorio

Archivos de partida del laboratorio de la semana.

lab/comparison.txtDescargar lab/comparison.txt
Paso 6: comparen las dos versiones. Corran `make compare` y reemplacen cada línea pendiente.

1. Líneas del módulo en C (grade_book.h + grade_book.c): PENDIENTE
2. Líneas de la clase (grade_book.h + grade_book.cpp): PENDIENTE
3. ¿Qué memoria maneja a mano cada versión? ¿Quién la libera en cada una?
PENDIENTE
4. ¿Qué chequeos de "no hay memoria" del módulo en C desaparecieron, y por qué?
PENDIENTE
5. Paso 5: hagan que average modifique un campo, compilen y peguen aquí la línea
   del error. Después deshagan el cambio.
PENDIENTE
lab/grade_book.cppDescargar lab/grade_book.cpp
// Paso 3: porten aquí c/grade_book.c. Al implementar cada método, pónganle nombre a
// sus parámetros. Nada de malloc, free ni char *.
#include "grade_book.h"

bool GradeBook::add(const std::string&, int) {
    return false;
}

bool GradeBook::set_grade(const std::string&, int) {
    return false;
}

int GradeBook::grade_of(const std::string&) const {
    return -2;
}

bool GradeBook::remove(const std::string&) {
    return false;
}

std::size_t GradeBook::size() const {
    return 99;
}

const std::string& GradeBook::name_at(std::size_t) const {
    return placeholder_;
}

// Paso 5: definan aquí average y best.
lab/grade_book.hDescargar lab/grade_book.h
// Clase GradeBook: registro de notas de estudiantes. Notas de 0 a 20.
// Es el módulo de c/grade_book.h, como clase. Mismas reglas, sin memoria a mano.
#ifndef GRADE_BOOK_H
#define GRADE_BOOK_H

#include <cstddef>
#include <string>

class GradeBook {
public:
    // Agrega un estudiante al final. Devuelve true si pudo, y false si el nombre está
    // vacío o repetido, o si la nota no es válida. En ese caso, no cambia nada.
    bool add(const std::string& name, int grade);

    // Cambia la nota. Devuelve true si pudo, y false si no está o si la nota no es válida.
    bool set_grade(const std::string& name, int grade);

    // Devuelve la nota de un estudiante, o -1 si no está.
    int grade_of(const std::string& name) const;

    // Borra un estudiante, sin cambiar el orden de los demás. Devuelve true si estaba.
    bool remove(const std::string& name);

    // Devuelve cuántos estudiantes hay.
    std::size_t size() const;

    // Devuelve el nombre en la posición index. Exige: index < size().
    // La referencia es del registro: deja de servir si se modifica el registro.
    const std::string& name_at(std::size_t index) const;

    // Paso 5: declaren aquí average y best.

private:
    // Paso 3: reemplacen este campo por los datos del registro.
    std::string placeholder_;
};

#endif
lab/hello.cppDescargar lab/hello.cpp
// Paso 1: comprueben que el entorno compila y corre C++.
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> steps = {"entorno", "compilador", "make"};
    for (const std::string& step : steps) {
        std::cout << step << ": ok\n";
    }
    return 0;
}
lab/test_grade_book.cppDescargar lab/test_grade_book.cpp
// Paso 4: pasen aquí las pruebas de c/test_grade_book.c y agreguen casos borde propios.
// Ya está la primera, para que vean la forma.
#include <cassert>
#include <iostream>

#include "grade_book.h"

void test_starts_empty() {
    GradeBook book;
    assert(book.size() == 0);
    assert(book.grade_of("Ana") == -1);
}

// Falta pasar: test_add_and_get, test_rejects_invalid, test_set, test_remove_keeps_order,
// test_keeps_its_own_copy_of_the_name y test_grows_past_initial_capacity.
// Casos borde propios: agreguen al menos tres.

int main() {
    test_starts_empty();
    std::cout << "mis pruebas: todas pasaron\n";
    return 0;
}
lab/c/driver.cDescargar lab/c/driver.c
// Lee órdenes de la entrada y las aplica al registro. Se compara con ../tests/driver.cpp.
#include <stdio.h>
#include <string.h>

#include "grade_book.h"

int main(void) {
    GradeBook *book = grade_book_new();
    if (book == NULL) {
        return 1;
    }
    char command[16];
    char name[64];
    int grade;
    while (scanf("%15s", command) == 1) {
        if (strcmp(command, "add") == 0 && scanf("%63s %d", name, &grade) == 2) {
            printf("%d\n", grade_book_add(book, name, grade));
        } else if (strcmp(command, "set") == 0 && scanf("%63s %d", name, &grade) == 2) {
            printf("%d\n", grade_book_set(book, name, grade));
        } else if (strcmp(command, "get") == 0 && scanf("%63s", name) == 1) {
            printf("%d\n", grade_book_get(book, name));
        } else if (strcmp(command, "remove") == 0 && scanf("%63s", name) == 1) {
            printf("%d\n", grade_book_remove(book, name));
        } else if (strcmp(command, "list") == 0) {
            printf("%d:", grade_book_size(book));
            for (int i = 0; i < grade_book_size(book); i++) {
                printf(" %s", grade_book_name_at(book, i));
            }
            printf("\n");
        }
    }
    grade_book_free(book);
    return 0;
}
lab/c/grade_book.cDescargar lab/c/grade_book.c
#include "grade_book.h"

#include <stdlib.h>
#include <string.h>

#define INITIAL_CAPACITY 4
#define MAX_GRADE 20

struct entry {
    char *name;
    int grade;
};

struct grade_book {
    struct entry *entries;
    int size;
    int capacity;
};

// Devuelve una copia de text en un bloque nuevo, o NULL si no hay memoria.
static char *copy_text(const char *text) {
    char *copy = malloc(strlen(text) + 1);
    if (copy != NULL) {
        strcpy(copy, text);
    }
    return copy;
}

static int is_valid_grade(int grade) {
    return grade >= 0 && grade <= MAX_GRADE;
}

// Devuelve la posición de name, o -1 si no está.
static int find_index(const GradeBook *book, const char *name) {
    for (int i = 0; i < book->size; i++) {
        if (strcmp(book->entries[i].name, name) == 0) {
            return i;
        }
    }
    return -1;
}

// Asegura un lugar libre. Devuelve 0 si no hay memoria.
static int make_room(GradeBook *book) {
    if (book->size < book->capacity) {
        return 1;
    }
    int capacity = 2 * book->capacity;
    struct entry *entries = realloc(book->entries, capacity * sizeof *entries);
    if (entries == NULL) {
        return 0;
    }
    book->entries = entries;
    book->capacity = capacity;
    return 1;
}

GradeBook *grade_book_new(void) {
    GradeBook *book = malloc(sizeof *book);
    if (book == NULL) {
        return NULL;
    }
    book->entries = malloc(INITIAL_CAPACITY * sizeof *book->entries);
    if (book->entries == NULL) {
        free(book);
        return NULL;
    }
    book->size = 0;
    book->capacity = INITIAL_CAPACITY;
    return book;
}

void grade_book_free(GradeBook *book) {
    if (book == NULL) {
        return;
    }
    for (int i = 0; i < book->size; i++) {
        free(book->entries[i].name);
    }
    free(book->entries);
    free(book);
}

int grade_book_add(GradeBook *book, const char *name, int grade) {
    if (name[0] == '\0' || !is_valid_grade(grade) || find_index(book, name) >= 0) {
        return 0;
    }
    if (!make_room(book)) {
        return 0;
    }
    char *copy = copy_text(name);
    if (copy == NULL) {
        return 0;
    }
    book->entries[book->size].name = copy;
    book->entries[book->size].grade = grade;
    book->size++;
    return 1;
}

int grade_book_set(GradeBook *book, const char *name, int grade) {
    int index = find_index(book, name);
    if (index < 0 || !is_valid_grade(grade)) {
        return 0;
    }
    book->entries[index].grade = grade;
    return 1;
}

int grade_book_get(const GradeBook *book, const char *name) {
    int index = find_index(book, name);
    return index < 0 ? -1 : book->entries[index].grade;
}

int grade_book_remove(GradeBook *book, const char *name) {
    int index = find_index(book, name);
    if (index < 0) {
        return 0;
    }
    free(book->entries[index].name);
    for (int i = index; i < book->size - 1; i++) {
        book->entries[i] = book->entries[i + 1];
    }
    book->size--;
    return 1;
}

int grade_book_size(const GradeBook *book) {
    return book->size;
}

const char *grade_book_name_at(const GradeBook *book, int index) {
    if (index < 0 || index >= book->size) {
        return NULL;
    }
    return book->entries[index].name;
}
lab/c/grade_book.hDescargar lab/c/grade_book.h
// Módulo GradeBook: registro de notas de estudiantes. Notas de 0 a 20.
// Es un tipo opaco: quien lo usa solo tiene punteros y solo llama a estas funciones.
#ifndef GRADE_BOOK_H
#define GRADE_BOOK_H

typedef struct grade_book GradeBook;

// Crea un registro vacío. Devuelve NULL si no hay memoria.
// Owner: quien llama, que debe liberarlo con grade_book_free.
GradeBook *grade_book_new(void);

// Libera el registro y todos sus nombres. Acepta NULL.
void grade_book_free(GradeBook *book);

// Agrega un estudiante al final, con una copia de name. Devuelve 1 si pudo, y 0 si el
// nombre está vacío o repetido, si la nota no es válida o si no hay memoria.
// En ese caso, no cambia nada.
int grade_book_add(GradeBook *book, const char *name, int grade);

// Cambia la nota de un estudiante. Devuelve 1 si pudo, y 0 si no está o si la nota no es válida.
int grade_book_set(GradeBook *book, const char *name, int grade);

// Devuelve la nota de un estudiante, o -1 si no está.
int grade_book_get(const GradeBook *book, const char *name);

// Borra un estudiante, sin cambiar el orden de los demás. Devuelve 1 si estaba y 0 si no.
int grade_book_remove(GradeBook *book, const char *name);

// Devuelve cuántos estudiantes hay.
int grade_book_size(const GradeBook *book);

// Devuelve el nombre en la posición index, o NULL si esa posición no existe.
// Owner: el registro. Deja de servir al borrar ese estudiante o al liberar el registro.
const char *grade_book_name_at(const GradeBook *book, int index);

#endif
lab/c/test_grade_book.cDescargar lab/c/test_grade_book.c
// Pruebas del módulo en C. En el paso 4 las pasan a lab/test_grade_book.cpp.
#include <assert.h>
#include <stdio.h>
#include <string.h>

#include "grade_book.h"

static void test_starts_empty(void) {
    GradeBook *book = grade_book_new();
    assert(book != NULL);
    assert(grade_book_size(book) == 0);
    assert(grade_book_get(book, "Ana") == -1);
    assert(grade_book_name_at(book, 0) == NULL);
    grade_book_free(book);
}

static void test_add_and_get(void) {
    GradeBook *book = grade_book_new();
    assert(grade_book_add(book, "Ana", 14) == 1);
    assert(grade_book_add(book, "Luis", 9) == 1);
    assert(grade_book_size(book) == 2);
    assert(grade_book_get(book, "Ana") == 14);
    assert(grade_book_get(book, "Luis") == 9);
    assert(strcmp(grade_book_name_at(book, 1), "Luis") == 0);
    grade_book_free(book);
}

static void test_rejects_invalid(void) {
    GradeBook *book = grade_book_new();
    assert(grade_book_add(book, "Ana", -1) == 0);
    assert(grade_book_add(book, "Ana", 21) == 0);
    assert(grade_book_add(book, "", 10) == 0);
    assert(grade_book_size(book) == 0);
    assert(grade_book_add(book, "Ana", 0) == 1);
    assert(grade_book_add(book, "Ana", 20) == 0);
    assert(grade_book_get(book, "Ana") == 0);
    assert(grade_book_size(book) == 1);
    grade_book_free(book);
}

static void test_set(void) {
    GradeBook *book = grade_book_new();
    assert(grade_book_add(book, "Ana", 14) == 1);
    assert(grade_book_set(book, "Ana", 20) == 1);
    assert(grade_book_get(book, "Ana") == 20);
    assert(grade_book_set(book, "Ana", 21) == 0);
    assert(grade_book_get(book, "Ana") == 20);
    assert(grade_book_set(book, "Luis", 10) == 0);
    assert(grade_book_size(book) == 1);
    grade_book_free(book);
}

static void test_remove_keeps_order(void) {
    GradeBook *book = grade_book_new();
    assert(grade_book_add(book, "Ana", 14) == 1);
    assert(grade_book_add(book, "Luis", 9) == 1);
    assert(grade_book_add(book, "Rosa", 17) == 1);
    assert(grade_book_remove(book, "Luis") == 1);
    assert(grade_book_remove(book, "Luis") == 0);
    assert(grade_book_size(book) == 2);
    assert(strcmp(grade_book_name_at(book, 0), "Ana") == 0);
    assert(strcmp(grade_book_name_at(book, 1), "Rosa") == 0);
    grade_book_free(book);
}

static void test_keeps_its_own_copy_of_the_name(void) {
    GradeBook *book = grade_book_new();
    char name[] = "Ana";
    assert(grade_book_add(book, name, 14) == 1);
    name[0] = 'E';
    assert(grade_book_get(book, "Ana") == 14);
    assert(grade_book_get(book, "Ena") == -1);
    grade_book_free(book);
}

static void test_grows_past_initial_capacity(void) {
    GradeBook *book = grade_book_new();
    for (int i = 0; i < 100; i++) {
        char name[16];
        snprintf(name, sizeof name, "e%d", i);
        assert(grade_book_add(book, name, i % 21) == 1);
    }
    assert(grade_book_size(book) == 100);
    assert(grade_book_get(book, "e0") == 0);
    assert(grade_book_get(book, "e99") == 99 % 21);
    grade_book_free(book);
}

int main(void) {
    test_starts_empty();
    test_add_and_get();
    test_rejects_invalid();
    test_set();
    test_remove_keeps_order();
    test_keeps_its_own_copy_of_the_name();
    test_grows_past_initial_capacity();
    printf("módulo en C: todas las pruebas pasaron\n");
    return 0;
}
lab/tests/driver.cppDescargar lab/tests/driver.cpp
// Lee órdenes de la entrada y las aplica al registro. Se compara con ../c/driver.c.
#include <cstddef>
#include <iostream>
#include <string>

#include "grade_book.h"

int main() {
    GradeBook book;
    std::string command;
    while (std::cin >> command) {
        std::string name;
        int grade = 0;
        if (command == "add") {
            std::cin >> name >> grade;
            std::cout << (book.add(name, grade) ? 1 : 0) << "\n";
        } else if (command == "set") {
            std::cin >> name >> grade;
            std::cout << (book.set_grade(name, grade) ? 1 : 0) << "\n";
        } else if (command == "get") {
            std::cin >> name;
            std::cout << book.grade_of(name) << "\n";
        } else if (command == "remove") {
            std::cin >> name;
            std::cout << (book.remove(name) ? 1 : 0) << "\n";
        } else if (command == "list") {
            std::cout << book.size() << ":";
            for (std::size_t i = 0; i < book.size(); i++) {
                std::cout << " " << book.name_at(i);
            }
            std::cout << "\n";
        }
    }
    return 0;
}
lab/tests/public_port.cppDescargar lab/tests/public_port.cpp
// Pruebas públicas de los pasos 3 y 4: la clase hace lo mismo que el módulo en C.
#include <cassert>
#include <cstddef>
#include <iostream>
#include <random>
#include <string>
#include <utility>
#include <vector>

#include "grade_book.h"

void test_starts_empty() {
    const GradeBook book{};
    assert(book.size() == 0);
    assert(book.grade_of("Ana") == -1);
}

void test_add_and_get() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.add("Luis", 9));
    assert(book.size() == 2);
    assert(book.grade_of("Ana") == 14);
    assert(book.grade_of("Luis") == 9);
    assert(book.name_at(0) == "Ana");
    assert(book.name_at(1) == "Luis");
}

void test_rejects_invalid() {
    GradeBook book;
    assert(!book.add("Ana", -1));
    assert(!book.add("Ana", 21));
    assert(!book.add("", 10));
    assert(book.size() == 0);
    assert(book.add("Ana", 0));
    assert(book.add("Rosa", 20));
    assert(!book.add("Ana", 20));
    assert(book.grade_of("Ana") == 0);
    assert(book.size() == 2);
}

void test_set_grade() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.set_grade("Ana", 20));
    assert(book.grade_of("Ana") == 20);
    assert(!book.set_grade("Ana", 21));
    assert(!book.set_grade("Ana", -1));
    assert(book.grade_of("Ana") == 20);
    assert(!book.set_grade("Luis", 10));
    assert(book.size() == 1);
}

void test_remove_keeps_order() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.add("Luis", 9));
    assert(book.add("Rosa", 17));
    assert(book.remove("Luis"));
    assert(!book.remove("Luis"));
    assert(book.size() == 2);
    assert(book.name_at(0) == "Ana");
    assert(book.name_at(1) == "Rosa");
    assert(book.remove("Rosa"));
    assert(book.remove("Ana"));
    assert(book.size() == 0);
    assert(book.add("Luis", 3));
    assert(book.grade_of("Luis") == 3);
}

void test_keeps_its_own_copy_of_the_name() {
    GradeBook book;
    std::string name = "Ana";
    assert(book.add(name, 14));
    name[0] = 'E';
    assert(book.grade_of("Ana") == 14);
    assert(book.grade_of("Ena") == -1);
}

void test_copy_is_independent() {
    GradeBook book;
    assert(book.add("Ana", 14));
    GradeBook other = book;
    assert(other.set_grade("Ana", 5));
    assert(other.add("Luis", 9));
    assert(book.grade_of("Ana") == 14);
    assert(book.size() == 1);
}

// Devuelve la posición de name en la referencia, o reference.size() si no está.
std::size_t find_in(const std::vector<std::pair<std::string, int>>& reference, const std::string& name) {
    std::size_t i = 0;
    while (i < reference.size() && reference[i].first != name) {
        i++;
    }
    return i;
}

// Operaciones al azar, con semilla fija, comparadas contra un std::vector de pares.
void test_against_reference() {
    std::mt19937 random(2026);
    GradeBook book;
    std::vector<std::pair<std::string, int>> reference;
    for (int step = 0; step < 3000; step++) {
        std::string name = "e" + std::to_string(random() % 30);
        int grade = static_cast<int>(random() % 25) - 2;
        bool valid = grade >= 0 && grade <= 20;
        std::size_t place = find_in(reference, name);
        bool present = place < reference.size();
        switch (random() % 4) {
            case 0:
                assert(book.add(name, grade) == (valid && !present));
                if (valid && !present) {
                    reference.push_back({name, grade});
                }
                break;
            case 1:
                assert(book.set_grade(name, grade) == (valid && present));
                if (valid && present) {
                    reference[place].second = grade;
                }
                break;
            case 2:
                assert(book.remove(name) == present);
                if (present) {
                    reference.erase(reference.begin() + place);
                }
                break;
            default:
                assert(book.grade_of(name) == (present ? reference[place].second : -1));
        }
        assert(book.size() == reference.size());
        for (std::size_t i = 0; i < reference.size(); i++) {
            assert(book.name_at(i) == reference[i].first);
        }
    }
}

int main() {
    test_starts_empty();
    test_add_and_get();
    test_rejects_invalid();
    test_set_grade();
    test_remove_keeps_order();
    test_keeps_its_own_copy_of_the_name();
    test_copy_is_independent();
    test_against_reference();
    std::cout << "pasos 3 y 4: todas las pruebas pasaron\n";
    return 0;
}
lab/tests/public_stats.cppDescargar lab/tests/public_stats.cpp
// Pruebas públicas del paso 5. No compila hasta que declaren average y best.
#include <cassert>
#include <cmath>
#include <iostream>
#include <string>

#include "grade_book.h"

// Recibe const&: si average o best no son const, esto no compila.
double read_average(const GradeBook& book) {
    return book.average();
}

std::string read_best(const GradeBook& book) {
    return book.best();
}

bool close_to(double a, double b) {
    return std::fabs(a - b) < 1e-9;
}

void test_empty() {
    const GradeBook book{};
    assert(close_to(read_average(book), 0.0));
    assert(read_best(book).empty());
}

void test_average_is_not_integer_division() {
    GradeBook book;
    assert(book.add("Ana", 14));
    assert(book.add("Luis", 15));
    assert(close_to(read_average(book), 14.5));
}

void test_best() {
    GradeBook book;
    assert(book.add("Ana", 0));
    assert(read_best(book) == "Ana");
    assert(book.add("Luis", 12));
    assert(book.add("Rosa", 17));
    assert(book.add("Omar", 3));
    assert(read_best(book) == "Rosa");
    assert(close_to(read_average(book), 8.0));
}

void test_tie_keeps_the_first() {
    GradeBook book;
    assert(book.add("Ana", 18));
    assert(book.add("Luis", 18));
    assert(read_best(book) == "Ana");
}

void test_follows_changes() {
    GradeBook book;
    assert(book.add("Ana", 10));
    assert(book.add("Luis", 20));
    assert(book.remove("Luis"));
    assert(read_best(book) == "Ana");
    assert(close_to(read_average(book), 10.0));
    assert(book.set_grade("Ana", 4));
    assert(close_to(read_average(book), 4.0));
    assert(book.remove("Ana"));
    assert(close_to(read_average(book), 0.0));
    assert(read_best(book).empty());
}

int main() {
    test_empty();
    test_average_is_not_integer_division();
    test_best();
    test_tie_keeps_the_first();
    test_follows_changes();
    std::cout << "paso 5: todas las pruebas pasaron\n";
    return 0;
}

Compilación

Cómo se compilan y ejecutan los ejemplos.

MakefileDescargar Makefile
# Semana 1: de C a C++. Se usa dentro del entorno del curso.
#
#   make           compila los programas de la clase en build/
#   make measure   compila con -O2 y mide tiempos (tarda cerca de medio minuto)
#   make lab       compila el laboratorio en build/lab/
#   make test      corre las pruebas de los ejemplos y de los apuntes
#   make test-lab  prueba su laboratorio; falla hasta que lo terminen
#   make compare   cuenta líneas y memoria a mano de las dos versiones (paso 6)
#   make clean     borra lo generado
#
# Los programas de errors/ tienen errores a propósito. No se compilan con make:
# los compilan las pruebas, y ustedes, con los comandos de los apuntes.

CC = gcc
CXX = g++
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
CXXFLAGS = -Wall -Wextra -std=c++20 -g -fsanitize=address,undefined
FASTFLAGS = -Wall -Wextra -std=c++20 -O2
BUILD = build
LAB_DIR ?= lab
PROGRAMS = hello c_style small_changes namespaces reference_basics swap_both const_forms \
           copy_or_not const_shallow course strings vectors range_for lookup
C_MODULE = lab/c/grade_book.c lab/c/grade_book.h
CPP_MODULE = $(LAB_DIR)/grade_book.cpp $(LAB_DIR)/grade_book.h

.PHONY: all lab measure test test-lab compare clean

all: $(PROGRAMS:%=$(BUILD)/%) $(BUILD)/copy_in_loop $(BUILD)/sizes \
     $(BUILD)/tally_demo $(BUILD)/tally_tests $(BUILD)/tally_c_demo

$(BUILD)/%: %.cpp | $(BUILD)
	$(CXX) $(CXXFLAGS) $< -o $@

$(BUILD)/copy_in_loop: copy_in_loop.cpp value_at.cpp value_at.h | $(BUILD)
	$(CXX) $(CXXFLAGS) copy_in_loop.cpp value_at.cpp -o $@

$(BUILD)/sizes: sizes.cpp tally.cpp tally.h | $(BUILD)
	$(CXX) $(CXXFLAGS) sizes.cpp tally.cpp -o $@

$(BUILD)/tally_demo: tally_demo.cpp tally.cpp tally.h | $(BUILD)
	$(CXX) $(CXXFLAGS) tally_demo.cpp tally.cpp -o $@

$(BUILD)/tally_tests: tally_tests.cpp tally.cpp tally.h | $(BUILD)
	$(CXX) $(CXXFLAGS) tally_tests.cpp tally.cpp -o $@

$(BUILD)/tally_c_demo: tally_c/tally_demo.c tally_c/tally.c tally_c/tally.h | $(BUILD)
	$(CC) $(CFLAGS) tally_c/tally_demo.c tally_c/tally.c -o $@

# Para medir: -O2 y sin sanitizers.
$(BUILD)/fast/lookup: lookup.cpp | $(BUILD)/fast
	$(CXX) $(FASTFLAGS) $< -o $@

$(BUILD)/fast/copy_in_loop: copy_in_loop.cpp value_at.cpp value_at.h | $(BUILD)/fast
	$(CXX) $(FASTFLAGS) copy_in_loop.cpp value_at.cpp -o $@

measure: $(BUILD)/fast/lookup $(BUILD)/fast/copy_in_loop
	python3 measure.py

# Laboratorio: el módulo en C que se les da, y la clase que escriben.
$(BUILD)/lab/hello: lab/hello.cpp | $(BUILD)/lab
	$(CXX) $(CXXFLAGS) $< -o $@

$(BUILD)/lab/test_grade_book_c: lab/c/test_grade_book.c $(C_MODULE) | $(BUILD)/lab
	$(CC) $(CFLAGS) lab/c/test_grade_book.c lab/c/grade_book.c -o $@

$(BUILD)/lab/driver_c: lab/c/driver.c $(C_MODULE) | $(BUILD)/lab
	$(CC) $(CFLAGS) lab/c/driver.c lab/c/grade_book.c -o $@

$(BUILD)/lab/test_grade_book: $(LAB_DIR)/test_grade_book.cpp $(CPP_MODULE) | $(BUILD)/lab
	$(CXX) $(CXXFLAGS) -I$(LAB_DIR) $(LAB_DIR)/test_grade_book.cpp $(LAB_DIR)/grade_book.cpp -o $@

$(BUILD)/lab/public_port: lab/tests/public_port.cpp $(CPP_MODULE) | $(BUILD)/lab
	$(CXX) $(CXXFLAGS) -I$(LAB_DIR) lab/tests/public_port.cpp $(LAB_DIR)/grade_book.cpp -o $@

$(BUILD)/lab/driver: lab/tests/driver.cpp $(CPP_MODULE) | $(BUILD)/lab
	$(CXX) $(CXXFLAGS) -I$(LAB_DIR) lab/tests/driver.cpp $(LAB_DIR)/grade_book.cpp -o $@

$(BUILD) $(BUILD)/fast $(BUILD)/lab:
	mkdir -p $@

lab: $(BUILD)/lab/hello $(BUILD)/lab/test_grade_book_c $(BUILD)/lab/driver_c \
     $(BUILD)/lab/test_grade_book $(BUILD)/lab/public_port $(BUILD)/lab/driver

# Si existe solution/, test_lab.py también comprueba la solución del laboratorio.
test: all
	LAB_DIR=solution python3 -m unittest discover -p 'test_*.py'

test-lab: all
	LAB_DIR=$(LAB_DIR) python3 -m unittest -v test_lab

compare:
	@echo "módulo en C:"
	@wc -l lab/c/grade_book.h lab/c/grade_book.c
	@echo "clase en C++ ($(LAB_DIR)):"
	@wc -l $(LAB_DIR)/grade_book.h $(LAB_DIR)/grade_book.cpp
	@echo "llamadas a malloc, realloc y free en C:"
	@cat lab/c/grade_book.c | sed 's://.*::' | grep -E -c '\b(malloc|realloc|free)\(' || true
	@echo "llamadas a malloc, realloc y free en C++:"
	@cat $(LAB_DIR)/grade_book.cpp | sed 's://.*::' | grep -E -c '\b(malloc|realloc|free)\(' || true

clean:
	rm -rf $(BUILD) __pycache__