C++ stack e queue
Algumas listas têm uma regra de quem sai primeiro. Na pilha de pratos da cozinha da Ana, você sempre pega o prato de cima, o último que foi colocado. Na fila do banco, quem é atendido é quem chegou primeiro. O C++ tem uma "caixa" pronta para cada uma dessas regras: std::stack (pilha) e std::queue (fila). Elas fazem menos coisas que um std::vector, e é exatamente por isso que são úteis: não deixam você quebrar a regra sem querer.
- O que é uma pilha (o último a entrar é o primeiro a sair) e uma fila (o primeiro a entrar é o primeiro a sair).
- Como usar
std::stack:push,top,pop,sizeeempty. - Por que
pop()não devolve o valor, e o erro real que aparece quando você tenta. - Como montar um "desfazer" (Ctrl+Z) com uma pilha.
- Como usar
std::queue:push,front,backepop, numa fila de atendimento. - O básico de
std::priority_queue, a fila em que o "mais importante" sai primeiro.
Os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa (veja Compilar com g++). Antes desta aula, vale ter visto std::vector e list e deque.
A ideia: a pilha de pratos e a fila do banco
Pense em duas cenas do dia a dia:
- A pilha de pratos. A Ana lava um prato e coloca em cima da pilha. Quando precisa de um, pega o de cima. O prato que está lá embaixo, o primeiro que ela guardou, vai ser o último a sair.
- A fila do banco. Quem chega vai para o fim da fila. O caixa chama quem está na frente. Quem chegou primeiro é atendido primeiro.
Pilha (std::stack) | Fila (std::queue) | |
|---|---|---|
| Regra | O último a entrar é o primeiro a sair | O primeiro a entrar é o primeiro a sair |
| Sigla em inglês | LIFO (last in, first out) | FIFO (first in, first out) |
| Onde entra | No topo | No fim |
| Onde sai | Do topo | Da frente |
| Exemplos | Pratos, Ctrl+Z, botão "voltar" do navegador | Banco, impressora, senhas de atendimento |
Stack quer dizer "pilha" e queue (pronuncia-se "kiú") quer dizer "fila". As duas estão na biblioteca padrão (a STL, aula O que é a STL) e são chamadas de adaptadores de container: por baixo, elas guardam os elementos num std::deque, mas só mostram para você as operações que respeitam a regra.
1. std::stack: a pilha
Para usar a pilha, inclua o cabeçalho <stack>. Entre os sinais < > você diz o tipo do que vai guardar, como no std::vector:
#include <iostream>
#include <stack>
#include <string>
int main() {
std::stack<std::string> pratos;
pratos.push("prato azul");
pratos.push("prato verde");
pratos.push("prato vermelho");
std::cout << "Pratos na pilha: " << pratos.size() << '\n';
std::cout << "No topo: " << pratos.top() << '\n';
pratos.pop();
std::cout << "Depois do pop, no topo: " << pratos.top() << '\n';
std::cout << "Pratos na pilha: " << pratos.size() << '\n';
}
- #include <stack>
- Traz a
std::stackpara o programa. - std::stack<std::string> pratos;
- Cria uma pilha vazia chamada
pratos, que guarda textos. - pratos.push("prato azul");
- Push = "empurrar". Coloca um elemento no topo. Depois dos três
push, o vermelho está em cima e o azul está embaixo. - pratos.size()
- Quantos elementos a pilha tem agora.
- pratos.top()
- Top = "topo". Olha o elemento de cima, sem tirá-lo.
- pratos.pop();
- Pop = "estourar", "tirar". Remove o elemento de cima e não devolve nada.
$ g++ -std=c++23 -Wall -Wextra pilha.cpp -o pilha $ ./pilha Pratos na pilha: 3 No topo: prato vermelho « o último que entrou Depois do pop, no topo: prato verde Pratos na pilha: 2
Desenhando a pilha a cada passo (o topo está à direita):
push("prato azul")→ azulpush("prato verde")→ azul, verdepush("prato vermelho")→ azul, verde, vermelhotop()→ olha o vermelho; a pilha não muda.pop()→ tira o vermelho: azul, verde
Tirar tudo, do topo até o fundo
O jeito clássico de "percorrer" uma pilha é olhar o topo e tirá-lo, repetindo enquanto ela não estiver vazia. O empty() ("vazia?") responde true ou false:
#include <iostream>
#include <stack>
int main() {
std::stack<int> pilha;
for (int i = 1; i <= 4; ++i) {
pilha.push(i * 10);
}
while (!pilha.empty()) {
std::cout << pilha.top() << ' ';
pilha.pop();
}
std::cout << '\n';
std::cout << "Vazia? " << std::boolalpha << pilha.empty() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra esvaziar.cpp -o esvaziar $ ./esvaziar 40 30 20 10 « entrou 10, 20, 30, 40; saiu ao contrário Vazia? true
Repare: os números saíram na ordem inversa da que entraram. Essa é a "assinatura" da pilha. O std::boolalpha só faz o cout escrever true em vez de 1 (aula Booleanos). E perceba que, para ler a pilha inteira, você precisou destruí-la: a pilha não deixa olhar o meio. Se precisar ver todos os elementos sem tirá-los, a estrutura certa é um std::vector.
Experimente: rode como está. Depois coloque um quarto prato com push e veja quem fica no topo. Por fim, troque o pop() de lugar: chame-o antes de mostrar o primeiro top().
#include <iostream>
#include <stack>
#include <string>
int main() {
std::stack<std::string> pratos;
pratos.push("prato azul");
pratos.push("prato verde");
pratos.push("prato vermelho");
std::cout << "No topo: " << pratos.top() << '\n';
pratos.pop();
std::cout << "Agora no topo: " << pratos.top() << '\n';
std::cout << "Restam " << pratos.size() << " pratos\n";
}
pop() não devolve o valor
Quem vem de outras linguagens (Python, Java, JavaScript) costuma escrever x = pilha.pop(), porque lá o pop tira o elemento e devolve. Em C++, não. Veja o que o g++ diz:
#include <iostream>
#include <stack>
int main() {
std::stack<int> pilha;
pilha.push(7);
int x = pilha.pop();
std::cout << x << '\n';
}
$ g++ -std=c++23 -Wall -Wextra pop_valor.cpp -o pop_valor
pop_valor.cpp: In function ‘int main()’:
pop_valor.cpp:7:22: error: void value not ignored as it ought to be
7 | int x = pilha.pop();
| ~~~~~~~~~^~
Void value not ignored as it ought to be = "um valor void não foi ignorado, como deveria ser". void quer dizer "nada" (aula Retorno): o pop() devolve nada, e você tentou guardar esse nada num int. O jeito certo são duas linhas: primeiro olhe, depois tire.
int x = pilha.top(); // 1. copia o valor do topo pilha.pop(); // 2. tira o elemento da pilha
Para devolver o valor, o pop teria de copiá-lo para fora da pilha. Copiar pode falhar (por exemplo, faltar memória para copiar um texto enorme). Se o elemento já tivesse sido removido e a cópia falhasse, o valor se perderia para sempre. Separando em top() (copia, e se falhar a pilha continua intacta) e pop() (remove, o que não falha), nada se perde. É uma decisão de segurança dos anos 1990 que continua até hoje em stack, queue e priority_queue.
top() numa pilha vazia
Se a pilha está vazia, não existe topo. O que acontece se você pedir mesmo assim?
#include <iostream>
#include <stack>
int main() {
std::stack<int> pilha;
std::cout << "Topo: " << pilha.top() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra topo_vazio.cpp -o topo_vazio $ ./topo_vazio /usr/include/c++/15/bits/stl_stack.h:260: std::stack<_Tp, _Sequence>::reference std::stack<_Tp, _Sequence>::top() [with _Tp = int; _Sequence = std::deque<int, std::allocator<int> >; reference = int&]: Assertion '!this->empty()' failed. Abortado (imagem do núcleo gravada) ./topo_vazio $ echo $? 134 « 128 + 6: o programa foi abortado (sinal 6, SIGABRT)
Compilou sem nenhum aviso, mas o programa morreu ao rodar. Lendo a mensagem por partes:
- /usr/include/c++/15/bits/stl_stack.h:260
- O arquivo da biblioteca padrão onde a
std::stackestá escrita, linha 260: é o código dotop(). - [with _Tp = int; …]
- Detalhes técnicos: era uma pilha de
intguardada numstd::deque. - Assertion '!this->empty()' failed.
- "A conferência a pilha não está vazia falhou." Uma asserção é um teste que a biblioteca faz e que, se falhar, encerra o programa na hora.
- Abortado (imagem do núcleo gravada)
- Quem escreve isso é o terminal (o bash): o programa foi abortado.
A partir do g++ 15, a biblioteca padrão liga essas conferências sozinha quando você compila sem -O1/-O2. Com -O2 (o modo usado em programas "de verdade"), a conferência some, e o mesmo programa faz outra coisa:
$ g++ -std=c++23 -Wall -Wextra -O2 topo_vazio.cpp -o topo_vazio2 $ ./topo_vazio2 Falha de segmentação (imagem do núcleo gravada) ./topo_vazio2
Pedir o topo de uma pilha vazia é comportamento indefinido: o C++ não promete nada (aula Comportamento Indefinido). Pode travar, pode mostrar lixo, pode "funcionar" hoje e falhar amanhã. No editor do guia, que usa outro compilador e outra biblioteca, o resultado também pode ser diferente. A regra é uma só: confira empty() antes de top() e de pop().
2. Uso real: desfazer (Ctrl+Z)
Todo editor de texto tem o "desfazer". Por trás, quase sempre há uma pilha: antes de cada mudança, o programa guarda o estado antigo no topo. Quando você aperta Ctrl+Z, ele pega o estado do topo, que é o mais recente, e volta para ele.
#include <iostream>
#include <stack>
#include <string>
int main() {
std::string texto;
std::stack<std::string> historico;
// Cada vez que a Ana digita, guardamos o texto ANTES da mudança
historico.push(texto);
texto += "Lista: ";
historico.push(texto);
texto += "arroz, ";
historico.push(texto);
texto += "feijão, ";
historico.push(texto);
texto += "chocolate";
std::cout << "Texto: " << texto << '\n';
// Ctrl+Z duas vezes
for (int i = 1; i <= 2; ++i) {
if (!historico.empty()) {
texto = historico.top();
historico.pop();
}
std::cout << "Desfazer " << i << ": " << texto << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra desfazer.cpp -o desfazer $ ./desfazer Texto: Lista: arroz, feijão, chocolate Desfazer 1: Lista: arroz, feijão, « o chocolate, a última mudança, saiu primeiro Desfazer 2: Lista: arroz,
| Trecho | O que faz |
|---|---|
historico.push(texto); | Antes de mudar, guarda uma cópia do texto atual no topo. |
texto += "…"; | A mudança em si (+= junta texto no fim, aula Juntar Strings). |
if (!historico.empty()) | Só desfaz se houver o que desfazer. Sem isso, o terceiro, quarto, quinto Ctrl+Z chegariam numa pilha vazia. |
texto = historico.top(); historico.pop(); | Volta para o estado guardado mais recente e o remove do histórico. |
O botão "voltar" do navegador funciona igual: cada página visitada vai para o topo de uma pilha, e "voltar" tira a do topo.
3. std::queue: a fila
A fila fica no cabeçalho <queue>. Ela também tem push, pop, size e empty, mas no lugar do top tem duas "janelas": front (a frente, quem sai) e back (o fim, quem acabou de chegar).
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> atendimento;
atendimento.push("Ana");
atendimento.push("Bruno");
atendimento.push("Carla");
std::cout << "Pessoas na fila: " << atendimento.size() << '\n';
std::cout << "Primeira: " << atendimento.front() << '\n';
std::cout << "Última: " << atendimento.back() << '\n';
while (!atendimento.empty()) {
std::cout << "Atendendo " << atendimento.front() << '\n';
atendimento.pop();
}
std::cout << "Fila vazia.\n";
}
- #include <queue>
- Traz a
std::queue(e também astd::priority_queue, da seção 4). - atendimento.push("Ana");
- Coloca a Ana no fim da fila.
- atendimento.front()
- Olha quem está na frente (o próximo a ser atendido), sem tirar.
- atendimento.back()
- Olha quem está no fim (o último que chegou).
- atendimento.pop();
- Tira quem está na frente. Como na pilha, não devolve nada.
$ g++ -std=c++23 -Wall -Wextra fila.cpp -o fila $ ./fila Pessoas na fila: 3 Primeira: Ana Última: Carla Atendendo Ana « na ordem em que chegaram Atendendo Bruno Atendendo Carla Fila vazia.
Compare com a pilha: aqui, quem entrou primeiro (Ana) saiu primeiro. As regras de segurança são as mesmas: front(), back() e pop() numa fila vazia são comportamento indefinido, então confira empty() antes.
A fila não tem top
Um tropeço comum é misturar os nomes da pilha com os da fila:
$ cat fila_top.cpp
#include <queue>
int main() {
std::queue<int> fila;
fila.push(1);
return fila.top();
}
$ g++ -std=c++23 -Wall -Wextra fila_top.cpp -o fila_top
fila_top.cpp: In function ‘int main()’:
fila_top.cpp:6:17: error: ‘class std::queue<int>’ has no member named ‘top’; did you mean ‘pop’?
6 | return fila.top();
| ^~~
| pop
Has no member named ‘top’ = "não tem um membro chamado top". O g++ ainda tenta ajudar com did you mean ‘pop’? ("você quis dizer pop?"), mas aqui a sugestão está errada: o que você quer é front(). Para lembrar: pilha tem topo; fila tem frente.
Experimente: este programa lê nomes da entrada e distribui senhas na ordem de chegada. Rode e veja as senhas. Depois mude a entrada (ou o programa) para mostrar também quem é o último da fila antes de começar o atendimento, usando back().
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> fila;
std::string nome;
while (std::cin >> nome) {
fila.push(nome);
}
int senha = 1;
while (!fila.empty()) {
std::cout << "Senha " << senha << ": " << fila.front() << '\n';
fila.pop();
++senha;
}
}
No laboratório, com três nomes na entrada:
$ g++ -std=c++23 -Wall -Wextra atendimento.cpp -o atendimento $ printf "Ana Bruno Carla\n" | ./atendimento Senha 1: Ana Senha 2: Bruno Senha 3: Carla
4. std::priority_queue: a fila com prioridade (só o básico)
No pronto-socorro, ninguém é atendido por ordem de chegada: quem está em estado mais grave passa na frente. A std::priority_queue ("fila de prioridade") faz isso: o top() é sempre o maior elemento, não importa a ordem em que entraram.
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> urgencia;
urgencia.push(3);
urgencia.push(9);
urgencia.push(1);
urgencia.push(5);
while (!urgencia.empty()) {
std::cout << urgencia.top() << ' ';
urgencia.pop();
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prioridade.cpp -o prioridade $ ./prioridade 9 5 3 1 « do maior para o menor, não na ordem de entrada
Repare que ela usa top(), como a pilha, e não front(). Ela não guarda os elementos em ordem por dentro; só garante que o de cima é sempre o maior. Por isso é rápida para "pegar o mais importante", mas não serve para listar tudo em ordem sem esvaziá-la (para isso existe o std::sort, da aula Algoritmos).
Menor primeiro
Para que o menor saia primeiro (por exemplo, a senha de número mais baixo), a declaração fica mais comprida. Você precisa dizer três coisas: o tipo, onde guardar por baixo (std::vector<int>) e como comparar (std::greater<int>, "maior que", de <functional>):
#include <functional>
#include <iostream>
#include <queue>
#include <vector>
int main() {
std::priority_queue<int, std::vector<int>, std::greater<int>> senhas;
senhas.push(42);
senhas.push(17);
senhas.push(30);
while (!senhas.empty()) {
std::cout << senhas.top() << ' ';
senhas.pop();
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra prioridade_menor.cpp -o prioridade_menor $ ./prioridade_menor 17 30 42
Não se preocupe em decorar essa linha agora. O que importa é a ideia: fila de prioridade = sempre sai o "melhor", e você escolhe o que é "melhor". Prioridades com tipos próprios (structs com uma nota de gravidade, por exemplo) usam o mesmo mecanismo de comparação que você verá em Sobrecarga de Operadores e Lambdas.
Qual usar?
| Você precisa… | Use |
|---|---|
Guardar e ver qualquer elemento, em qualquer ordem, percorrer com for | std::vector (aula) |
| Sempre tratar o mais recente primeiro (desfazer, voltar, conferir parênteses) | std::stack |
| Tratar na ordem de chegada (atendimento, tarefas, impressões) | std::queue |
| Tratar sempre o maior (ou o menor) primeiro | std::priority_queue |
| Pôr e tirar dos dois lados e ainda ver o meio | std::deque (aula) |
"Mas um std::vector com push_back, back e pop_back também faz uma pilha." Faz, e muita gente usa assim. A vantagem da std::stack é a intenção: quem lê std::stack sabe na hora que ali vale a regra do topo, e ninguém consegue mexer no meio por engano.
Por padrão, stack e queue guardam os elementos num std::deque. A pilha pode usar um vetor: std::stack<int, std::vector<int>> p;. O uso (push, top, pop) continua igual. Na dúvida, fique com o padrão.
Quando dá errado
error: void value not ignored as it ought to be
pop_valor.cpp:7:22: error: void value not ignored as it ought to be
7 | int x = pilha.pop();
| ~~~~~~~~~^~
O que significa: você tentou usar o resultado do pop(), mas ele não devolve nada (void).
Como consertar: leia com top() (pilha e fila de prioridade) ou front() (fila) e só depois chame pop().
Assertion '!this->empty()' failed. ou Falha de segmentação
O que significa: o programa chamou top(), front(), back() ou pop() numa pilha ou fila vazia. Sem otimização, o g++ 15 confere e aborta com a mensagem de asserção; com -O2, é comportamento indefinido (o programa pode até continuar com lixo).
Como consertar: proteja cada acesso com if (!pilha.empty()) ou use um laço while (!pilha.empty()). Para encontrar onde aconteceu, use o GDB.
has no member named ‘top’ (numa queue) ou has no member named ‘front’ (numa stack)
O que significa: nomes trocados. A pilha só tem top(); a fila tem front() e back(); a fila de prioridade tem top().
Como consertar: confira a tabela de referência abaixo.
error: no matching function for call to ‘begin(std::stack<int>&)’
$ cat percorrer.cpp
#include <iostream>
#include <stack>
int main() {
std::stack<int> pilha;
pilha.push(1);
pilha.push(2);
for (int x : pilha) {
std::cout << x << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra percorrer.cpp -o percorrer
percorrer.cpp: In function ‘int main()’:
percorrer.cpp:8:18: error: no matching function for call to ‘begin(std::stack<int>&)’
8 | for (int x : pilha) {
| ^~~~~
percorrer.cpp:8:18: note: there are 6 candidates
« … e mais de 100 linhas de notes sobre os 6 candidatos,
« com erros como ‘class std::stack<int>’ has no member named ‘begin’
O que significa: o for de intervalo precisa de begin() e end() (aula Iteradores), e pilhas e filas não têm, de propósito: elas não deixam você passear pelo meio. Diante de uma mensagem gigante como essa, leia a primeira linha de error:; o resto são tentativas do compilador.
Como consertar: se você precisa percorrer, esvazie com while (!p.empty()) { … p.top() …; p.pop(); } (talvez numa cópia: auto copia = pilha;) ou troque a estrutura por um std::vector/std::deque.
Referência rápida
| Operação | std::stack | std::queue | std::priority_queue |
|---|---|---|---|
| Cabeçalho | <stack> | <queue> | <queue> |
| Colocar | push(x) no topo | push(x) no fim | push(x) (vai para o lugar certo) |
| Olhar quem sai | top() | front() | top() (o maior) |
| Olhar o último que entrou | top() | back() | (não tem) |
| Tirar | pop() (sem devolver) | pop() (sem devolver) | pop() (sem devolver) |
| Tamanho / vazio? | size() / empty() | ||
| Construir no lugar | emplace(argumentos…): como push, mas cria o elemento já lá dentro | ||
Percorrer com for | Não dá: esvazie com while (!x.empty()) | ||
A tabela de todos os containers está em Referência: Containers.
Exercícios
1. O cabeçalho da fila
Complete para poder usar std::queue:
2. Olhar a frente
Complete para mostrar quem é o próximo da fila f, sem tirá-lo:
3. Numa std::stack<int>, você faz push(1), push(2), push(3), pop() e mostra top(). O que aparece?
4. Numa std::queue<int>, você faz push(1), push(2), push(3), pop() e mostra front(). O que aparece?
5. Por que int x = pilha.pop(); não compila?
6. Uma std::priority_queue<int> recebe 4, 8, 2. Qual é o top()?
Missão: o caixa do banco
Missão: a fila já tem quatro pessoas. Atenda as duas primeiras (mostre Atendida: NOME e tire da fila), depois mostre quem é a próxima e quantas pessoas ainda esperam. A saída deve ser:
Atendida: Ana Atendida: Bruno Próxima: Carla Ainda esperando: 2
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> fila;
fila.push("Ana");
fila.push("Bruno");
fila.push("Carla");
fila.push("Davi");
// atenda duas pessoas aqui
// mostre a próxima e quantas faltam aqui
}
#include <iostream>
#include <queue>
#include <string>
int main() {
std::queue<std::string> fila;
fila.push("Ana");
fila.push("Bruno");
fila.push("Carla");
fila.push("Davi");
// atenda duas pessoas
for (int i = 0; i < 2; ++i) {
std::cout << "Atendida: " << fila.front() << '\n';
fila.pop();
}
std::cout << "Próxima: " << fila.front() << '\n';
std::cout << "Ainda esperando: " << fila.size() << '\n';
}
Missão final: ROMA ao contrário
Missão: use uma std::stack<char> para inverter a palavra ROMA. Empilhe cada letra; depois, enquanto a pilha não estiver vazia, junte o top() na string invertida e faça pop(). O programa deve mostrar AMOR.
#include <iostream>
#include <stack>
#include <string>
int main() {
std::string palavra = "ROMA";
std::stack<char> pilha;
// 1. empilhe cada letra de palavra
std::string invertida;
// 2. desempilhe tudo, juntando em invertida
std::cout << invertida << '\n';
}
#include <iostream>
#include <stack>
#include <string>
int main() {
std::string palavra = "ROMA";
std::stack<char> pilha;
for (char letra : palavra) {
pilha.push(letra);
}
std::string invertida;
while (!pilha.empty()) {
invertida += pilha.top();
pilha.pop();
}
std::cout << invertida << '\n';
}
Resultado da solução no laboratório:
$ g++ -std=c++23 -Wall -Wextra roma.cpp -o roma $ ./roma AMOR
Resumo
- Pilha (
std::stack,<stack>): o último a entrar sai primeiro.pushpõe no topo,topolha,poptira. - Fila (
std::queue,<queue>): o primeiro a entrar sai primeiro.pushpõe no fim,frontolha a frente,backolha o fim,poptira da frente. - Fila de prioridade (
std::priority_queue,<queue>):topé sempre o maior; comstd::greater, o menor. pop()não devolve nada: leia antes comtop()/front().- Olhar ou tirar de uma pilha/fila vazia é comportamento indefinido: confira
empty()antes. - Pilhas e filas não têm
begin/end: não dá para usarfor. Se precisar ver o meio, usevectoroudeque. - Próxima aula: Algoritmos, para ordenar, buscar e contar dentro de um vetor sem escrever o laço na mão.