Cheatsheet C++
Linguagem de alto desempenho com controlo de memória
C++
Sintaxe Básica
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
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 returngoto 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
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çãoinline 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.0va_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
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(); // 3static 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
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(); // truevector é 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); // 2Iteradores 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-rangestd::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 lentosdeque = 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
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 objetoshared_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ó leituraReferê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(); // nullptrweak_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ícitostd::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
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çãoCRTP = 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; // doubleEspecializaçã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
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 morrerstring_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 89Coroutines (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é iterarRanges (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
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 macroO 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 ownersRegras 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
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; }); // 3std::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); // 0std::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.