DevTools

Cheatsheet C++

Linguagem de alto desempenho com controlo de memória

Voltar às linguagens
C++
100 cards encontrados
Categorias:
Versões:

Sintaxe Básica


10 cards
Variáveis e tipos
int idade = 30;
double preco = 9.99;
char letra = 'A';
bool ativo = true;
std::string nome = "Ana";
auto x = 42;          // tipo inferido (int)
unsigned int contador = 0;
long long grande = 9999999999LL;

int, double, char, bool e std::string são os tipos mais usados. auto deixa o compilador inferir o tipo. unsigned só aceita valores positivos.

Entrada e saída (I/O)
#include <iostream>

int main() {
    std::cout << "Nome: ";
    std::string nome;
    std::getline(std::cin, nome);  // linha completa

    int idade;
    std::cout << "Idade: ";
    std::cin >> idade;

    std::cout << nome << " tem " << idade << " anos\n";
    return 0;
}

std::cout imprime, std::cin lê. << encadeia saídas. std::getline lê linha inteira (com espaços). std::endl faz flush — prefere "\n" para performance.

using e 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 cria alias de tipos (mais legível que typedef). Suporta templates. Útil para simplificar tipos longos como std::vector<std::vector<double>>. Padrão em C++ moderno.

Tipos de tamanho fixo
#include <cstdint>

int8_t   a = -128;      // 1 byte
uint8_t  b = 255;       // 1 byte sem sinal
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 sinal
size_t   n = 0;         // tamanho (unsigned)

Tipos de <cstdint> garantem tamanho exato em qualquer plataforma. int32_t = 4 bytes sempre. size_t é usado para tamanhos e índices. Prefere estes quando o tamanho importa.

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

std::string s = "Olá Mundo";
s.length();          // 10
s.substr(4, 5);     // "Mundo"
s.find("Mundo");    // 4 (posição)
s.replace(0, 3, "Oi"); // "Oi Mundo"
s += "!!";           // concatenação
s.empty();           // false
s.compare("Oi Mundo!!"); // 0 se igual

// Formatação (C++20):
std::string r = std::format("{} tem {}", nome, idade);

std::string é dinâmica e segura. substr extrai, find procura (retorna npos se não achar). std::format (C++20) formata como Python. Evita C-strings (char*).

Estrutura de um programa
#include <iostream>
#include <vector>
#include <string>

// Declarações antecipadas:
void processar(const std::string& s);

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

    for (const auto& d : dados) {
        processar(d);
    }

    return 0;  // sucesso
}

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

Todo programa C++ tem main() como ponto de entrada. #include importa headers. return 0 indica sucesso. Funções podem ser declaradas antes e definidas depois. Compila com g++ main.cpp -o app.

Constantes
const int MAX = 100;          // imutável em runtime
constexpr double PI = 3.14159; // avaliada em compilação
constexpr int fatorial(int n) {
    return n <= 1 ? 1 : n * fatorial(n - 1);
}

inline constexpr int TAM = 1024;

// enum class (tipado, sem poluição):
enum class Cor { Vermelho, Verde, Azul };
Cor c = Cor::Verde;

const impede modificação. constexpr é avaliado em tempo de compilação (mais rápido). enum class é tipado e não polui o namespace (usa Cor::Verde em vez de só Verde).

Conversões 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 converte número→string. std::stoi/std::stod fazem o inverso. static_cast é o cast seguro em compilação. dynamic_cast verifica em runtime (herança).

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

// Comparação:
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 comparação, lógicos e bitwise. Cuidado: 10 / 3 = 3 (divisão inteira). % dá o resto. && e || são curto-circuito.

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 é fixo mas seguro (conhece o tamanho). Arrays C (int arr[5]) não sabem o próprio tamanho. Para tamanho dinâmico, usa std::vector. std::array não aloca no heap.

Controlo de Fluxo


10 cards
if / else if / else
if (idade >= 18) {
    std::cout << "adulto\n";
} else if (idade >= 13) {
    std::cout << "adolescente\n";
} else {
    std::cout << "criança\n";
}

// Sem chaves (só 1 instrução):
if (x > 0) std::cout << "positivo";

if avalia condição booleana. else if encadeia verificações. Chavetas são opcionais para 1 instrução mas recomendadas sempre. C++ não tem elif — usa else if.

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

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

