Cheatsheet C++
Linguagem de alto desempenho com controlo de memória
C++
Sintaxe Básica
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
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 returngoto 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
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óninline 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.0va_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
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 + encapsulamentoLa ú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(); // 3Los 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
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(); // truevector 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); // 2Los 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-rangestd::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 lentosdeque = 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
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 objetoshared_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 lecturaUna 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(); // nullptrweak_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ícitostd::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
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ónCRTP = 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; // doubleLa 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
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 morrerstring_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 89Las 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 iterarRanges (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
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 macroEl 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 ownersReglas 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
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; }); // 3std::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); // 0std::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.