DevTools

Cheatsheet C++

Linguagem de alto desempenho com controlo de memória

Volver a los lenguajes
C++
100 tarjetas encontradas
Categorías:
Versiones:

Sintaxe Básica


10 cards
Variables y tipos
int edad = 30;
double precio = 9.99;
char letra = 'A';
bool activo = true;
std::string nombre = "Ana";
auto x = 42;          // tipo inferido (int)
unsigned int contador = 0;
long long grande = 9999999999LL;

int, double, char, bool y std::string son los tipos más usados. auto deja que el compilador infiera el tipo. unsigned solo acepta valores positivos.

Entrada y salida (I/O)
#include <iostream>

int main() {
    std::cout << "Nombre: ";
    std::string nombre;
    std::getline(std::cin, nombre);  // linea completa

    int edad;
    std::cout << "Edad: ";
    std::cin >> edad;

    std::cout << nombre << " tem " << edad << " anios\n";
    return 0;
}

std::cout imprime, std::cin lee. << encadena salidas. std::getline lee una línea entera (con espacios). std::endl hace flush — prefiere "\n" para rendimiento.

using y typedef
// Alias de tipo (moderno):
using Vec = std::vector<int>;
using Matrix = std::vector<std::vector<double>>;
using Callback = std::function<void(int)>;

Vec v = {1, 2, 3};

// typedef (legado):
typedef unsigned long ulong;

// Template alias:
template <typename T>
using Ptr = std::shared_ptr<T>;

Ptr<int> p = std::make_shared<int>(42);

using crea alias de tipos (más legible que typedef). Admite templates. Útil para simplificar tipos anchos como std::vector<std::vector<double>>. Estándar en C++ moderno.

Tipos de tamaño fijo
#include <cstdint>

int8_t   a = -128;      // 1 byte
uint8_t  b = 255;       // 1 byte sem señal
int16_t  c = -32768;    // 2 bytes
int32_t  d = 2000000;   // 4 bytes
int64_t  e = 9e18;      // 8 bytes
uint64_t f = 18e18;     // 8 bytes sem señal
size_t   n = 0;         // tamano (unsigned)

Los tipos de <cstdint> garantizan un tamaño exacto en cualquier plataforma. int32_t = 4 bytes siempre. size_t se usa para tamaños e índices. Prefiérelos cuando el tamaño importa.

Strings (std::string)
#include <string>

std::string s = "Hola Mundo";
s.length();          // 10
s.substr(4, 5);     // "Mundo"
s.find("Mundo");    // 4 (posición)
s.replace(0, 3, "Oi"); // "Oi Mundo"
s += "!!";           // concatenación
s.empty();           // false
s.compare("Oi Mundo!!"); // 0 se igual

// Formato (C++20):
std::string r = std::format("{} tem {}", nombre, edad);

std::string es dinámica y segura. substr extrae, find búsqueda (devuelve npos si no encuentra). std::format (C++20) formatea como Python. Evita las C-strings (char*).

Estructura de un programa
#include <iostream>
#include <vector>
#include <string>

// Declaraciones anticipadas:
void procesar(const std::string& s);

int main() {
    std::vector<std::string> datos = {"a", "b"};

    for (const auto& d : datos) {
        procesar(d);
    }

    return 0;  // éxito
}

void procesar(const std::string& s) {
    std::cout << s << "\n";
}

Todo programa C++ tiene main() como punto de entrada. #include importa headers. return 0 indica éxito. Las funciones pueden declararse antes y definirse después. Compila con g++ main.cpp -o app.

Constantes
const int MAX = 100;          // inmutable em runtime
constexpr double PI = 3.14159; // evaluada em compilación
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

inline constexpr int TAM = 1024;

// enum class (tipado, sem contaminación):
enum class Color { Rojo, Verde, Azul };
Color c = Color::Verde;

const impide la modificación. constexpr se evalúa en tiempo de compilación (más rápido). enum class es tipado y no contamina el namespace (usa Color::Verde en vez de solo Verde).

Conversiones de tipo
// Numérico → string:
std::string s = std::to_string(42);
std::string d = std::to_string(3.14);

// String → numérico:
int n = std::stoi("123");
double x = std::stod("3.14");
long l = std::stol("999999");

// Cast explícito:
int i = static_cast<int>(3.99);  // 3 (trunca)
double dd = static_cast<double>(7) / 2; // 3.5

// dynamic_cast (polimorfismo):
Base* b = dynamic_cast<Derivada*>(ptr);

std::to_string convierte numero→string. std::stoi/std::stod hacen lo inverso. static_cast es el cast seguro en compilación. dynamic_cast verifica en runtime (herencia).

Operadores
// Aritméticos:
int r = 10 + 3;  // 13
int q = 10 / 3;  // 3 (entero!)
int m = 10 % 3;  // 1 (resto)

// Comparación:
bool b = (5 == 5);  // true
bool c = (5 != 3);  // true

// Lógicos:
bool d = (true && false);  // false
bool e = (true || false);  // true

// Bitwise:
int f = 0b1010 & 0b1100;  // 0b1000
int g = 0b1010 | 0b1100;  // 1110

Operadores aritméticos, de comparación, lógicos y bitwise. Cuidado: 10 / 3 = 3 (división entera). % da el resto. && y || son de cortocircuito.

Arrays
// Array fixo (C-style):
int arr[5] = {1, 2, 3, 4, 5};
arr[0];  // 1

// std::array (preferir, C++11):
#include <array>
std::array<int, 5> a = {10, 20, 30, 40, 50};
a.size();   // 5
a.front();  // 10
a.back();   // 50

// Percorrer:
for (int x : a) std::cout << x << " ";

std::array es fijo pero seguro (conoce su tamaño). Los arrays C (int arr[5]) no conocen su propio tamaño. Para tamaño dinámico, usa std::vector. std::array no reserva en el heap.

Controlo de Fluxo


10 cards
if / else if / else
if (edad >= 18) {
    std::cout << "adulto\n";
} else if (edad >= 13) {
    std::cout << "adolescente\n";
} else {
    std::cout << "niño\n";
}

// Sem chaves (solo 1 instrucción):
if (x > 0) std::cout << "positivo";

if evalúa una condición booleana. else if encadena comprobaciones. Las llaves son opcionales para 1 instrucción pero siempre recomendadas. C++ no tiene elif — usa else if.

while y do-while
// while: verifica ANTES de ejecutar
int n = 5;
while (n > 0) {
    std::cout << n-- << " ";
}

// do-while: executa PELO MENOS 1 vez
int opción;
do {
    std::cout << "Menu: ";
    std::cin >> opción;
} while (opción != 0);

while repite mientras la condición sea verdadera. do-while garantiza al menos 1 ejecución (verifica al final). Ideal para menús y entrada con validación.

Loops anidados
// Matriz 3x3:
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        std::cout << matriz[i][j] << " ";
    }
    std::cout << "\n";
}

// Com range-for:
for (const auto& linea : matriz) {
    for (int val : linea) {
        std::cout << val << " ";
    }
    std::cout << "\n";
}

Los bucles anidados recorren estructuras 2D (matrices, grids). El bucle externo controla las filas, el interno las columnas. Un range-for anidado es más legible. Cuidado con el rendimiento en O(n²).

switch / case
switch (dia) {
    case 1:
        std::cout << "Segunda";
        break;
    case 2:
    case 3:
        std::cout << "Ter/Qua";
        break;
    default:
        std::cout << "Outro";
        break;
}

switch compara con constantes enteras. break evita el fall-through. Los cases pueden compartir código (2 y 3). default es el fallback. Más rápido que if-else para muchos casos (tabla de saltos).