while repete enquanto a condição for verdadeira. do-while garante pelo menos 1 execução (verifica no fim). Ideal para menus e input com validação.

Loops aninhados
// 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& linha : matriz) {
    for (int val : linha) {
        std::cout << val << " ";
    }
    std::cout << "\n";
}

Loops aninhados percorrem estruturas 2D (matrizes, grids). O loop externo controla linhas, o interno colunas. range-for aninhado é mais legível. Cuidado com performance em 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 com constantes inteiras. break evita fall-through. Cases podem partilhar código (2 e 3). default é o fallback. Mais rápido que if-else para muitos casos (tabela de saltos).

break e continue
for (int i = 0; i < 100; i++) {
    if (i == 50) break;      // sai do ciclo
    if (i % 2 == 0) continue; // salta iteração
    std::cout << i << " ";   // só ímpares até 49
}

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

break termina o ciclo imediatamente. continue salta para a próxima iteração. Em switch, break evita execução do case seguinte. Usa com moderação para legibilidade.

goto e labels
// Uso legítimo: sair de loops aninhados
for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        if (encontrado(i, j)) {
            goto fim;
        }
    }
}
fim:
std::cout << "Pesquisa terminada\n";

// EVITAR: torna código ilegível
// Preferir: break com flag, ou função com return

goto salta para um label. Único uso aceitável: sair de loops profundamente aninhados. Em geral, evita — torna o fluxo difícil de seguir. Prefere return, break ou refactorizar para função.

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

// Múltiplas variáveis:
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 tem 3 partes: inicialização, condição, incremento. i++ é pós-incremento. Aceita múltiplas variáveis separadas por vírgula. Ideal quando sabes o número de iterações.

Operador ternário
// condição ? valor_se_true : valor_se_false
int maior = (a > b) ? a : b;

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

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

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

O operador ?: é um if-else numa expressão. Retorna um valor. Ideal para atribuições simples. Evita encadear (ilegível). Os parênteses em (cond) são opcionais mas ajudam.

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

// Por valor (cópia):
for (int x : nums) std::cout << x;

// Por referência (modifica):
for (int& x : nums) x *= 2;

// Por const referência (eficiente, sem cópia):
for (const auto& item : nums) {
    std::cout << item << "\n";
}

for (auto& x : container) percorre sem índice. const auto& evita cópias (essencial para strings/objetos). auto& permite modificar. Mais legível que iteradores. Prefere sempre que possível.

if com inicialização (C++17)
// Variável com escopo limitado ao if:
if (auto it = mapa.find("chave"); it != mapa.end()) {
    std::cout << it->second;
}
// 'it' não existe aqui

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

// Também em switch:
switch (auto [ok, val] = tentar(); ok) {
    case true:  usar(val); break;
    case false: erro();    break;
}

if (init; condição) declara variável com escopo limitado ao bloco. Evita poluir o scope exterior. Muito usado com find() e conversões. Também funciona em switch. Feature do C++17.

Funções


10 cards
Função com retorno
int somar(int a, int b) {
    return a + b;
}

void saudacao(const std::string& nome) {
    std::cout << "Olá, " << nome << "!\n";
}

int main() {
    int r = somar(3, 7);      // 10
    saudacao("Ana");           // "Olá, Ana!"
}

Funções têm tipo de retorno, nome e parâmetros. void = sem retorno. return devolve o valor e termina. Parâmetros são passados por valor (cópia) por padrão.

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

// Com captura por referência:
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 = função anónima inline. [] é a captura: [&] por referência, [=] por valor, [x] variável específica. Ideal para callbacks e comparadores em algoritmos STL.

Funções com retorno auto
// Retorno inferido (C++14):
auto dividir(double a, double b) {
    return a / b;  // retorna double
}

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

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

// decltype(auto) preserva referência:
decltype(auto) primeiro(std::vector<int>& v) {
    return v[0];  // retorna int&
}

auto como retorno deixa o compilador inferir. -> tipo (trailing) é explícito. decltype(auto) preserva referências e cv-qualifiers. Útil para tipos longos e templates.

Passagem por referência
// Modifica o original:
void incrementar(int& x) {
    x++;
}

// Só leitura (sem cópia):
void imprimir(const std::string& s) {
    std::cout << s << "\n";
    // s = "erro";  // ERRO: é const
}

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

