Templates e STL

Código genérico, escolha de contentores, iteradores, invalidação, algoritmos e comparadores.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

maior(3, 8) compara inteiros; maior(2.5, 1.5) compara reais. Um parâmetro de tipo permite escrever o mesmo corpo para ambos. A biblioteca padrão usa esta ideia para combinar contentores que guardam valores, iteradores que delimitam intervalos e algoritmos que trabalham nesses intervalos.

Funções template

template <typename T> introduz um parâmetro de tipo. typename e class são equivalentes nesta declaração simples. Na chamada maior(3, 8), o compilador deduz T = int; em maior(2.5, 1.5), deduz double.

O exemplo combina uma função e uma classe template; a pilha guarda os dados num vector, onde push_back acrescenta no fim, back consulta o último, pop_back remove-o e empty testa o vazio. A retirada verifica o vazio antes de usar back.

Programa completo para compilar localmente:

#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

template <typename T>
T maior(T a, T b) { return a < b ? b : a; }

template <typename T>
class Pilha {
public:
    void inserir(const T& valor) { dados_.push_back(valor); }
    T retirar() {
        if (dados_.empty()) throw std::out_of_range("Pilha vazia");
        T valor = dados_.back();
        dados_.pop_back();
        return valor;
    }
    bool vazia() const { return dados_.empty(); }
private:
    std::vector<T> dados_;
};

int main() {
    std::cout << maior(3, 8) << " " << maior(2.5, 1.5) << "\n";
    Pilha<std::string> palavras;
    palavras.inserir("primeira");
    palavras.inserir("segunda");
    std::cout << palavras.retirar() << " " << palavras.retirar() << "\n";
    std::cout << std::boolalpha << palavras.vazia() << "\n";
}

Saída:

8 2.5
segunda primeira
true

O corpo de maior exige que a < b seja válido. maior(3, 2.5) não deduz um único T, porque os argumentos têm tipos diferentes; escreve maior<double>(3, 2.5) para escolher double.

Um template não elimina requisitos dos tipos. maior(objeto1, objeto2) só funciona se esse tipo oferecer a comparação necessária. As definições dos templates têm de estar disponíveis quando o compilador as instancia, por isso ficam habitualmente no cabeçalho, ao contrário de funções não template cuja implementação pode ficar num .cpp separado.

Classes template

Pilha<std::string> e Pilha<int> são tipos distintos. Cada instância conserva a mesma disciplina, último a entrar, primeiro a sair, mas guarda valores de outro tipo. A retirada do exemplo copia o último valor antes de o remover e lança uma exceção se a pilha está vazia. Exige, portanto, um tipo copiável. Um tipo só com movimento precisaria de outra retirada, que devolvesse por movimento em vez de copiar.

Depois das inserções, a pilha contém primeira, segunda; a primeira retirada devolve segunda e deixa primeira, a seguinte devolve primeira e deixa o vetor vazio. Uma terceira retirada lançaria uma exceção, em vez de consultar um elemento inexistente.

vector: o array que cresce

std::vector<T> tem armazenamento contíguo, tamanho variável e acesso por índice. size() conta os elementos existentes; capacity() conta a capacidade em elementos. reserve(n) só aumenta a reserva quando n excede a capacidade atual; nunca a diminui nem cria elementos. resize(n) muda o tamanho, construindo ou destruindo elementos.

v[i] exige i < v.size() e não verifica o limite. v.at(i) verifica e lança std::out_of_range. front, back e pop_back exigem um vetor não vazio.

Reservar capacidade para dez elementos não permite escrever v[9] se o tamanho ainda é zero. Depois de reserve(3), o tamanho continua 0 e a capacidade passa a pelo menos 3. Depois de resize(3), existem os índices 0, 1 e 2.

push_back tem custo amortizado constante: algumas inserções são mais caras porque realocam e transferem os elementos. Inserir ou remover no meio desloca elementos e custa linear no número deslocado. A gestão automática de memória não elimina os custos das operações.

Escolher um contentor