break y continue
for (int i = 0; i < 100; i++) {
    if (i == 50) break;      // sai do ciclo
    if (i % 2 == 0) continue; // salta iteración
    std::cout << i << " ";   // solo impares hasta 49
}

// break em switch:
switch (x) {
    case 1: fazer(); break;  // sem break = fall-through
}

break termina el bucle inmediatamente. continue salta a la siguiente iteración. En un switch, break evita ejecutar el case siguiente. Úsalos con moderación para la legibilidad.

goto y labels
// Uso legítimo: salir de loops anidados
for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        if (encontrado(i, j)) {
            goto fin;
        }
    }
}
fin:
std::cout << "Búsqueda terminada\n";

// EVITAR: vuelve código ilegible
// Preferir: break com flag, ou funcion com return

goto salta a un label. El único uso aceptable: salir de bucles profundamente anidados. En general, evítalo — hace que el flujo sea difícil de seguir. Prefiere return, break o refactorizar en una función.

for clásico
for (int i = 0; i < 10; i++) {
    std::cout << i << " ";
}

// Múltiples variables:
for (int i = 0, j = 10; i < j; i++, j--) {
    std::cout << i << "," << j << " ";
}

// Decrescente:
for (int i = 9; i >= 0; i--) {
    std::cout << i << " ";
}

for tiene 3 partes: inicialización, condición, incremento. i++ es postincremento. Acepta múltiples variables separadas por comas. Ideal cuando conoces el numero de iteraciones.

Operador ternario
// condición ? valor_se_true : valor_se_false
int mayor = (a > b) ? a : b;

std::string estado = activo ? "online" : "offline";

// Encadeado (evitar, pouco legible):
char nota = (p >= 90) ? 'A'
          : (p >= 80) ? 'B'
          : (p >= 70) ? 'C' : 'F';

// Com cout:
std::cout << (par ? "par" : "impar");

El operador ?: es un if-else en una expresión. Devuelve un valor. Ideal para asignaciones simples. Evita encadenarlo (ilegible). Los paréntesis en (cond) son opcionales pero ayudan.

Range-based for (C++11)
std::vector<int> nums = {10, 20, 30};

// Por valor (copia):
for (int x : nums) std::cout << x;

// Por referencia (modifica):
for (int& x : nums) x *= 2;

// Por const referencia (eficiente, sem copia):
for (const auto& item : nums) {
    std::cout << item << "\n";
}

for (auto& x : container) recorre sin índice. const auto& evita copias (esencial para strings/objetos). auto& permite modificar. Más legible que los iteradores. Prefiérelo siempre que sea posible.

if con inicialización (C++17)
// Variable com escopo limitado ao if:
if (auto it = mapa.find("clave"); it != mapa.end()) {
    std::cout << it->second;
}
// 'it' no existe aqui

if (int n = std::stoi(input); n > 0) {
    procesar(n);
}

// También em switch:
switch (auto [ok, val] = tentar(); ok) {
    case true:  usar(val); break;
    case false: error();    break;
}

if (init; condición) declara una variable con ámbito limitado al bloque. Evita contaminar el ámbito exterior. Muy usado con find() y conversiones. También funciona en switch. Característica de C++17.

Funções


10 cards
Función con retorno
int sumar(int a, int b) {
    return a + b;
}

void saludo(const std::string& nombre) {
    std::cout << "Hola, " << nombre << "!\n";
}

int main() {
    int r = sumar(3, 7);      // 10
    saludo("Ana");           // "Hola, Ana!"
}

Las funciones tienen un tipo de retorno, un nombre y parámetros. void = sin retorno. return devuelve el valor y termina. Los parámetros se pasan por valor (copia) por defecto.

Lambda expressions
// Lambda simples:
auto doble = [](int x) { return x * 2; };
doble(5);  // 10

// Com captura por referencia:
int total = 0;
auto acumular = [&total](int x) { total += x; };
acumular(10);
acumular(20);  // total = 30

// Como comparador:
std::sort(v.begin(), v.end(),
    [](int a, int b) { return a > b; });

// Captura por valor:
auto copiar = [x]() { return x; };

Lambda = función anónima inline. [] es la captura: [&] por referencia, [=] por valor, [x] una variable específica. Ideal para callbacks y comparadores en algoritmos STL.

Funciones con retorno auto
// Retorno inferido (C++14):
auto dividir(double a, double b) {
    return a / b;  // retorna double
}

// Trailing return type:
auto sumar(int a, int b) -> int {
    return a + b;
}

// Útil com tipos complexos:
auto getMap() {
    return std::map<std::string, std::vector<int>>{};
}

// decltype(auto) preserva referencia:
decltype(auto) primero(std::vector<int>& v) {
    return v[0];  // retorna int&
}

auto como retorno deja que el compilador infiera. -> tipo (trailing) es explícito. decltype(auto) preserva referencias y cv-qualifiers. Útil para tipos anchos y templates.

Paso por referencia
// Modifica o original:
void incrementar(int& x) {
    x++;
}

// Solo lectura (sem copia):
void imprimir(const std::string& s) {
    std::cout << s << "\n";
    // s = "error";  // ERROR: es const
}

int n = 5;
incrementar(n);  // n = 6

& pasa por referencia (sin copia, modifica el original). const & es de solo lectura pero evita una copia — úsalo siempre para strings, vectors y objetos. Esencial para el rendimiento.

inline y constexpr
// inline: sugerencia para evitar overhead de llamada
inline int abs(int x) {
    return x < 0 ? -x : x;
}

// constexpr: evaluada em tiempo de compilación
constexpr int cuadrado(int n) {
    return n * n;
}

constexpr int ARR[5] = {
    cuadrado(1), cuadrado(2), cuadrado(3),
    cuadrado(4), cuadrado(5)
};
// ARR = {1, 4, 9, 16, 25} — tudo em compilación

inline sugiere al compilador expandir la función (evita el overhead de llamada). constexpr garantiza la evaluación en compilación si los args son constantes. Los arrays y templates se benefician de constexpr.

Argumentos variádicos (C-style)
#include <cstdarg>

double media(int n, ...) {
    va_list args;
    va_start(args, n);
    double suma = 0;
    for (int i = 0; i < n; i++) {
        suma += va_arg(args, double);
    }
    va_end(args);
    return suma / n;
}

media(3, 10.0, 20.0, 30.0);  // 20.0

va_list/va_arg permiten un numero variable de argumentos (estilo C). Poco seguro (sin type-check). En C++ moderno, prefiere los variadic templates o std::initializer_list.

Parámetros por defecto
void conectar(const std::string& host = "localhost",
              int puerto = 3306,
              bool ssl = false) {
    std::cout << host << ":" << puerto
              << (ssl ? " (SSL)" : "") << "\n";
}

conectar();                    // localhost:3306
conectar("db.local");          // db.local:3306
conectar("db.local", 5432);    // db.local:5432
conectar("db.local", 5432, true);

Los parámetros con = valor son opcionales en la llamada. Deben estar al final de la lista. Las llamadas pueden omitir los últimos argumentos. Evita sobrecargas innecesarias.

noexcept y especificaciones
// Promete no lanzar excepciones:
void swap(int& a, int& b) noexcept {
    int tmp = a;
    a = b;
    b = tmp;
}

// Condicional:
template <typename T>
void mover(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T tmp = std::move(a);
    a = std::move(b);
    b = std::move(tmp);
}

// Verificar:
static_assert(noexcept(swap(a, b)));

noexcept promete que la función no lanza excepciones. Permite optimizaciones (move en vez de copy en vector). Si se viola, llama a std::terminate. Esencial en move constructors y swap.