& passa por referência (sem cópia, modifica original). const & é só leitura mas evita cópia — usa sempre para strings, vectors e objetos. Essencial para performance.

inline e constexpr
// inline: sugestão para evitar overhead de chamada
inline int abs(int x) {
    return x < 0 ? -x : x;
}

// constexpr: avaliada em tempo de compilação
constexpr int quadrado(int n) {
    return n * n;
}

constexpr int ARR[5] = {
    quadrado(1), quadrado(2), quadrado(3),
    quadrado(4), quadrado(5)
};
// ARR = {1, 4, 9, 16, 25} — tudo em compilação

inline sugere ao compilador para expandir a função (evita call overhead). constexpr garante avaliação em compilação se os args forem constantes. Arrays e templates beneficiam de constexpr.

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

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

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

va_list/va_arg permitem número variável de argumentos (estilo C). Pouco seguro (sem type-check). Em C++ moderno, prefere variadic templates ou std::initializer_list.

Parâmetros por omissão
void conectar(const std::string& host = "localhost",
              int porta = 3306,
              bool ssl = false) {
    std::cout << host << ":" << porta
              << (ssl ? " (SSL)" : "") << "\n";
}

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

Parâmetros com = valor são opcionais na chamada. Devem estar no fim da lista. Chamadas podem omitir os últimos argumentos. Evita sobrecargas desnecessárias.

noexcept e especificações
// Promete não lançar exceções:
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 a função não lança exceções. Permite otimizações (move em vez de copy em vector). Se violado, chama std::terminate. Essencial em move constructors e swap.

Overloading de funções
int soma(int a, int b) { return a + b; }
double soma(double a, double b) { return a + b; }
std::string soma(const std::string& a, const std::string& b) {
    return a + b;
}

soma(3, 5);          // int → 8
soma(3.1, 2.9);     // double → 6.0
soma("Olá", "!");   // string → "Olá!"

Overloading = mesmo nome, parâmetros diferentes. O compilador escolhe pelo tipo dos argumentos. Não pode diferir só no retorno. Muito usado na STL (ex: std::abs para int/double).

Ponteiros de função
int soma(int a, int b) { return a + b; }
int mult(int a, int b) { return a * b; }

// Ponteiro de função:
int (*op)(int, int) = &soma;
op(3, 4);  // 7
op = &mult;
op(3, 4);  // 12

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

Ponteiros de função armazenam endereços de funções. Sintaxe: tipo (*nome)(params). std::function é mais flexível (aceita lambdas, binds). Usado para callbacks e estratégias.

Classes e Objetos


10 cards
Classe com construtor
class Pessoa {
private:
    std::string nome;
    int idade;

public:
    // Construtor com lista de inicialização:
    Pessoa(std::string n, int i)
        : nome(std::move(n)), idade(i) {}

    // Getters:
    const std::string& getNome() const { return nome; }
    int getIdade() const { return idade; }
};

Pessoa p("Ana", 30);

Lista de inicialização (: nome(n), idade(i)) é mais eficiente que atribuir no corpo. std::move evita cópia de strings. const no método promete não modificar o 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 << ")";
    }
};

Sobrecarrega operator+, ==, << para uso natural. const no fim = não modifica. friend acede a privados (para <<). Permite p1 + p2 e cout << p.