Nesta tabela, n é o número de elementos. Os custos usuais de procura por chave pressupõem comparações ou dispersão de custo constante.

ContentorOrganizaçãoAcesso e operação úteis
array<T, N>Contíguo, tamanho fixoÍndice em tempo constante
vector<T>Contíguo, tamanho variávelÍndice constante; adicionar no fim amortizado constante
deque<T>Sequência de blocosÍndice constante; inserir nos extremos constante
list<T>Nós ligadosInserir/remover com iterador conhecido constante; procura linear
set<T>Chaves únicas ordenadasProcura, inserção e remoção logarítmicas
map<K, V>Chaves únicas ordenadas com valoresProcura por chave logarítmica
unordered_map<K, V>Tabela de dispersãoProcura média constante, pior caso linear

Uma lista não oferece acesso por índice. Encontrar o nó pode custar linear, mesmo que a remoção desse nó seja constante. stack e queue são adaptadores que expõem as operações de pilha e fila sobre um contentor subjacente. Escolhe pela sequência de operações de que precisas, não só por uma complexidade isolada.

map e set: chaves e conjuntos

Contar palavras por chave
#include <iostream>
#include <map>
#include <sstream>
#include <string>

int main() {
    std::istringstream entrada("azul verde azul vermelho verde azul");
    std::map<std::string, int> frequencias;
    std::string palavra;
    while (entrada >> palavra) ++frequencias[palavra];
    for (const auto& par : frequencias)
        std::cout << par.first << ": " << par.second << "\n";
    std::cout << std::boolalpha << (frequencias.find("branco") == frequencias.end())
              << " " << frequencias.size() << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

azul: 3
verde: 2
vermelho: 1
true 3

frequencias[palavra] insere a chave a zero se ela ainda não existe; serve para contar, mas surpreende numa simples consulta, por isso usa find quando queres consultar sem inserir. Cada elemento de um map é um pair<const Key, Valor>: par.first é a chave e par.second é a contagem.

map percorre as chaves pela ordem do comparador. set guarda apenas as chaves, sem o segundo valor, e ignora inserções repetidas de uma chave equivalente. unordered_map não oferece ordem de percurso fixa.

Iteradores

Um iterador identifica uma posição num contentor. begin() identifica o primeiro elemento e end() a posição depois do último. No vazio, são iguais. Um intervalo [first, last) pode representar o vazio sem caso especial; percorre enquanto it != last, lê *it e avança com ++it. Nunca desreferencies end().

// Excerto: v é um vector<int> já existente.
for (auto it = v.begin(); it != v.end(); ++it) {
    std::cout << *it << " ";
}

Iteradores têm capacidades diferentes. vector fornece acesso aleatório e permite it + 3. list fornece avanço e recuo, mas não soma de um deslocamento. std::sort exige acesso aleatório; numa list, usa a sua operação sort().

Invalidação de iteradores

Um iterador ou referência pode deixar de ser válido quando a coleção muda. Num vector, uma realocação invalida todos os iteradores, apontadores e referências aos elementos. Se uma inserção não realoca, os acessos anteriores à posição conservam-se, mas os da posição em diante, incluindo o antigo end, são invalidados. erase invalida os acessos na posição removida e posteriores.

Não guardes um iterador e continues a usá-lo depois de uma operação que o pode invalidar. Para remover durante um percurso de vector, a forma habitual é it = v.erase(it) quando removes, e ++it quando conservas. erase devolve um novo iterador válido para o elemento seguinte, ou end().

Em list e nos contentores associativos ordenados, remover um elemento invalida os acessos a esse elemento, e inserir normalmente não invalida os anteriores. As regras de deque e dos contentores de dispersão diferem; não generalizes a regra do vetor.

Algoritmos: sort e find

Ordenar e filtrar um vetor
#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

int main() {
    std::vector<int> valores = {5, 2, 3, 2, 1, 4, 5};
    std::sort(valores.begin(), valores.end());
    valores.erase(std::unique(valores.begin(), valores.end()), valores.end());
    valores.erase(std::remove_if(valores.begin(), valores.end(),
        [](int x) { return x % 2 == 0; }), valores.end());
    for (int x : valores) std::cout << x << " ";
    std::cout << "\n" << std::accumulate(valores.begin(), valores.end(), 0) << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

1 3 5
9

Depois de ordenar, unique compacta valores iguais adjacentes e devolve o novo fim lógico sem mudar o tamanho. No exemplo, o tamanho continua 7 até ao erase. O resultado lógico termina no iterador devolvido. As posições depois dele continuam desreferenciáveis, com estado válido mas não especificado, por isso o programa não conta com esses valores.

O primeiro erase apaga esse resto e reduz o tamanho para 5. O segundo remove os pares filtrados por remove_if.

find percorre até encontrar um valor ou chegar ao fim. count_if conta os que satisfazem um predicado. accumulate, de <numeric>, começa no valor inicial recebido; esse valor também influencia o tipo do acumulador. Para somar double, usa 0.0 quando queres acumulação real.

Uma lambda, como [](int x) { return x % 2 == 0; }, cria uma operação sem lhe dar um nome separado. Lê [] como a lista de capturas: vazia aqui, [limite] copia uma variável e [&limite] referencia-a. Uma captura por referência não prolonga a vida do objeto capturado. No programa de filtragem acima, a lambda decide que elementos são pares; remove_if desloca os restantes para o início.

Comparadores e pesquisa ordenada

Um comparador de ordenação deve representar “vem antes”, com uma relação estrita consistente. Para qualquer x, comp(x, x) tem de ser falso. Por isso a <= b não serve como comparador de sort: dá verdadeiro para valores iguais e quebra a exigência. Para desempatar objetos, compara primeiro o critério principal e depois o secundário:

// Excerto: ordena por nota e desempata por identificador.
struct Registo { int nota; int id; };
bool antes(const Registo& a, const Registo& b) {
    if (a.nota != b.nota) return a.nota < b.nota;
    return a.id < b.id;
}

Com {10:2, 10:1, 9:3}, a ordem é 9:3, 10:1, 10:2. Verificar apenas comp(x, x) == false não prova transitividade; prova só o caso mais simples. Um comparador que ordena por média e desempata por nome usa a mesma estrutura: primeiro o valor, depois a chave estável.

Para pesquisar, lower_bound encontra a primeira posição que não vem antes do valor procurado. O intervalo tem de estar particionado por esse critério, normalmente ordenado pelo mesmo comparador. Num vetor {1, 3, 5, 7}, procurar 5 devolve a posição 2; procurar 4 devolve a mesma posição, que contém 5. Encontrar uma posição de inserção não prova que o valor existe: confirma it != v.end() antes de ler *it e compara o valor encontrado com o procurado.

sort não garante a ordem anterior entre elementos equivalentes; stable_sort conserva-a. Procurar por pesquisa binária num vetor arbitrário não satisfaz a condição de partição.

Agregar por chave e ordenar pelo resultado

Um map<string, list<int>> liga cada estudante às suas notas e percorre os nomes pela ordem das chaves. Se o relatório pede ordem decrescente de média, esse percurso não chega. Calculamos um resumo por estudante e ordenamos os resumos pelo critério pedido, conservando o mapa original.

Cada lista contém entre 1 e 1000 notas inteiras de 0 a 20. O resultado usa a média de cada estudante e, no fim, a média das médias, dando o mesmo peso a cada estudante. Para o mapa vazio, escrevemos Sem estudantes. Estes contratos evitam dividir pelo tamanho de uma lista vazia ou pelo número zero de estudantes.

Ordenar médias sem perder estudantes empatados
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <string>
#include <vector>

struct Media {
    std::string nome;
    double valor;
};

// Cada lista tem 1 a 1000 notas inteiras de 0 a 20.
void imprimir_medias(const std::map<std::string, std::list<int>>& notas) {
    std::vector<Media> resumo;
    double soma_medias = 0;
    for (const auto& estudante : notas) {
        double soma = 0;
        for (int nota : estudante.second) soma += nota;
        double media = soma / estudante.second.size();
        resumo.push_back({estudante.first, media});
        soma_medias += media;
    }
    std::sort(resumo.begin(), resumo.end(), [](const Media& a, const Media& b) {
        if (a.valor != b.valor) return a.valor > b.valor;
        return a.nome < b.nome;
    });
    std::cout << std::fixed << std::setprecision(2);
    for (const auto& estudante : resumo)
        std::cout << estudante.nome << " " << estudante.valor << "\n";
    if (resumo.empty()) std::cout << "Sem estudantes\n";
    else std::cout << "Global " << soma_medias / resumo.size() << "\n";
}

int main() {
    imprimir_medias({{"Ana", {10, 20}}, {"Bruno", {12}}, {"Carla", {13, 17}}});
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

Ana 15.00
Carla 15.00
Bruno 12.00
Global 14.00

Ana tem (10 + 20) / 2 = 15, Bruno tem 12 / 1 = 12 e Carla tem (13 + 17) / 2 = 15. A soma começa em double, por isso a divisão não perde a parte fracionária. O vetor guarda os três pares nome/média, incluindo os dois com média 15. Um map<double, string> usaria a média como chave única e perderia um dos estudantes empatados.

O comparador põe a maior média primeiro e desempata por nome crescente. Comparar com > mantém a relação estrita; usar >= daria verdadeiro para um objeto comparado consigo próprio. O desempate por nome é uma escolha explícita deste exemplo, não uma consequência de sort nem uma regra atribuída à prova antiga.

No fim, (15 + 12 + 15) / 3 = 14. Juntar todas as notas daria 72 / 5 = 14,4: nesse cálculo, os estudantes com duas notas pesariam o dobro do que tem uma. O denominador e o peso de cada grupo vêm do contrato, não do contentor escolhido.

fixed com setprecision(2) formata a impressão com duas casas decimais; não arredonda as médias guardadas antes de ordenar ou calcular o total. Se duas médias diferentes aparecerem iguais no ecrã, a ordem continua a usar os valores calculados. Para m estudantes e g notas no total, a agregação percorre as g notas e a ordenação faz O(m log m) comparações; o vetor adicional guarda m resumos.

Exercícios

Repetidos adjacentes e fim lógico

O vetor começa com 1, 1, 2, 1, 1, 4. Executa-se apenas auto fim = std::unique(v.begin(), v.end());. Indica o tamanho de v e o conteúdo de [v.begin(), fim). Repete o raciocínio partindo dos mesmos dados, mas ordenando antes de unique. Em cada versão, como se elimina a cauda física?

Pista

A primeira versão tem dois grupos de valores 1 separados pelo 2.

Solução

Sem ordenar, os grupos adjacentes são 1, 1, 2, 1, 1 e 4. O prefixo lógico fica 1, 2, 1, 4, com quatro elementos; o tamanho físico continua 6. Depois de ordenar os dados originais, temos 1, 1, 1, 1, 2, 4. unique deixa o prefixo 1, 2, 4, mas continua sem reduzir o tamanho. Em ambas, v.erase(fim, v.end()) destrói a cauda e ajusta o tamanho ao prefixo. Os valores da cauda antes de erase são válidos, mas não especificados.

Confere a tua resposta:

  • Obtive tamanho físico 6 e prefixo lógico 1, 2, 1, 4 na primeira versão.
  • Obtive tamanho físico 6 e prefixo lógico 1, 2, 4 depois de ordenar.
  • Escrevi erase para reduzir o tamanho e não atribuí valores fixos à cauda.
Voltar à explicação
Um percurso para vector e list

Escreve uma função template que substitua por novo cada valor igual a antigo no intervalo válido [inicio, fim) e devolva o número de substituições. Deve servir tanto os iteradores de um vector<int> como os de uma list<int>. Os elementos admitem comparação e atribuição; antigo e novo são recebidos por valor para não dependerem de elementos que a função possa alterar.

template <typename It, typename T>
std::size_t substituir(It inicio, It fim, T antigo, T novo);

Testa a sequência 2, 3, 3, 4, substituindo 3 por 9, e um intervalo vazio. Justifica por que it + 1 limitaria o conjunto de iteradores aceites. O enunciado adapta o problema de iteradores P11 do suplemento FEUP_PROG.

Pista
O algoritmo só precisa de comparar posições, ler o elemento e avançar.
Solução
// Inclui <cstddef> para std::size_t.
template <typename It, typename T>
std::size_t substituir(It inicio, It fim, T antigo, T novo) {
    std::size_t total = 0;
    for (auto it = inicio; it != fim; ++it) {
        if (*it == antigo) {
            *it = novo;
            ++total;
        }
    }
    return total;
}

Com 2, 3, 3, 4, as duas posições com 3 passam a 9, dando 2, 9, 9, 4 e resultado 2. No vazio, a condição é logo falsa e o resultado é zero. Uma list permite ++it, mas não it + 1. Não há inserção nem remoção, por isso este percurso não invalida os iteradores de nenhuma das duas coleções.

Confere a tua resposta:

  • Percorri [inicio, fim) com !=, * e ++, sem ler fim.
  • Obtive 2 substituições e a sequência 2, 9, 9, 4 no caso dado.
  • Tratei o intervalo vazio e não usei aritmética indisponível numa list.
Voltar à explicação
Reconhecer um comparador inválido

Uma ordenação de notas usa [](int a, int b) { return a <= b; }. Explica o problema para notas iguais e corrige o critério. Indica também como conservar a ordem anterior entre elementos com a mesma nota.

Pista

Testa o comparador com dois valores iguais.

Solução

Para duas notas iguais, a <= b dá verdadeiro. Isto viola a exigência de ordenação estrita. Para ordem crescente, usa a < b. Se queres conservar a ordem anterior entre notas iguais, usa esse comparador com stable_sort.

Confere a tua resposta:

  • Mostrei comp(x, x) verdadeiro no código inicial.
  • Substituí <= por <.
  • Expliquei quando stable_sort conserva a ordem anterior.

Erros frequentes

A ordem dos iguais e a validade do comparador são decisões diferentes.

Voltar à explicação
Criar elementos, além de reservar espaço

O programa deve criar três elementos e escrever 0 1 2. Corrige a preparação do vetor. Mantém at, para que um acesso inválido continue a ser detetado.

Preparar três elementos no vetor
#include <iostream>
#include <vector>

int main() {
    std::vector<int> valores;
    valores.reserve(3);
    if (valores.size() != 3) {
        std::cout << "Nao existem os tres elementos\n";
        return 0;
    }
    for (std::size_t i = 0; i < 3; ++i) valores.at(i) = static_cast<int>(i);
    for (int x : valores) std::cout << x << " ";
    std::cout << "\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Pista

Consulta o tamanho depois de reserve, não só a capacidade.

Solução

Substitui valores.reserve(3) por valores.resize(3), ou constrói logo std::vector<int> valores(3). Agora existem os índices 0, 1 e 2, e o ciclo pode atribuir-lhes valores. A saída é 0 1 2.

Confere a tua resposta:

  • Distingui size de capacity.
  • Criei três elementos antes de lhes atribuir valores.
  • Obtive a saída 0 1 2 sem erro de tamanho.

Erros frequentes

Trocar at por [] esconderia a verificação e introduziria comportamento indefinido, porque reserve não cria elementos.

Voltar à explicação
Remover sem saltar o elemento seguinte

Define void remover_negativos(std::vector<int>& valores). Altera o vetor recebido, conservando os restantes valores pela mesma ordem. Nesta versão, usa um iterador e erase para seguir a invalidação. Esta resposta é de autoavaliação.

Escrever a remoção por iterador
#include <iostream>
#include <vector>

void remover_negativos(std::vector<int>& valores) {
    // Escreve o percurso e a remoção.
}

void mostrar(std::vector<int> valores) {
    remover_negativos(valores);
    std::cout << "[";
    for (int x : valores) std::cout << " " << x;
    std::cout << " ]\n";
}

int main() {
    mostrar({}); // [ ]
    mostrar({-1, -2}); // [ ]
    mostrar({1, -1, -2, 0, 3, 3, -4}); // [ 1 0 3 3 ]
}
Resultado

Executa o código para ver a saída e os gráficos.

Pista
O elemento seguinte ocupa a posição que acabaste de remover.
Mais uma pista

Se removes, usa it = valores.erase(it). Se conservas, usa ++it. Os dois casos não devem fazer o mesmo avanço.

Solução
void remover_negativos(std::vector<int>& valores) {
    auto it = valores.begin();
    while (it != valores.end()) {
        if (*it < 0) it = valores.erase(it);
        else ++it;
    }
}

Em {-1, -2}, remover -1 devolve um iterador para -2. Esse valor ainda precisa de ser analisado. Ao remover o último elemento, erase devolve end() e o ciclo termina sem o ler. Cada remoção pode deslocar o resto do vetor; no pior caso, esta versão tem custo quadrático no número de elementos. A combinação remove_if e erase da lição permite uma passagem linear.

Confere a tua resposta:

  • Removi todos os negativos, incluindo negativos consecutivos.
  • Conservei zero, positivos, repetições e a ordem relativa.
  • Usei o iterador devolvido por erase sem o avançar novamente.

Erros frequentes

Não uses um for com avanço incondicional depois de erase. Não conserves o antigo end() nem uses um iterador invalidado. Passar o vetor por valor alteraria apenas uma cópia.

Voltar à explicação
Consultar sem inserir chaves

Compara duas cópias independentes do mesmo mapa, para a primeira consulta não alterar a entrada da segunda. Um map<string, int> tem azul: 3 e verde: 2. Queres saber se existe branco sem alterar o mapa. Compara frequencias[“branco”] com frequencias.find(“branco”).

Pista
Compara o tamanho antes e depois de cada forma.
Solução

frequencias[“branco”] insere branco: 0 e o tamanho passa a 3. find(“branco”) == end() responde que a chave falta e o tamanho continua 2. Usa operator[] para contar e find para consultar.

Confere a tua resposta:

  • Distingui operator[] de find para consulta.
  • Verifiquei que o tamanho não muda após a consulta.
  • Expliquei quando cada um é adequado.
Voltar à explicação
Ordenar sem perder o empate

Um map<string, list<int>> contém Duarte: {18, 12}, Beatriz: {15} e Caio: {9, 9, 9}. As listas têm entre 1 e 1000 notas de 0 a 20. Define uma operação de impressão que conserve o mapa, escreva nome e média por média decrescente, desempate por nome crescente e termine com a média das médias. Formata os números com duas casas decimais; para o mapa vazio, escreve Sem estudantes.

Prevê a saída completa e explica dois erros propostos: guardar cada nome num map<double, string> cuja chave é a média e calcular o valor global somando todas as notas e dividindo pelo número de notas. É necessário arredondar as médias antes de ordenar?

Solução

As médias são 15 para Duarte, 15 para Beatriz e 9 para Caio. O desempate por nome põe Beatriz antes de Duarte. A saída é:

Beatriz 15.00
Duarte 15.00
Caio 9.00
Global 13.00

Um vetor de resumos conserva um par nome/média por estudante. O comparador usa média maior primeiro, e nome menor quando as médias são iguais. Um mapa cuja chave é 15 não conserva dois valores distintos para essa mesma chave; acabaria por omitir um dos estudantes.

A média das médias é (15 + 15 + 9) / 3 = 13. A outra conta dá (18 + 12 + 15 + 9 + 9 + 9) / 6 = 12: dá peso triplo a Caio face a Beatriz. São medidas diferentes. Usa acumulação real, ordena e calcula o total sem arredondar os valores intermédios. fixed e setprecision(2) só formatam a impressão. No mapa vazio, não existe média para dividir; aplica a mensagem definida pelo contrato.

Confere a tua resposta:

  • Conservei os três estudantes, ordenados por média e depois por nome.
  • Obtive média das médias 13 e distingui a média de todas as notas.
  • Calculei antes de formatar e tratei o mapa vazio sem dividir por zero.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.