Overloading de funciones
int suma(int a, int b) { return a + b; }
double suma(double a, double b) { return a + b; }
std::string suma(const std::string& a, const std::string& b) {
    return a + b;
}

suma(3, 5);          // int → 8
suma(3.1, 2.9);     // double → 6.0
suma("Hola", "!");   // string → "Hola!"

Overloading = mismo nombre, parámetros diferentes. El compilador elige por el tipo de los argumentos. No puede diferir solo en el retorno. Muy usado en la STL (p. ej. std::abs para int/double).

Punteros de función
int suma(int a, int b) { return a + b; }
int mult(int a, int b) { return a * b; }

// Puntero de funcion:
int (*op)(int, int) = &suma;
op(3, 4);  // 7
op = &mult;
op(3, 4);  // 12

// Com std::function (flexível):
#include <functional>
std::function<int(int,int)> f = suma;
f = [](int a, int b) { return a - b; };

Los punteros de función almacenan las direcciones de funciones. Sintaxis: tipo (*nombre)(params). std::function es más flexible (acepta lambdas, binds). Se usa para callbacks y estrategias.

Classes e Objetos


10 cards
Clase con constructor
class Persona {
private:
    std::string nombre;
    int edad;

public:
    // Constructor com lista de inicialización:
    Persona(std::string n, int i)
        : nombre(std::move(n)), edad(i) {}

    // Getters:
    const std::string& getName() const { return nombre; }
    int getIdade() const { return edad; }
};

Persona p("Ana", 30);

La lista de inicialización (: nombre(n), edad(i)) es más eficiente que asignar en el cuerpo. std::move evita copiar strings. const en el método promete no modificar el objeto.

Sobrecarga de operadores
class Ponto {
public:
    double x, y;

    Ponto operator+(const Ponto& o) const {
        return {x + o.x, y + o.y};
    }

    bool operator==(const Ponto& o) const {
        return x == o.x && y == o.y;
    }

    friend std::ostream& operator<<(std::ostream& os, const Ponto& p) {
        return os << "(" << p.x << ", " << p.y << ")";
    }
};

Sobrecarga operator+, ==, << para un uso natural. const al final = no modifica. friend accede a los privados (para <<). Permite p1 + p2 y cout << p.

Constructor de copia y move
class Buffer {
    std::unique_ptr<int[]> datos;
    size_t tam;
public:
    // Move constructor:
    Buffer(Buffer&& o) noexcept
        : datos(std::move(o.datos)), tam(o.tam) {
        o.tam = 0;
    }

    // Copy constructor:
    Buffer(const Buffer& o) : tam(o.tam),
        datos(std::make_unique<int[]>(o.tam)) {
        std::copy(o.datos.get(), o.datos.get() + tam, datos.get());
    }
};

El move constructor (&&) transfiere recursos sin copiar — rápido. El copy constructor (const &) duplica los datos. noexcept en el move es esencial para que vector use move al redimensionar.

Herencia
class Animal {
protected:
    std::string nombre;
public:
    Animal(std::string n) : nombre(std::move(n)) {}
    virtual void hablar() const = 0;  // puro virtual
    virtual ~Animal() = default;
};

class Perro : public Animal {
public:
    Perro(std::string n) : Animal(std::move(n)) {}
    void hablar() const override {
        std::cout << nombre << ": Au!\n";
    }
};

public hereda la interfaz. virtual permite el override. = 0 lo hace puro virtual (clase abstracta). override confirma que sobrescribe un método de la base. El destructor virtual es obligatorio en clases base.

struct vs class
// struct: membros públicos por patrón
struct Ponto {
    double x, y;  // público
};

// class: membros privados por patrón
class Cuenta {
    double saldo;  // privado
public:
    void depositar(double v) { saldo += v; }
};

// Convenção:
// struct → datos simples (POD, DTOs)
// class  → lógica + encapsulamento

La única diferencia: struct tiene los miembros públicos por defecto, class privados. Convención: struct para datos simples (sin invariantes), class para objetos con lógica. Técnicamente intercambiables.

Interfaces (clase abstracta)
class ISerializable {
public:
    virtual ~ISerializable() = default;
    virtual std::string toJSON() const = 0;
    virtual void fromJSON(const std::string& json) = 0;
};

class User : public ISerializable {
public:
    std::string toJSON() const override {
        return R"({"nombre": ")" + nombre + R"("})";
    }
    void fromJSON(const std::string& json) override {
        // parse...
    }
};

Una clase abstracta = interfaz: solo métodos puros virtuales (= 0). No puede instanciarse. Las clases derivadas implementan todo. El destructor virtual es obligatorio. Equivalente a las interfaces de Java/C#.

Polimorfismo
std::vector<std::unique_ptr<Animal>> animales;
animales.push_back(std::make_unique<Perro>("Rex"));
animales.push_back(std::make_unique<Gato>("Miau"));

// Dispatch dinâmico:
for (const auto& a : animales) {
    a->hablar();  // chama o override correto
}

// Downcast seguro:
if (auto* perro = dynamic_cast<Perro*>(animales[0].get())) {
    perro->abanarCauda();
}

Polimorfismo = llamar al método correcto vía un puntero base. Requiere virtual. unique_ptr gestiona la memoria automáticamente. dynamic_cast hace un downcast seguro (devuelve nullptr si falla).

Miembros static
class Contador {
    static int total;  // partilhado por todos
public:
    Contador() { total++; }
    ~Contador() { total--; }
    static int getTotal() { return total; }
};

// Definir fora da clase:
int Contador::total = 0;

Contador a, b, c;
Contador::getTotal();  // 3

Los miembros static pertenecen a la clase, no al objeto. Compartidos por todas las instancias. Se accede vía Clase::miembro. Debe definirse fuera de la clase. Ideal para contadores y constantes.

Regla de los 5 (Rule of Five)
class Recurso {
    int* datos;
public:
    Recurso(int n) : datos(new int[n]) {}
    ~Recurso() { delete[] datos; }                    // destrutor
    Recurso(const Recurso& o);                        // copia
    Recurso& operator=(const Recurso& o);             // asignación copia
    Recurso(Recurso&& o) noexcept;                    // move
    Recurso& operator=(Recurso&& o) noexcept;         // asignación move
};
// Ou: Rule of Zero (usa smart pointers!)

Si defines uno, probablemente necesitas los 5: destructor, copy ctor, copy assign, move ctor, move assign. Rule of Zero: usa unique_ptr/shared_ptr y no definas ninguno.

Friend y encapsulamiento
class Secreto {
    int código = 42;
    friend class Hacker;           // clase amiga
    friend void revelar(Secreto&); // funcion amiga
};

class Hacker {
public:
    void aceder(Secreto& s) {
        std::cout << s.código;  // OK: es friend
    }
};

void revelar(Secreto& s) {
    std::cout << s.código;  // OK: es friend
}

friend da acceso a los miembros privados. Rompe el encapsulamiento — úsalo con moderación. Útil para los operadores <<, tests unitarios y clases tightly-coupled. No es transitivo ni hereditario.

STL Contentores


10 cards
std::vector
#include <vector>

std::vector<int> v = {1, 2, 3};
v.push_back(4);       // adiciona no fin
v.size();             // 4
v[0];                 // 1 (sem bounds check)
v.at(0);              // 1 (com bounds check)
v.front(); v.back();  // primero / último
v.pop_back();         // remove último
v.erase(v.begin() + 1);  // remove índice 1
v.clear();            // remove todos
v.empty();            // true