Construtor de cópia e move
class Buffer {
    std::unique_ptr<int[]> dados;
    size_t tam;
public:
    // Move constructor:
    Buffer(Buffer&& o) noexcept
        : dados(std::move(o.dados)), tam(o.tam) {
        o.tam = 0;
    }

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

Move constructor (&&) transfere recursos sem copiar — rápido. Copy constructor (const &) duplica dados. noexcept no move é essencial para vector usar move em resize.

Herança
class Animal {
protected:
    std::string nome;
public:
    Animal(std::string n) : nome(std::move(n)) {}
    virtual void falar() const = 0;  // puro virtual
    virtual ~Animal() = default;
};

class Cao : public Animal {
public:
    Cao(std::string n) : Animal(std::move(n)) {}
    void falar() const override {
        std::cout << nome << ": Au!\n";
    }
};

public herda interface. virtual permite override. = 0 torna puro virtual (classe abstracta). override confirma que sobrepõe método da base. Destrutor virtual é obrigatório em classes base.

struct vs class
// struct: membros públicos por padrão
struct Ponto {
    double x, y;  // público
};

// class: membros privados por padrão
class Conta {
    double saldo;  // privado
public:
    void depositar(double v) { saldo += v; }
};

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

Única diferença: struct tem membros públicos por padrão, class privados. Convenção: struct para dados simples (sem invariantes), class para objetos com lógica. Tecnicamente intercambiáveis.

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

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

Classe abstracta = interface: só métodos puro virtuais (= 0). Não pode ser instanciada. Classes derivadas implementam tudo. Destrutor virtual obrigatório. Equivalente a interfaces de Java/C#.

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

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

// Downcast seguro:
if (auto* cao = dynamic_cast<Cao*>(animais[0].get())) {
    cao->abanarCauda();
}

Polimorfismo = chamar método correto via ponteiro base. Requer virtual. unique_ptr gere memória automaticamente. dynamic_cast faz downcast seguro (retorna nullptr se falhar).

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

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

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

static membros pertencem à classe, não ao objeto. Partilhados por todas as instâncias. Acede via Classe::membro. Deve ser definido fora da classe. Ideal para contadores e constantes.

Regra dos 5 (Rule of Five)
class Recurso {
    int* dados;
public:
    Recurso(int n) : dados(new int[n]) {}
    ~Recurso() { delete[] dados; }                    // destrutor
    Recurso(const Recurso& o);                        // cópia
    Recurso& operator=(const Recurso& o);             // atribuição cópia
    Recurso(Recurso&& o) noexcept;                    // move
    Recurso& operator=(Recurso&& o) noexcept;         // atribuição move
};
// Ou: Rule of Zero (usa smart pointers!)

Se defines um, provavelmente precisas dos 5: destrutor, copy ctor, copy assign, move ctor, move assign. Rule of Zero: usa unique_ptr/shared_ptr e não defines nenhum.

Friend e encapsulamento
class Segredo {
    int codigo = 42;
    friend class Hacker;           // classe amiga
    friend void revelar(Segredo&); // função amiga
};

class Hacker {
public:
    void aceder(Segredo& s) {
        std::cout << s.codigo;  // OK: é friend
    }
};

void revelar(Segredo& s) {
    std::cout << s.codigo;  // OK: é friend
}

friend dá acesso a membros privados. Quebra encapsulamento — usa com moderação. Útil para operadores <<, testes unitários e classes tightly-coupled. Não é transitivo nem hereditário.

STL Contentores


10 cards
std::vector
#include <vector>

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

vector é o contentor mais usado — array dinâmico contíguo. push_back adiciona O(1) amortizado. at() verifica limites (lança exceção). erase remove por iterador. Reserva com reserve() para performance.

std::list
#include <list>

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

// Inserção/remoção O(1) em qualquer posição
// (com iterador):
auto it = std::next(l.begin(), 2);
l.insert(it, 99);
l.erase(it);

list é lista duplamente ligada. Inserção/remoção O(1) com iterador. Sem acesso por índice. sort() e splice() são métodos próprios. Usa para inserções frequentes no meio. Raramente necessário.

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 posições:
auto it = v.begin();
std::advance(it, 2);  // aponta para 30
auto dist = std::distance(v.begin(), it);  // 2

Iteradores apontam para elementos. begin()/end() delimitam range. * desreferencia. rbegin/rend para ordem inversa. std::advance move N posições. Base de todos os algoritmos STL.

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

// map: ordenado por chave (árvore)
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 mantém chaves ordenadas (O(log n)). unordered_map usa hash (O(1) médio). operator[] cria se não existir. find + end() para acesso seguro. at() lança exceção se não existir.

std::stack e 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 a entrar, primeiro a sair). queue = FIFO (primeiro a entrar, primeiro a sair). São adaptadores (usam deque por baixo). Sem iteradores. Ideais para DFS/BFS e undo/redo.

std::array e 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 sequência contígua (C++20)
void processar(std::span<int> dados) {
    for (int x : dados) std::cout << x;
}

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

std::array é fixo em tamanho (compile-time), sem heap. std::span (C++20) é uma vista leve sobre dados contíguos (array, vector, C-array). Sem ownership. Substitui par ponteiro+tamanho.

std::set e 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 e ordenados. unordered_set é mais rápido (hash). insert ignora duplicados. contains (C++20) verifica presença. Ideal para verificar existência e eliminar duplicados.

std::priority_queue
#include <queue>

// Max-heap (maior no topo):
std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
pq.top();   // 4 (maior)
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 é heap — topo é sempre o maior (max-heap). Para min-heap usa std::greater. push/pop em 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 memória contígua
// - iteradores mais lentos

deque = fila de dupla ponta. push_front e push_back em O(1). Sem memória contígua (blocos). Usa quando precisas de inserir/remover em ambas as pontas. Base de stack e queue.

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

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

// Structured binding (C++17):
auto [nome, idade] = 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 desestrutura elegantemente. std::get<N> acede por índice. Retornos múltiplos de funções usam pair/tuple.

Ponteiros e Memória


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

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

// Ponteiro para ponteiro:
int** pp = &p;
**pp;  // 42 → 100

& obtém endereço. * desreferencia (acede ao valor). nullptr é o ponteiro nulo moderno (substitui NULL). Sempre inicializa ponteiros. Verifica antes de desreferenciar.

std::shared_ptr
#include <memory>

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

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

// Custo: atomic ref counting (mais lento que unique)
// Usar quando vários owners precisam do mesmo objeto

shared_ptr = posse partilhada com reference counting. Liberta quando count = 0. Copiável (incrementa count). Overhead de atomic ops. Usa quando múltiplos donos partilham o mesmo recurso. Prefere unique_ptr se possível.

Arrays dinâmicos e 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

// Ponteiro + tamanho → span (C++20):
void processar(std::span<int> dados);
processar(v);
processar({arr, 5});

Evita C-arrays e new[]. vector para tamanho dinâmico. std::array para fixo. make_unique<T[]> se precisar de heap com ownership. std::span (C++20) como parâmetro universal.

Referências
int x = 10;
int& ref = x;   // alias para x
ref = 20;       // x agora é 20

// Não pode ser nula nem reassociada:
// int& r;      // ERRO: deve inicializar
// &ref = &y;   // impossível

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

// Referência constante:
const int& cr = x;  // só leitura

Referência = alias para variável existente. Deve ser inicializada. Não pode ser nula nem reassociada. const & impede modificação. Preferir referências a ponteiros quando null não é opção.

std::weak_ptr
#include <memory>

std::weak_ptr<Recurso> fraco;
{
    auto forte = std::make_shared<Recurso>();
    fraco = forte;

    if (auto sp = fraco.lock()) {
        sp->usar();  // OK: ainda existe
    }
}
// forte destruído

if (fraco.expired()) {
    // objeto já não existe
}
auto sp = fraco.lock();  // nullptr

weak_ptr observa sem possuir (não incrementa ref count). Resolve ciclos de referência. lock() tenta obter shared_ptr (nullptr se expirado). expired() verifica. Essencial para caches e observers.

lock_guard e recursos
#include <mutex>

std::mutex mtx;
int partilhado = 0;

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

// C++17 (dedução de tipo):
void outro() {
    std::lock_guard lock(mtx);
    partilhado += 10;
}

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

lock_guard aplica RAII a mutexes: lock no construtor, unlock no destrutor. Impossível esquecer unlock. scoped_lock (C++17) faz lock de múltiplos mutexes sem deadlock. Padrão para thread safety.

new e delete (evitar)
// Alocação 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 Pessoa("Ana");
delete obj;

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

new aloca no heap, delete liberta. delete[] para arrays. Se esquecer o delete = memory leak. Se delete duas vezes = crash. Em C++ moderno, usa unique_ptr/shared_ptr sempre.

RAII
// RAII: Resource Acquisition Is Initialization
class Ficheiro {
    FILE* f;
public:
    Ficheiro(const char* path) {
        f = fopen(path, "r");
        if (!f) throw std::runtime_error("erro");
    }
    ~Ficheiro() {
        if (f) fclose(f);  // liberta no destrutor
    }
    // Não copiável:
    Ficheiro(const Ficheiro&) = delete;
};

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

RAII = adquirir recurso no construtor, libertar no destrutor. Garante cleanup mesmo com exceções. Base de unique_ptr, lock_guard, fstream. Padrão mais importante em C++.

std::unique_ptr
#include <memory>