vector es el contenedor más usado — un array dinámico contiguo. push_back añade en O(1) amortizado. at() verifica los límites (lanza una excepción). erase elimina por iterador. Reserva con reserve() para el rendimiento.

std::list
#include <list>

std::list<int> l = {3, 1, 4};
l.push_front(0);
l.push_back(5);
l.sort();            // ordena (no usa std::sort)
l.unique();          // remove duplicados consecutivos
l.reverse();

// Inserción/eliminación O(1) em qualquer posición
// (com iterador):
auto it = std::next(l.begin(), 2);
l.insert(it, 99);
l.erase(it);

list es una lista doblemente enlazada. La inserción/eliminación es O(1) con un iterador. Sin acceso por índice. sort() y splice() son métodos propios. Úsala para inserciones frecuentes en el medio. Raramente necesaria.

Iteradores
std::vector<int> v = {10, 20, 30};

// Iterador clássico:
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}

// reverse_iterator:
for (auto it = v.rbegin(); it != v.rend(); ++it) {
    std::cout << *it << " ";  // 30 20 10
}

// Avançar N posiciones:
auto it = v.begin();
std::advance(it, 2);  // aponta para 30
auto dist = std::distance(v.begin(), it);  // 2

Los iteradores apuntan a los elementos. begin()/end() delimitan el rango. * desreferencia. rbegin/rend para el orden inverso. std::advance mueve N posiciones. Son la base de todos los algoritmos de la STL.

std::map y unordered_map
#include <map>
#include <unordered_map>

// map: ordenado por clave (árbol)
std::map<std::string, int> notas;
notas["ana"] = 95;
notas["joao"] = 88;

// unordered_map: hash (mais rápido)
std::unordered_map<std::string, int> cache;
cache["key"] = 42;

// Acesso seguro:
if (auto it = notas.find("ana"); it != notas.end()) {
    std::cout << it->second;  // 95
}

map mantiene las claves ordenadas (O(log n)). unordered_map usa un hash (O(1) medio). operator[] crea si no existe. find + end() para un acceso seguro. at() lanza una excepción si no existe.

std::stack y std::queue
#include <stack>
#include <queue>

// Stack (LIFO):
std::stack<int> pilha;
pilha.push(1); pilha.push(2); pilha.push(3);
pilha.top();    // 3
pilha.pop();    // remove 3
pilha.size();   // 2

// Queue (FIFO):
std::queue<int> fila;
fila.push(1); fila.push(2); fila.push(3);
fila.front();   // 1
fila.back();    // 3
fila.pop();     // remove 1

stack = LIFO (último en entrar, primero en salir). queue = FIFO (primero en entrar, primero en salir). Son adaptadores (usan deque por debajo). Sin iteradores. Ideales para DFS/BFS y undo/redo.

std::array y std::span
#include <array>
#include <span>  // C++20

std::array<int, 5> arr = {1, 2, 3, 4, 5};
arr.size();   // 5 (compile-time)
arr.fill(0);  // todos a 0

// span: vista sobre secuencia contigua (C++20)
void procesar(std::span<int> datos) {
    for (int x : datos) std::cout << x;
}

procesar(arr);              // de array
std::vector<int> v = {1,2};
procesar(v);                // de vector
procesar({arr.data(), 3});  // sub-range

std::array es fijo en tamaño (compile-time), sin heap. std::span (C++20) es una vista ligera sobre datos contiguos (array, vector, C-array). Sin ownership. Sustituye el par puntero+tamaño.

std::set y unordered_set
#include <set>
#include <unordered_set>

std::set<int> s = {3, 1, 4, 1, 5};
// s = {1, 3, 4, 5} — únicos e ordenados

s.insert(9);
s.count(3);    // 1 (existe)
s.erase(1);
s.size();      // 4

// unordered_set: hash, sem ordem
std::unordered_set<std::string> tags;
tags.insert("cpp");
tags.contains("cpp");  // true (C++20)

set guarda valores únicos y ordenados. unordered_set es más rápido (hash). insert ignora los duplicados. contains (C++20) verifica la presencia. Ideal para verificar la existencia y eliminar duplicados.

std::priority_queue
#include <queue>

// Max-heap (mayor no encabezado):
std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
pq.top();   // 4 (mayor)
pq.pop();   // remove 4

// Min-heap:
std::priority_queue<int, std::vector<int>,
    std::greater<int>> min_pq;
min_pq.push(3); min_pq.push(1);
min_pq.top();  // 1 (menor)

priority_queue es un heap — el tope es siempre el mayor (max-heap). Para un min-heap usa std::greater. push/pop en O(log n). Ideal para scheduling, Dijkstra, top-K elementos.

std::deque
#include <deque>

std::deque<int> d = {2, 3, 4};
d.push_front(1);   // {1, 2, 3, 4}
d.push_back(5);    // {1, 2, 3, 4, 5}
d.pop_front();     // remove 1
d.pop_back();      // remove 5
d[2];              // acesso por índice

// vs vector:
// + push_front O(1)
// - sem memoria contigua
// - iteradores mais lentos

deque = cola de doble extremo. push_front y push_back en O(1). Sin memoria contigua (bloques). Úsala cuando necesites insertar/eliminar en ambos extremos. Es la base de stack y queue.

std::pair y std::tuple
#include <utility>
#include <tuple>

auto p = std::make_pair("Ana", 30);
p.first;   // "Ana"
p.second;  // 30

// Structured binding (C++17):
auto [nombre, edad] = p;

// Tuple (mais de 2):
auto t = std::make_tuple(1, "ok", 3.14);
auto [a, b, c] = t;
std::get<0>(t);  // 1

// tie (atribuir a existentes):
int x; std::string s;
std::tie(x, s) = std::make_tuple(42, "hi");

pair agrupa 2 valores. tuple agrupa N. structured binding desestructura con elegancia. std::get<N> accede por índice. Los retornos múltiples de funciones usan pair/tuple.

Ponteiros e Memória


10 cards
Punteros básicos
int x = 42;
int* p = &x;      // p guarda dirección de x
std::cout << *p;  // 42 (desreferenciação)
*p = 100;         // modifica x
std::cout << x;   // 100

int* nulo = nullptr;  // puntero nulo (C++11)

// Puntero para puntero:
int** pp = &p;
**pp;  // 42 → 100

& obtiene la dirección. * desreferencia (accede al valor). nullptr es el puntero nulo moderno (sustituye a NULL). Inicializa siempre los punteros. Verifica antes de desreferenciar.

std::shared_ptr
#include <memory>

auto sp1 = std::make_shared<Recurso>();
{
    auto sp2 = sp1;  // shares (ref count = 2)
    auto sp3 = sp1;  // ref count = 3
}
// sp2, sp3 destruidos → ref count = 1

sp1.use_count();  // 1
sp1.reset();      // releases (ref count = 0)

// Custo: atomic ref counting (mais lento que unique)
// Usar quando varios owners necesitan do mesmo objeto

shared_ptr = posesión compartida con reference counting. Libera cuando el count = 0. Es copiable (incrementa el count). Tiene overhead de atomic ops. Úsalo cuando varios dueños comparten el mismo recurso. Prefiere unique_ptr si es posible.

Arrays dinámicos y C-arrays
// C-array (evitar):
int arr[5] = {1, 2, 3, 4, 5};
int* dyn = new int[n];
delete[] dyn;

// Moderno (preferir):
std::vector<int> v(n);           // dinâmico
std::array<int, 5> a = {};      // fixo, stack
auto up = std::make_unique<int[]>(n);  // heap, RAII

// Puntero + tamano → span (C++20):
void procesar(std::span<int> datos);
procesar(v);
procesar({arr, 5});