// Posse exclusiva (não copiável):
auto p = std::make_unique<Pessoa>("Ana", 30);
p->getNome();  // "Ana"

// Transferir posse (move):
auto p2 = std::move(p);
// p agora é nullptr!

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

// Liberta automaticamente ao sair do scope
// Sem necessidade de delete!

unique_ptr = posse exclusiva. Não pode ser copiado, só movido (std::move). Liberta memória automaticamente (RAII). make_unique (C++14) cria com segurança. Substitui new/delete na maioria dos casos.

Move semantics
#include <utility>

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

auto v1 = criar();
auto v2 = std::move(v1);  // transfere buffer
// v1 agora está vazio (moved-from)

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

processar(std::string("temporário"));  // OK
// std::string nome = "x";
// processar(std::move(nome));  // move explícito

std::move converte em rvalue (permite roubar recursos). && é rvalue reference. Evita cópias caras. After move, objeto fica em estado válido mas indefinido. RVO otimiza retornos automaticamente.

Templates


10 cards
Função template
template <typename T>
T maior(T a, T b) {
    return (a > b) ? a : b;
}

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

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

template <typename T> cria função genérica. O compilador gera código para cada tipo usado. T é substituído em compilação. Zero overhead vs código manual. Base da programação genérica em C++.

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

// Usar concept existente:
template <std::integral T>
T soma(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) restringem templates com requisitos claros. Erros de compilação legíveis. std::integral, std::floating_point são built-in. requires define custom. Substitui SFINAE.

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

class Concreta : public Base<Concreta> {
public:
    void implementacao() {
        std::cout << "Feito!\n";
    }
};

Concreta c;
c.interface();  // "Feito!" — resolvido em compilação

CRTP = classe deriva de template de si mesma. Permite polimorfismo estático (sem vtable, mais rápido). Usado para mix-ins, contadores e interfaces em compilação. Padrão avançado mas poderoso.

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

Caixa<int> c(42);
Caixa<std::string> s("olá");

// C++17: CTAD (dedução automática):
Caixa c2(42);       // deduz Caixa<int>
Caixa s2("olá"s);   // deduz Caixa<std::string>

Classes template parametrizam tipos. Instancia com Caixa<int>. CTAD (C++17) deduz o tipo do construtor. STL é toda template: vector<T>, map<K,V>.

Variadic templates
// Número variável 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 soma(Args... args) {
    return (args + ...);  // fold right
}

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

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

Variadic templates aceitam N tipos. Args... é parameter pack. Fold expression ((args + ...)) expande elegantemente. sizeof... conta elementos. Base de std::tuple, make_unique, printf seguro.

if constexpr
template <typename T>
std::string toStr(const T& val) {
    if constexpr (std::is_same_v<T, std::string>) {
        return val;  // já é 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 não compilados são descartados!

if constexpr (C++17) avalia em compilação. Ramos falsos são descartados (não precisam compilar). Elimina necessidade de SFINAE/tag dispatch. Ideal para código genérico com branches por tipo.

Especialização
// Template genérico:
template <typename T>
struct TipoInfo {
    static std::string nome() { return "desconhecido"; }
};

// Especialização total:
template <>
struct TipoInfo<int> {
    static std::string nome() { return "inteiro"; }
};

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

TipoInfo<int>::nome();     // "inteiro"
TipoInfo<char>::nome();    // "desconhecido"

Especialização total (template <>) substitui o template para um tipo específico. O compilador escolhe a versão mais específica. Usado em type traits e metaprogramação.

Type traits
#include <type_traits>

// Verificar em compilação:
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&>;

Type traits consultam e transformam tipos em compilação. _v = valor booleano, _t = tipo resultante. static_assert falha compilação se falso. Essenciais para metaprogramação e SFINAE.

Especialização parcial
// Genérico:
template <typename T>
struct Container { using type = T; };

// Especialização parcial para ponteiros:
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

Especialização parcial especializa um padrão (ex: todos os ponteiros). Só para classes/structs (não funções). Padrão T* captura qualquer ponteiro. Base de type traits como remove_pointer.

SFINAE
// enable_if: só compila se condição for true
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
metade(T x) { return x / 2; }

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

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

SFINAE = Substitution Failure Is Not An Error. Remove overloads inválidos silenciosamente. enable_if é a forma clássica. C++17/20 oferecem alternativas melhores: if constexpr e concepts.

C++ Moderno


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

// decltype: tipo de uma expressão
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 referência)
}

auto infere tipo (evita repetição). decltype obtém tipo de expressão. decltype(auto) preserva referências e cv-qualifiers. Usa auto para iteradores e tipos longos. Não abusar para tipos óbvios.

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

// Vista leve (sem cópia):
void processar(std::string_view sv) {
    sv.substr(0, 5);
    sv.size();
    sv.starts_with("Olá");  // C++20
}

processar("Olá Mundo");         // de literal
std::string s = "teste";
processar(s);                    // de string
processar(std::string_view(s.data(), 3)); // sub

// ⚠️ Não possui os dados!
// Não guardar se a origem pode morrer

string_view é vista read-only sobre chars existentes (sem cópia). Aceita literals, strings, substrings. Zero alocação. Cuidado: não é owner — se a origem morre, fica dangling. Ideal para parâmetros de funções.

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

Coroutines (C++20) são funções que podem suspender (co_yield, co_await, co_return). std::generator (C++23) simplifica. Lazy, eficiente para sequências infinitas e I/O assíncrono.

Structured bindings (C++17)
// De pair:
auto [nome, idade] = 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";
}

Structured bindings desestruturam pairs, tuples, arrays e structs. auto [a, b] = ... cria variáveis locais. Funciona em range-for para maps. Mais legível 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 é calculado até iterar

Ranges (C++20) compõem operações em pipelines com |. Lazy evaluation (só calcula ao iterar). filter, transform, take são views. Substitui loops + vectors temporários. Inspirado em 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 deve ser a da declaração
// Campos omitidos usam default
Config minimal{.title = "Jogo"};

Designated initializers (C++20) nomeiam campos na inicialização. Ordem deve seguir a declaração. Campos omitidos usam defaults. Mais legível que inicialização posicional. Funciona com structs e classes 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 = valor que pode não existir (sem sentinelas como -1). has_value() verifica, * acede. value_or dá fallback. nullopt = vazio. Substitui ponteiros nulos para opcionais.

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

// Medir tempo:
auto inicio = steady_clock::now();
fazerAlgo();
auto fim = steady_clock::now();
auto ms = duration_cast<milliseconds>(fim - 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 gere tempo com type safety. steady_clock para medições (monotónico). duration_cast converte unidades. Literais 5s, 100ms (C++14). Substitui clock() e APIs C.

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

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

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

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

std::variant = union com type safety. Guarda UM de N tipos. std::get acede (lança exceção se tipo errado). std::visit aplica visitor. Substituto seguro de unions C. Sem alocação heap.

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

fs::path p = "/home/user/ficheiro.txt";
p.filename();    // "ficheiro.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 diretório:
for (const auto& entry : fs::directory_iterator(".")) {
    std::cout << entry.path() << "\n";
}

std::filesystem (C++17) manipula ficheiros e pastas de forma portável. path compõe/decompõe caminhos. exists, copy, remove, create_directories. Substitui APIs do SO.

Compilação e Dicas


10 cards
Compilar com 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últiplos ficheiros:
g++ -std=c++20 main.cpp utils.cpp -o app

# Executar:
./app

g++ e clang++ são os compiladores principais. -std=c++20 define o standard. -Wall -Wextra ativa warnings. -O2 otimiza. Sempre compila com warnings ativos.

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

O preprocessor processa antes da compilação. #define cria macros (substituição textual). #ifdef compilação condicional. #pragma once evita dupla inclusão. Prefere constexpr a macros.

Boas práticas modernas
// ✅ FAZER:
// • Smart pointers (unique_ptr, shared_ptr)
// • const em tudo que não muda
// • const reference para parâmetros
// • auto para tipos longos/iteradores
// • Range-for em vez 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)
// • Ponteiros raw como owners

Regras de ouro: smart pointers em vez de new/delete, const por padrão, const & para parâmetros, range-for para iterar. [[nodiscard]] avisa se retorno é ignorado. Segue 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 é o build system padrão de facto. add_executable define o alvo. target_include_directories adiciona headers. Build out-of-source em 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

Sanitizers detetam bugs em runtime. address = buffer overflow, use-after-free. undefined = signed overflow, null deref. thread = data races. -g para stack traces. Usar em testes sempre.

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

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

# Criar 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 sem linkar (gera .o). ar cria biblioteca estática (.a). -shared -fPIC cria dinâmica (.so/.dll). -L define path, -l linka. CMake gere tudo automaticamente.

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

#include <string>
#include <vector>

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

// main.cpp (implementação):
#include "main.h"
#include <iostream>

void app::iniciar(const std::string& nome) {
    std::cout << "Olá " << nome << "\n";
}

#pragma once evita inclusão dupla. Headers declaram, .cpp implementam. #include "file.h" para locais, <file> para sistema. Minimiza includes nos headers (forward declare quando possível).

Debugging com 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 linha
(gdb) run                # executar
(gdb) next               # step over
(gdb) step               # step into
(gdb) print variavel     # ver valor
(gdb) backtrace          # call stack
(gdb) continue           # continuar

GDB é o debugger padrão. Compila com -g -O0 (sem otimização). break define breakpoints. next/step avançam. print inspeciona variáveis. backtrace mostra call stack.

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

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

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

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

// NUNCA: using namespace std; em headers!

namespace organiza código e evita colisões. :: acede a membros. using namespace importa (evitar em headers!). Alias (namespace au = ...) simplifica. C++17 permite app::utils aninhado.

Exceções
#include <stdexcept>

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

try {
    processar(-1);
} catch (const std::invalid_argument& e) {
    std::cerr << "Erro: " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cerr << "Erro: " << e.what() << "\n";
} catch (...) {
    std::cerr << "Erro desconhecido\n";
}

throw lança exceção. try/catch captura. std::exception é a base. what() dá a mensagem. catch(...) apanha tudo. RAII garante cleanup. Alguns projetos proíbem exceções (performance).

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; });