Evita los C-arrays y new[]. vector para tamaño dinámico. std::array para fijo. make_unique<T[]> si necesitas heap con ownership. std::span (C++20) como parámetro universal.

Referencias
int x = 10;
int& ref = x;   // alias para x
ref = 20;       // x ahora es 20

// No pode ser nula ni reasignada:
// int& r;      // ERROR: debe inicializar
// &ref = &y;   // imposible

// Em parámetros:
void trocar(int& a, int& b) {
    std::swap(a, b);
}

// Referencia constante:
const int& cr = x;  // solo lectura

Una referencia = alias para una variable existente. Debe inicializarse. No puede ser nula ni reasignarse. const & impide la modificación. Prefiere las referencias a los punteros cuando null no es una opción.

std::weak_ptr
#include <memory>

std::weak_ptr<Recurso> débil;
{
    auto fuerte = std::make_shared<Recurso>();
    débil = fuerte;

    if (auto sp = débil.lock()) {
        sp->usar();  // OK: ainda existe
    }
}
// fuerte destruido

if (débil.expired()) {
    // objeto ya no existe
}
auto sp = débil.lock();  // nullptr

weak_ptr observa sin poseer (no incrementa el ref count). Resuelve los ciclos de referencia. lock() intenta obtener un shared_ptr (nullptr si expiró). expired() verifica. Esencial para caches y observers.

lock_guard y recursos
#include <mutex>

std::mutex mtx;
int partilhado = 0;

void incrementar() {
    std::lock_guard<std::mutex> lock(mtx);
    partilhado++;
    // unlock automático ao salir do scope
}

// C++17 (deducción de tipo):
void outro() {
    std::lock_guard lock(mtx);
    partilhado += 10;
}

// scoped_lock (múltiples mutexes):
void multi() {
    std::scoped_lock lock(mtx1, mtx2);
}

lock_guard aplica RAII a los mutexes: lock en el constructor, unlock en el destructor. Imposible olvidar el unlock. scoped_lock (C++17) hace lock de múltiples mutexes sin deadlock. El patrón estándar para thread safety.

new y delete (evitar)
// Asignación manual:
int* p = new int(42);
delete p;         // libertar
p = nullptr;      // evitar dangling

// Array:
int* arr = new int[100];
delete[] arr;     // delete[] para arrays!

// Objeto:
auto* obj = new Persona("Ana");
delete obj;

// ⚠️ Riesgos: memory leaks, double free,
// dangling pointers. PREFERIR smart pointers!

new asigna en el heap, delete libera. delete[] para arrays. Si olvidas el delete = memory leak. Si haces delete dos veces = crash. En C++ moderno, usa siempre unique_ptr/shared_ptr.

RAII
// RAII: Resource Acquisition Is Initialization
class Fichero {
    FILE* f;
public:
    Fichero(const char* path) {
        f = fopen(path, "r");
        if (!f) throw std::runtime_error("error");
    }
    ~Fichero() {
        if (f) fclose(f);  // liberta no destrutor
    }
    // No copiable:
    Fichero(const Fichero&) = delete;
};

// Uso:
{
    Fichero f("datos.txt");
    // usar f...
}  // fclose automático aqui!

RAII = adquirir el recurso en el constructor, liberarlo en el destructor. Garantiza la limpieza incluso con excepciones. Es la base de unique_ptr, lock_guard, fstream. El patrón más importante en C++.

std::unique_ptr
#include <memory>

// Propiedad exclusiva (no copiable):
auto p = std::make_unique<Persona>("Ana", 30);
p->getName();  // "Ana"

// Transferir posse (move):
auto p2 = std::move(p);
// p ahora es nullptr!

// Array:
auto arr = std::make_unique<int[]>(100);
arr[0] = 42;

// Liberta automaticamente ao salir do scope
// Sem necesidad de delete!

unique_ptr = posesión exclusiva. No puede copiarse, solo moverse (std::move). Libera la memoria automáticamente (RAII). make_unique (C++14) lo crea con seguridad. Sustituye a new/delete en la mayoría de los casos.

Move semantics
#include <utility>

std::vector<int> crear() {
    return {1, 2, 3, 4, 5};  // move (RVO)
}

auto v1 = crear();
auto v2 = std::move(v1);  // transfere buffer
// v1 ahora está vacio (moved-from)

// Rvalue reference:
void procesar(std::string&& s) {
    datos_ = std::move(s);  // rouba recursos
}

procesar(std::string("temporal"));  // OK
// std::string nombre = "x";
// procesar(std::move(nombre));  // move explícito

std::move convierte a un rvalue (permite robar recursos). && es una rvalue reference. Evita copias costosas. Tras un move, el objeto queda en un estado válido pero indefinido. RVO optimiza los retornos automáticamente.

Templates


10 cards
Función template
template <typename T>
T mayor(T a, T b) {
    return (a > b) ? a : b;
}

mayor(3, 7);         // int → 7
mayor(3.14, 2.71);   // double → 3.14
mayor<std::string>("a", "b");  // explícito

// Múltiples tipos:
template <typename T, typename U>
auto suma(T a, U b) {
    return a + b;
}

template <typename T> crea una función genérica. El compilador genera código para cada tipo usado. T se sustituye en compilación. Cero overhead vs código manual. La base de la programación genérica en C++.

Concepts (C++20)
#include <concepts>

// Usar concept existente:
template <std::integral T>
T suma(T a, T b) { return a + b; }

template <std::floating_point T>
T raiz(T x) { return std::sqrt(x); }

// Definir concept custom:
template <typename T>
concept Printavel = requires(T x) {
    { std::cout << x } -> std::same_as<std::ostream&>;
};

void imprimir(const Printavel auto& x) {
    std::cout << x;
}

Concepts (C++20) restringen los templates con requisitos claros. Errores de compilación legibles. std::integral, std::floating_point son built-in. requires define los custom. Sustituye a SFINAE.

CRTP
// Curiously Recurring Template Pattern
template <typename Derived>
class Base {
public:
    void interface() {
        // "Polimorfismo" estático (sem virtual):
        static_cast<Derived*>(this)->implementación();
    }
};

class Concreta : public Base<Concreta> {
public:
    void implementación() {
        std::cout << "Hecho!\n";
    }
};

Concreta c;
c.interface();  // "Hecho!" — resolvido em compilación

CRTP = una clase deriva de un template de sí misma. Permite el polimorfismo estático (sin vtable, más rápido). Se usa para mix-ins, contadores e interfaces en compilación. Un patrón avanzado pero potente.

Clase template
template <typename T>
class Caja {
    T valor;
public:
    Caja(T v) : valor(std::move(v)) {}
    T get() const { return valor; }
    void set(T v) { valor = std::move(v); }
};

Caja<int> c(42);
Caja<std::string> s("hola");

// C++17: CTAD (deducción automática):
Caja c2(42);       // deduz Caja<int>
Caja s2("hola"s);   // deduz Caja<std::string>

Las clases template parametrizan tipos. Instancia con Caja<int>. CTAD (C++17) deduce el tipo del constructor. La STL es toda template: vector<T>, map<K,V>.

Variadic templates
// Numero variable de argumentos:
template <typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << "\n";
}

print(1, " + ", 2, " = ", 3);

// Fold expressions (C++17):
template <typename... Args>
auto suma(Args... args) {
    return (args + ...);  // fold right
}

suma(1, 2, 3, 4);  // 10

// sizeof... cuenta:
template <typename... Ts>
constexpr size_t contar() { return sizeof...(Ts); }