// Estável (mantém ordem relativa):
std::stable_sort(v.begin(), v.end());

std::sort ordena em O(n log n). Aceita comparador custom (lambda ou functor). std::greater inverte. stable_sort mantém ordem de elementos iguais. Só funciona em contentores com acesso aleatório.

std::for_each
#include <algorithm>

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

// Aplicar função 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 função/lambda a cada elemento. Pode modificar com int&. Aceita functors com estado. Em C++11+, range-for é geralmente preferido. for_each_n (C++17) para N elementos.

remove e 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; });

Idioma erase-remove: std::remove desloca elementos, retorna novo fim; erase redimensiona. C++20 simplifica com std::erase/std::erase_if. unique remove duplicados consecutivos.

std::find e 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 procura por valor (O(n)). find_if usa predicado. Retorna iterador (compara com end()). count/count_if contam ocorrências. Em map/set, usa .find() do contentor (O(log n)).

min, max e 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 maior = *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 comparam 2 valores. min_element/max_element retornam iterador para o extremo do range. std::clamp (C++17) limita valor a [min, max].

partition e nth_element
#include <algorithm>

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

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

// Estável:
std::stable_partition(v.begin(), v.end(), pred);

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

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

std::partition divide em 2 grupos (predicado true/false). nth_element coloca o N-ésimo na posição correta em O(n) — ideal para mediana e top-K sem ordenar tudo.

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 função a cada elemento e guarda resultado. Pode ser in-place (output = input). Versão com 2 ranges combina elementos. Similar a map de linguagens funcionais.

binary_search e 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);

// Posição de inserção:
auto lb = std::lower_bound(v.begin(), v.end(), 6);
// aponta para 7 (primeiro >= 6)

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

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

binary_search verifica existência em O(log n) (requer ordenado). lower_bound = primeiro ≥ valor. upper_bound = primeiro > valor. equal_range dá ambos. Essenciais para pesquisa eficiente.

std::accumulate e reduce
#include <numeric>

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

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

// Produto:
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 reduz um range a um valor. Começa com valor inicial. Aceita operação custom (lambda). std::reduce (C++17) é paralelo. Equivalente a reduce/fold funcional.

copy, fill e 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 condição:
std::copy_if(src.begin(), src.end(),
    std::back_inserter(dst),
    [](int x) { return x > 3; });

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

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

std::copy copia range. copy_if filtra. back_inserter adiciona ao fim. fill preenche com valor. generate chama função para cada posição. mutable permite modificar captura da lambda.