Los variadic templates aceptan N tipos. Args... es un parameter pack. Una fold expression ((args + ...)) expande con elegancia. sizeof... cuenta los elementos. La base de std::tuple, make_unique y un printf seguro.

if constexpr
template <typename T>
std::string toStr(const T& val) {
    if constexpr (std::is_same_v<T, std::string>) {
        return val;  // ya es string
    } else if constexpr (std::is_arithmetic_v<T>) {
        return std::to_string(val);
    } else {
        return val.toString();  // tem método
    }
}

toStr(42);            // "42"
toStr(std::string("oi")); // "oi"

// Ramos no compilados son descartados!

if constexpr (C++17) evalúa en compilación. Las ramas falsas se descartan (no necesitan compilar). Elimina la necesidad de SFINAE/tag dispatch. Ideal para código genérico con ramas por tipo.

Especialización
// Template genérico:
template <typename T>
struct TypeInfo {
    static std::string nombre() { return "desconocido"; }
};

// Especialización total:
template <>
struct TypeInfo<int> {
    static std::string nombre() { return "entero"; }
};

template <>
struct TypeInfo<double> {
    static std::string nombre() { return "real"; }
};

TypeInfo<int>::nombre();     // "entero"
TypeInfo<char>::nombre();    // "desconocido"

La especialización total (template <>) sustituye el template para un tipo específico. El compilador elige la versión más específica. Se usa en type traits y metaprogramación.

Type traits
#include <type_traits>

// Verificar em compilación:
static_assert(std::is_integral_v<int>);
static_assert(std::is_floating_point_v<double>);
static_assert(std::is_same_v<int, int32_t>);

// Transformar tipos:
using T1 = std::remove_const_t<const int>;  // int
using T2 = std::remove_pointer_t<int*>;     // int
using T3 = std::add_lvalue_reference_t<int>; // int&

// Condicional:
template <typename T>
using Storage = std::conditional_t<
    sizeof(T) <= 8, T, const T&>;

Los type traits consultan y transforman tipos en compilación. _v = valor booleano, _t = tipo resultante. static_assert falla la compilación si es falso. Esenciales para la metaprogramación y SFINAE.

Especialización parcial
// Genérico:
template <typename T>
struct Container { using type = T; };

// Especialización parcial para punteros:
template <typename T>
struct Container<T*> {
    using type = T;  // remove o *
};

// Para vectors:
template <typename T>
struct Container<std::vector<T>> {
    using type = T;  // tipo do elemento
};

Container<int*>::type;              // int
Container<std::vector<double>>::type; // double

La especialización parcial especializa un patrón (p. ej. todos los punteros). Solo para clases/structs (no funciones). El patrón T* captura cualquier puntero. La base de type traits como remove_pointer.

SFINAE
// enable_if: solo compila se condición for true
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
mitad(T x) { return x / 2; }

// C++20 (preferir concepts):
template <std::integral T>
T mitad(T x) { return x / 2; }

// if constexpr (C++17):
template <typename T>
auto procesar(T x) {
    if constexpr (std::is_integral_v<T>)
        return x / 2;
    else
        return x;
}

SFINAE = Substitution Failure Is Not An Error. Elimina los overloads inválidos silenciosamente. enable_if es la forma clásica. C++17/20 ofrecen mejores alternativas: if constexpr y concepts.

C++ Moderno


10 cards
auto y decltype
auto x = 42;            // int
auto s = std::string("hola");  // std::string
auto& ref = x;          // int&
const auto& cr = x;    // const int&

// decltype: tipo de uma expresión
decltype(x) y = 10;    // int
decltype(x + 1.0) z;   // double

// decltype(auto) (C++14):
auto getVec() { return std::vector<int>{1,2,3}; }
decltype(auto) first(std::vector<int>& v) {
    return v[0];  // int& (preserva referencia)
}

auto infiere el tipo (evita repetición). decltype obtiene el tipo de una expresión. decltype(auto) preserva referencias y cv-qualifiers. Usa auto para iteradores y tipos anchos. No abuses de él para tipos obvios.

std::string_view (C++17)
#include <string_view>

// Vista leve (sem copia):
void procesar(std::string_view sv) {
    sv.substr(0, 5);
    sv.size();
    sv.starts_with("Hola");  // C++20
}

procesar("Hola Mundo");         // de literal
std::string s = "prueba";
procesar(s);                    // de string
procesar(std::string_view(s.data(), 3)); // sub

// ⚠️ No possui os datos!
// No guardar se a origem pode morrer

string_view es una vista read-only sobre chars existentes (sin copia). Acepta literals, strings, substrings. Cero asignación. Cuidado: no es owner — si la origen muere, queda dangling. Ideal para parámetros de funciones.

Coroutines (C++20)
#include <coroutine>
#include <generator>  // C++23

// Generator simples (C++23):
std::generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        auto tmp = a;
        a = b;
        b = tmp + b;
    }
}

for (int x : fibonacci()) {
    if (x > 100) break;
    std::cout << x << " ";
}
// 0 1 1 2 3 5 8 13 21 34 55 89

Las coroutines (C++20) son funciones que pueden suspenderse (co_yield, co_await, co_return). std::generator (C++23) las simplifica. Lazy, eficientes para secuencias infinitas e I/O asíncrono.

Structured bindings (C++17)
// De pair:
auto [nombre, edad] = std::make_pair("Ana", 30);

// De tuple:
auto [x, y, z] = std::make_tuple(1.0, 2.0, 3.0);

// De struct:
struct Ponto { double x, y; };
auto [px, py] = Ponto{3.0, 4.0};

// Em range-for:
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
for (const auto& [key, val] : m) {
    std::cout << key << " = " << val << "\n";
}

Los structured bindings desestructuran pairs, tuples, arrays y structs. auto [a, b] = ... crea variables locales. Funciona en range-for para maps. Más legible que .first/.second.

Ranges (C++20)
#include <ranges>
namespace views = std::views;

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};

auto resultado = v
    | views::filter([](int n) { return n % 2 == 0; })
    | views::transform([](int n) { return n * n; })
    | views::take(3);

for (int x : resultado) {
    std::cout << x << " ";  // 4 16 36
}

// Lazy evaluation: nada es calculado hasta iterar

Ranges (C++20) componen operaciones en pipelines con |. Lazy evaluation (solo calcula al iterar). filter, transform, take son views. Sustituye los loops + vectors temporales. Inspirado en Python/Rust.

Designated initializers (C++20)
struct Config {
    int width = 800;
    int height = 600;
    bool fullscreen = false;
    std::string title = "App";
};

// C++20:
Config cfg{
    .width = 1920,
    .height = 1080,
    .fullscreen = true,
    // .title usa default "App"
};

// Ordem debe ser a da declaración
// Campos omitidos usam default
Config minimal{.title = "Juego"};

Los designated initializers (C++20) nombran los campos en la inicialización. El orden debe seguir la declaración. Los campos omitidos usan defaults. Más legible que la inicialización posicional. Funciona con structs y clases aggregate.

std::optional (C++17)
#include <optional>

std::optional<int> encontrar(int id) {
    if (id > 0) return id * 10;
    return std::nullopt;
}

auto resultado = encontrar(5);
if (resultado.has_value()) {
    std::cout << *resultado;  // 50
}

// Value_or (fallback):
int val = encontrar(-1).value_or(0);  // 0

// Em parámetros (opcional explícito):
void config(std::optional<int> timeout = std::nullopt);

std::optional = un valor que puede no existir (sin centinelas como -1). has_value() verifica, * accede. value_or da un fallback. nullopt = vacío. Sustituye a los punteros nulos para opcionales.

std::chrono
#include <chrono>
using namespace std::chrono;

// Medir tiempo:
auto inicio = steady_clock::now();
doSomething();
auto fin = steady_clock::now();
auto ms = duration_cast<milliseconds>(fin - inicio);
std::cout << ms.count() << " ms\n";

// Literais (C++14):
using namespace std::chrono_literals;
auto timeout = 5s;
auto delay = 100ms;
std::this_thread::sleep_for(delay);

// Timestamp:
auto now = system_clock::now();
auto epoch = now.time_since_epoch();

std::chrono gestiona el tiempo con type safety. steady_clock para mediciones (monotónico). duration_cast convierte unidades. Literales 5s, 100ms (C++14). Sustituye a clock() y las APIs de C.

std::variant (C++17)
#include <variant>

std::variant<int, double, std::string> v;
v = 42;
v = 3.14;
v = "hola";

std::get<std::string>(v);       // "hola"
std::get<2>(v);                 // por índice
v.index();                      // 2 (tipo activo)
std::holds_alternative<int>(v); // false

// std::visit (pattern matching):
std::visit([](auto&& val) {
    std::cout << val << "\n";
}, v);

std::variant = una union con type safety. Guarda UNO de N tipos. std::get accede (lanza una excepción si el tipo es erróneo). std::visit aplica un visitor. Un sustituto seguro de las unions de C. Sin asignación en heap.

std::filesystem (C++17)
#include <filesystem>
namespace fs = std::filesystem;

fs::path p = "/home/user/fichero.txt";
p.filename();    // "fichero.txt"
p.extension();   // ".txt"
p.parent_path(); // "/home/user"

fs::exists(p);
fs::file_size(p);
fs::create_directories("a/b/c");
fs::copy("orig.txt", "dest.txt");
fs::remove("temp.txt");

// Iterar directorio:
for (const auto& entry : fs::directory_iterator(".")) {
    std::cout << entry.path() << "\n";
}

std::filesystem (C++17) manipula ficheros y carpetas de forma portable. path compone/descompone rutas. exists, copy, remove, create_directories. Sustituye a las APIs del SO.

Compilação e Dicas


10 cards
Compilar con g++ / clang++
# Básico:
g++ main.cpp -o app

# Com standard e warnings:
g++ -std=c++20 -Wall -Wextra -O2 main.cpp -o app

# Clang:
clang++ -std=c++20 -Wall main.cpp -o app

# Múltiples ficheros:
g++ -std=c++20 main.cpp utils.cpp -o app

# Executar:
./app

g++ y clang++ son los compiladores principales. -std=c++20 define el standard. -Wall -Wextra activa los warnings. -O2 optimiza. Compila siempre con los warnings activos.

Preprocessor
#define MAX_SIZE 1024
#define SQUARE(x) ((x) * (x))

#ifdef DEBUG
    #define LOG(msg) std::cout << "[DBG] " << msg << "\n"
#else
    #define LOG(msg)
#endif

#if __cplusplus >= 202002L
    // código C++20
#endif

#pragma once  // header guard

#undef MAX_SIZE  // remover macro

El preprocessor se ejecuta antes de la compilación. #define crea macros (sustitución textual). #ifdef compilación condicional. #pragma once evita la inclusión doble. Prefiere constexpr a las macros.

Buenas prácticas modernas
// ✅ FAZER:
// • Smart pointers (unique_ptr, shared_ptr)
// • const em tudo que no muda
// • const reference para parámetros
// • auto para tipos longos/iteradores
// • Range-for en lugar de índices
// • [[nodiscard]] em retornos importantes

[[nodiscard]] int calcular() { return 42; }

// ❌ EVITAR:
// • new/delete manual
// • using namespace std em headers
// • C-arrays e C-strings
// • Macros (preferir constexpr)
// • Punteros raw como owners

Reglas de oro: smart pointers en vez de new/delete, const por defecto, const & para los parámetros, range-for para iterar. [[nodiscard]] avisa si el retorno se ignora. Sigue las C++ Core Guidelines.

CMake básico
cmake_minimum_required(VERSION 3.20)
project(MeuProjeto LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(app
    src/main.cpp
    src/utils.cpp
)

target_include_directories(app PRIVATE include)

# Build:
# mkdir build && cd build
# cmake .. && cmake --build .

CMake es el build system estándar de facto. add_executable define el target. target_include_directories añade headers. Build out-of-source en build/. Multiplataforma (Linux, Win, Mac).

Sanitizers
# Address Sanitizer (memory errors):
g++ -fsanitize=address -g main.cpp -o app

# Undefined Behavior Sanitizer:
g++ -fsanitize=undefined -g main.cpp -o app

# Thread Sanitizer (data races):
g++ -fsanitize=thread -g main.cpp -o app

# Memory Sanitizer (uninitialized reads):
clang++ -fsanitize=memory -g main.cpp -o app

# Combinar (exceto thread):
g++ -fsanitize=address,undefined -g main.cpp -o app

Los sanitizers detectan bugs en runtime. address = buffer overflow, use-after-free. undefined = signed overflow, null deref. thread = data races. -g para stack traces. Úsalos siempre en los tests.

Linking y bibliotecas
# Compilar para object file:
g++ -c -std=c++20 utils.cpp -o utils.o

# Crear biblioteca estática:
ar rcs libutils.a utils.o

# Crear biblioteca partilhada:
g++ -shared -fPIC utils.o -o libutils.so

# Linkar:
g++ main.cpp -L. -lutils -o app

# CMake (automático):
# add_library(utils STATIC src/utils.cpp)
# target_link_libraries(app PRIVATE utils)

-c compila sin linkar (genera .o). ar crea una biblioteca estática (.a). -shared -fPIC crea una dinámica (.so/.dll). -L define la ruta, -l linka. CMake gestiona todo automáticamente.

Headers e includes
// main.h (header):
#pragma once  // header guard

#include <string>
#include <vector>

namespace app {
    void iniciar(const std::string& nombre);
    std::vector<int> generar(int n);
}

// main.cpp (implementación):
#include "main.h"
#include <iostream>

void app::iniciar(const std::string& nombre) {
    std::cout << "Hola " << nombre << "\n";
}

#pragma once evita la inclusión doble. Los headers declaran, los .cpp implementan. #include "file.h" para los locales, <file> para los del sistema. Minimiza los includes en los headers (forward declare cuando sea posible).

Debugging con GDB
# Compilar com debug info:
g++ -g -O0 main.cpp -o app

# Iniciar GDB:
gdb ./app

# Comandos:
(gdb) break main         # breakpoint
(gdb) break utils.cpp:42 # breakpoint em linea
(gdb) run                # ejecutar
(gdb) next               # step over
(gdb) step               # step into
(gdb) print variable     # ver valor
(gdb) backtrace          # call stack
(gdb) continue           # continuar

GDB es el debugger estándar. Compila con -g -O0 (sin optimización). break define breakpoints. next/step avanzan. print inspecciona variables. backtrace muestra el call stack.

Namespaces
namespace app::utils {
    int suma(int a, int b) { return a + b; }
}

// Usar:
int r = app::utils::suma(3, 4);

// using (scope limitado):
void funcion() {
    using namespace app::utils;
    suma(1, 2);  // OK aqui
}

// Alias:
namespace au = app::utils;
au::suma(5, 6);

// NUNCA: using namespace std; em headers!

namespace organiza el código y evita colisiones. :: accede a los miembros. using namespace importa (¡evítalo en los headers!). Un alias (namespace au = ...) simplifica. C++17 permite app::utils anidado.

Excepciones
#include <stdexcept>

void procesar(int x) {
    if (x < 0) {
        throw std::invalid_argument("x negativo");
    }
}

try {
    procesar(-1);
} catch (const std::invalid_argument& e) {
    std::cerr << "Error: " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
} catch (...) {
    std::cerr << "Error desconocido\n";
}

throw lanza una excepción. try/catch la captura. std::exception es la base. what() da el mensaje. catch(...) captura todo. RAII garantiza la limpieza. Algunos proyectos prohíben las excepciones (rendimiento).

STL Algoritmos


10 cards
std::sort
#include <algorithm>

std::vector<int> v = {5, 2, 8, 1, 9};

// Ordem crescente:
std::sort(v.begin(), v.end());
// {1, 2, 5, 8, 9}

// Ordem decrescente:
std::sort(v.begin(), v.end(), std::greater<int>());

// Comparador custom:
std::sort(v.begin(), v.end(),
    [](int a, int b) { return a % 3 < b % 3; });

// Estable (mantiene ordem relativa):
std::stable_sort(v.begin(), v.end());

std::sort ordena en O(n log n). Acepta un comparador custom (lambda o functor). std::greater invierte. stable_sort mantiene el orden de los elementos iguales. Solo funciona en contenedores con acceso aleatorio.

std::for_each
#include <algorithm>

std::vector<int> v = {1, 2, 3, 4, 5};

// Aplicar funcion a cada elemento:
std::for_each(v.begin(), v.end(),
    [](int& x) { x *= 2; });
// v = {2, 4, 6, 8, 10}

// Com estado (functor):
struct Printer {
    int count = 0;
    void operator()(int x) {
        std::cout << ++count << ": " << x << "\n";
    }
};
std::for_each(v.begin(), v.end(), Printer{});

std::for_each aplica una función/lambda a cada elemento. Puede modificar con int&. Acepta functors con estado. En C++11+, el range-for suele preferirse. for_each_n (C++17) para N elementos.

remove y unique
#include <algorithm>

std::vector<int> v = {1, 2, 2, 3, 2, 4};

// Remove todos os 2 (idiom erase-remove):
v.erase(
    std::remove(v.begin(), v.end(), 2),
    v.end());
// v = {1, 3, 4}

// Remover duplicados (requer ordenado):
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());

// C++20 (mais simples):
std::erase(v, 2);          // remove todos os 2
std::erase_if(v, [](int x) { return x > 3; });

El idioma erase-remove: std::remove desplaza los elementos y devuelve el nuevo final; erase redimensiona. C++20 lo simplifica con std::erase/std::erase_if. unique elimina los duplicados consecutivos.

std::find y search
#include <algorithm>

std::vector<int> v = {10, 20, 30, 40};

// Encontrar valor:
auto it = std::find(v.begin(), v.end(), 30);
if (it != v.end()) {
    std::cout << "Encontrado: " << *it;
}

// Encontrar com predicado:
auto it2 = std::find_if(v.begin(), v.end(),
    [](int x) { return x > 25; });

// Contar:
int n = std::count(v.begin(), v.end(), 20);  // 1
int m = std::count_if(v.begin(), v.end(),
    [](int x) { return x > 15; });  // 3

std::find búsqueda por valor (O(n)). find_if usa un predicado. Devuelve un iterador (compara con end()). count/count_if cuentan ocurrencias. En map/set, usa el .find() del contenedor (O(log n)).

min, max y clamp
#include <algorithm>

int a = std::min(3, 7);    // 3
int b = std::max(3, 7);    // 7
auto [mn, mx] = std::minmax({5, 2, 8, 1});

// Min/max de range:
std::vector<int> v = {4, 2, 9, 1};
auto menor = *std::min_element(v.begin(), v.end());
auto mayor = *std::max_element(v.begin(), v.end());

// Clamp (limitar a intervalo):
int c = std::clamp(15, 0, 10);  // 10
int d = std::clamp(-5, 0, 10);  // 0

std::min/std::max comparan 2 valores. min_element/max_element devuelven un iterador al extremo del rango. std::clamp (C++17) limita un valor a [min, max].

partition y nth_element
#include <algorithm>

std::vector<int> v = {5, 2, 8, 1, 9, 3};

// Particionar (pares antes de impares):
auto it = std::partition(v.begin(), v.end(),
    [](int x) { return x % 2 == 0; });
// [2, 8] | [5, 1, 9, 3]

// Estable:
std::stable_partition(v.begin(), v.end(), pred);

// N-ésimo elemento (O(n) medio):
std::nth_element(v.begin(), v.begin() + 2, v.end());
// v[2] es o 3º menor (resto no ordenado)

// Mediana:
std::nth_element(v.begin(), v.begin() + v.size()/2, v.end());

std::partition divide en 2 grupos (predicado true/false). nth_element coloca el N-ésimo en su posición correcta en O(n) — ideal para la mediana y top-K sin ordenar todo.

std::transform
#include <algorithm>

std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> resultado(v.size());

// Transformar cada elemento:
std::transform(v.begin(), v.end(), resultado.begin(),
    [](int x) { return x * x; });
// resultado = {1, 4, 9, 16, 25}

// In-place:
std::transform(v.begin(), v.end(), v.begin(),
    [](int x) { return x * 2; });

// Dois inputs:
std::transform(a.begin(), a.end(), b.begin(),
    resultado.begin(), std::plus<int>());

std::transform aplica una función a cada elemento y guarda el resultado. Puede ser in-place (output = input). La versión con 2 rangos combina elementos. Similar a map de los lenguajes funcionales.

binary_search y lower_bound
#include <algorithm>

std::vector<int> v = {1, 3, 5, 7, 9, 11};
// DEVE estar ordenado!

// Existe?
bool existe = std::binary_search(v.begin(), v.end(), 7);

// Posición de inserción:
auto lb = std::lower_bound(v.begin(), v.end(), 6);
// aponta para 7 (primero >= 6)

auto ub = std::upper_bound(v.begin(), v.end(), 7);
// aponta para 9 (primero > 7)

// Range de iguais:
auto [lo, hi] = std::equal_range(v.begin(), v.end(), 7);

binary_search verifica la existencia en O(log n) (requiere estar ordenado). lower_bound = primero ≥ valor. upper_bound = primero > valor. equal_range da ambos. Esenciales para una búsqueda eficiente.

std::accumulate y reduce
#include <numeric>

std::vector<int> v = {1, 2, 3, 4, 5};

// Suma:
int suma = std::accumulate(v.begin(), v.end(), 0);
// 15

// Producto:
int prod = std::accumulate(v.begin(), v.end(), 1,
    std::multiplies<int>());
// 120

// Com lambda:
auto concat = std::accumulate(v.begin(), v.end(),
    std::string(""), [](std::string acc, int x) {
        return acc + std::to_string(x) + ",";
    });
// "1,2,3,4,5,"

std::accumulate reduce un rango a un solo valor. Comienza con un valor inicial. Acepta una operación custom (lambda). std::reduce (C++17) es paralelo. Equivalente a reduce/fold funcional.

copy, fill y generate
#include <algorithm>

std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5);

// Copiar:
std::copy(src.begin(), src.end(), dst.begin());

// Copiar com condición:
std::copy_if(src.begin(), src.end(),
    std::back_inserter(dst),
    [](int x) { return x > 3; });

// Rellenar:
std::fill(dst.begin(), dst.end(), 0);

// Generar:
std::generate(dst.begin(), dst.end(),
    [n = 0]() mutable { return n++; });

std::copy copia un rango. copy_if filtra. back_inserter añade al final. fill rellena con un valor. generate llama a una función para cada posición. mutable permite modificar la captura de la lambda.