C Pilhas e Filas
Muitos programas guardam coisas para usar depois, e a ordem em que elas saem é o que importa. O Ctrl+Z de um editor desfaz primeiro a última coisa que você fez. O caixa do banco chama primeiro quem chegou primeiro. Esses dois jeitos têm nome: pilha e fila. Nesta aula você vai construir os dois em C, primeiro com um array e depois com uma lista ligada, e vai ver o truque do array circular.
- O que é uma pilha (o último a entrar é o primeiro a sair) e uma fila (o primeiro a entrar é o primeiro a sair).
- As operações da pilha:
push(empilhar),pop(desempilhar) e ver o topo. - Como fazer uma pilha com array e com lista ligada, e um "desfazer" de verdade.
- As operações da fila: enfileirar e desenfileirar, com a fila do banco.
- Como fazer uma fila com lista ligada (ponteiros de início e fim) e com array circular (o resto
%). - Os erros clássicos: tirar de uma pilha vazia, esquecer o
%, esquecer de zerar o fim.
Esta aula usa structs, passar por ponteiro e listas ligadas. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: pratos e o banco
Na cozinha da Ana, os pratos limpos ficam empilhados. Ela sempre põe o prato novo em cima e sempre tira o prato de cima. O prato que está embaixo de todos foi o primeiro a ser guardado e vai ser o último a ser usado. Isso é uma pilha (em inglês, stack).
No banco, as pessoas pegam uma senha e entram na fila. Quem chega vai para o fim; o caixa chama quem está no começo. Quem chegou primeiro é atendido primeiro. Isso é uma fila (em inglês, queue).
| Pilha | Fila | |
|---|---|---|
| Regra | O último que entra é o primeiro que sai | O primeiro que entra é o primeiro que sai |
| Sigla em inglês | LIFO (last in, first out) | FIFO (first in, first out) |
| Por onde entra | Pelo topo | Pelo fim |
| Por onde sai | Pelo topo | Pelo começo |
| Nomes das operações | push ("empurrar", empilhar), pop ("estourar", desempilhar) | enqueue (enfileirar), dequeue (desenfileirar) |
| No computador | Desfazer (Ctrl+Z), voltar página do navegador, conferir parênteses, a pilha de chamadas de funções | Fila de impressão, mensagens esperando resposta, atendimento por ordem de chegada |
Nenhuma das duas deixa você mexer no meio. Essa é a graça: com só duas operações, não tem como errar a ordem.
1. Pilha com array
A forma mais simples de pilha é um array e um número, topo, que diz quantos itens estão guardados. Os itens moram em itens[0], itens[1]… e o de cima está em itens[topo - 1]:
#include <stdio.h>
#define MAX 5
typedef struct {
int itens[MAX];
int topo;
} Pilha;
bool push(Pilha *p, int valor) {
if (p->topo == MAX) {
return false;
}
p->itens[p->topo] = valor;
p->topo++;
return true;
}
bool pop(Pilha *p, int *saida) {
if (p->topo == 0) {
return false;
}
p->topo--;
*saida = p->itens[p->topo];
return true;
}
bool ver_topo(const Pilha *p, int *saida) {
if (p->topo == 0) {
return false;
}
*saida = p->itens[p->topo - 1];
return true;
}
int main(void) {
Pilha pratos = {.topo = 0};
for (int i = 1; i <= 6; i++) {
if (push(&pratos, i * 10)) {
printf("push %d\n", i * 10);
} else {
printf("push %d: pilha cheia!\n", i * 10);
}
}
int x;
if (ver_topo(&pratos, &x)) {
printf("Topo: %d (com %d itens)\n", x, pratos.topo);
}
while (pop(&pratos, &x)) {
printf("pop %d\n", x);
}
printf("Vazia: %s\n", pratos.topo == 0 ? "sim" : "não");
return 0;
}
$ gcc -std=c23 -Wall -Wextra pilha_array.c -o pilha_array $ ./pilha_array push 10 push 20 push 30 push 40 push 50 push 60: pilha cheia! Topo: 50 (com 5 itens) pop 50 pop 40 pop 30 pop 20 pop 10 Vazia: sim
- #define MAX 5
- A capacidade da pilha. Com um array, ela é fixa: o sexto
pushé recusado. - int topo;
- Quantos itens estão na pilha. Também é a posição livre onde o próximo item vai entrar.
- Pilha pratos = {.topo = 0};
- Começa vazia. O
.topo = 0é um inicializador designado C99: dá valor ao campo pelo nome, e os campos não citados (o array) ficam zerados. - bool push(Pilha *p, int valor)
- Recebe a pilha por ponteiro, para poder mudá-la, e devolve
falsese ela estiver cheia. Quem chama confere. - p->itens[p->topo] = valor; p->topo++;
- Guarda na posição livre e sobe o topo.
- bool pop(Pilha *p, int *saida)
- O valor tirado sai pelo ponteiro
saida; o retorno diz se deu certo. Assim, "pilha vazia" nunca se confunde com um valor de verdade. - p->topo--; *saida = p->itens[p->topo];
- Desce o topo e lê o item que estava lá em cima. O valor continua no array, mas agora está na parte "livre" e vai ser sobrescrito no próximo
push. - ver_topo
- Olha o item de cima sem tirar. Em inglês é o peek ("espiar") ou top.
- while (pop(&pratos, &x))
- Desempilha até o
popdevolverfalse. Repare que os números saíram na ordem contrária à que entraram.
push 40 guarda o 40 em itens[3] (verde) e o topo passa de 3 para 4. Um pop faz o caminho inverso: o topo volta a 3 e o 40 é devolvido. Nada é empurrado nem copiado: só o topo sobe e desce.Experimente: o programa já empilhou as letras de "pilha", uma por uma. Escreva o laço que desempilha todas e mostra cada letra com putchar(c). Como a pilha devolve na ordem contrária, a palavra vai sair de trás para a frente: ahlip.
ahlip
#include <stdio.h>
#include <string.h>
#define MAX 20
typedef struct {
char letras[MAX];
int topo;
} Pilha;
bool push(Pilha *p, char c) {
if (p->topo == MAX) {
return false;
}
p->letras[p->topo++] = c;
return true;
}
bool pop(Pilha *p, char *saida) {
if (p->topo == 0) {
return false;
}
*saida = p->letras[--p->topo];
return true;
}
int main(void) {
const char *palavra = "pilha";
Pilha p = {.topo = 0};
for (size_t i = 0; i < strlen(palavra); i++) {
push(&p, palavra[i]);
}
// tire as letras da pilha, uma por uma, e mostre cada uma com putchar
putchar('\n');
return 0;
}
#include <stdio.h>
#include <string.h>
#define MAX 20
typedef struct {
char letras[MAX];
int topo;
} Pilha;
bool push(Pilha *p, char c) {
if (p->topo == MAX) {
return false;
}
p->letras[p->topo++] = c;
return true;
}
bool pop(Pilha *p, char *saida) {
if (p->topo == 0) {
return false;
}
*saida = p->letras[--p->topo];
return true;
}
int main(void) {
const char *palavra = "pilha";
Pilha p = {.topo = 0};
for (size_t i = 0; i < strlen(palavra); i++) {
push(&p, palavra[i]);
}
char c;
while (pop(&p, &c)) {
putchar(c);
}
putchar('\n');
return 0;
}
Repare em p->letras[p->topo++] = c; e p->letras[--p->topo]: são as mesmas duas linhas do pilha_array.c juntadas numa só, com o ++ depois (usa e depois soma) e o -- antes (subtrai e depois usa). Veja ++ e --.
2. Pilha com lista ligada: o desfazer
A pilha com array tem tamanho fixo. Com uma lista ligada, ela cresce enquanto houver memória, e as operações já existem: push é inserir no início e pop é remover o primeiro. O topo é a cabeça da lista.
Vamos fazer o "desfazer" de um editor de texto. Cada ação vai para a pilha; o Ctrl+Z tira a de cima:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Acao {
char texto[40];
struct Acao *abaixo;
} Acao;
bool empilhar(Acao **topo, const char *texto) {
Acao *a = malloc(sizeof *a);
if (a == NULL) {
return false;
}
snprintf(a->texto, sizeof a->texto, "%s", texto);
a->abaixo = *topo;
*topo = a;
return true;
}
bool desempilhar(Acao **topo, char *saida, size_t tam) {
if (*topo == NULL) {
return false;
}
Acao *a = *topo;
snprintf(saida, tam, "%s", a->texto);
*topo = a->abaixo;
free(a);
return true;
}
int main(void) {
Acao *historico = NULL;
const char *feitas[] = {"digitou \"Olá\"", "pôs negrito", "trocou a cor"};
for (int i = 0; i < 3; i++) {
if (!empilhar(&historico, feitas[i])) {
fprintf(stderr, "Sem memória\n");
return 1;
}
printf("Fez: %s\n", feitas[i]);
}
char ultima[40];
for (int i = 0; i < 2; i++) {
if (desempilhar(&historico, ultima, sizeof ultima)) {
printf("Ctrl+Z desfez: %s\n", ultima);
}
}
printf("Ainda pode desfazer: %s\n", historico != NULL ? historico->texto : "nada");
while (desempilhar(&historico, ultima, sizeof ultima)) {
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra -g desfazer.c -o desfazer $ ./desfazer Fez: digitou "Olá" Fez: pôs negrito Fez: trocou a cor Ctrl+Z desfez: trocou a cor Ctrl+Z desfez: pôs negrito Ainda pode desfazer: digitou "Olá"
| Linha | O que faz |
|---|---|
struct Acao *abaixo; | O prox da lista, com um nome que lembra a pilha: aponta para a ação de baixo. |
Acao **topo | As funções recebem o endereço da variável historico (um ponteiro para ponteiro), para mudar o topo diretamente. |
snprintf(a->texto, sizeof a->texto, "%s", texto); | Copia o texto sem passar do tamanho do campo (veja snprintf). Mais seguro que strcpy. |
a->abaixo = *topo; *topo = a; | O push: a ação nova aponta para o topo antigo e vira o topo. |
*topo = a->abaixo; free(a); | O pop: o de baixo vira o topo, e o nó tirado é liberado (o texto já foi copiado para saida). |
while (desempilhar(…)) { } | No fim, esvazia o que sobrou: cada desempilhar faz um free. O laço não tem corpo porque o trabalho todo está na condição. |
historico) é o topo, a ação mais recente. O Ctrl+Z tira "trocou a cor" e o topo passa a ser "pôs negrito". A primeira ação ("digitou Olá") é a última a ser desfeita.O valgrind confirma que o laço final devolveu tudo (3 ações + o bloco do printf):
$ valgrind --leak-check=full ./desfazer 2>&1 | tail -8 ==64513== HEAP SUMMARY: ==64513== in use at exit: 0 bytes in 0 blocks ==64513== total heap usage: 4 allocs, 4 frees, 4,240 bytes allocated ==64513== ==64513== All heap blocks were freed -- no leaks are possible ==64513== ==64513== For lists of detected and suppressed errors, rerun with: -s ==64513== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
O 2>&1 | tail -8 junta o relatório do valgrind (que sai no stderr) com a saída normal e mostra só as 8 últimas linhas (veja stdin, stdout e stderr).
3. Fila com lista ligada: o banco
Numa fila, entra-se pelo fim e sai-se pelo começo. Com uma lista ligada, as duas pontas precisam ser rápidas, então a fila guarda dois ponteiros: ini (o próximo a ser atendido) e fim (o último que chegou):
#include <stdio.h>
#include <stdlib.h>
typedef struct No {
int senha;
struct No *prox;
} No;
typedef struct {
No *ini;
No *fim;
} Fila;
bool enfileirar(Fila *f, int senha) {
No *n = malloc(sizeof *n);
if (n == NULL) {
return false;
}
n->senha = senha;
n->prox = NULL;
if (f->fim == NULL) {
f->ini = n;
} else {
f->fim->prox = n;
}
f->fim = n;
return true;
}
bool desenfileirar(Fila *f, int *senha) {
if (f->ini == NULL) {
return false;
}
No *n = f->ini;
*senha = n->senha;
f->ini = n->prox;
if (f->ini == NULL) {
f->fim = NULL;
}
free(n);
return true;
}
int main(void) {
Fila banco = {NULL, NULL};
for (int s = 1; s <= 3; s++) {
if (!enfileirar(&banco, s)) {
return 1;
}
printf("Chegou a senha %d\n", s);
}
int s;
if (desenfileirar(&banco, &s)) {
printf("Caixa chama: %d\n", s);
}
if (enfileirar(&banco, 4)) {
printf("Chegou a senha 4\n");
}
while (desenfileirar(&banco, &s)) {
printf("Caixa chama: %d\n", s);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra -g fila_lista.c -o fila_lista $ ./fila_lista Chegou a senha 1 Chegou a senha 2 Chegou a senha 3 Caixa chama: 1 Chegou a senha 4 Caixa chama: 2 Caixa chama: 3 Caixa chama: 4 $ valgrind -q --leak-check=full ./fila_lista Chegou a senha 1 Chegou a senha 2 Chegou a senha 3 Caixa chama: 1 Chegou a senha 4 Caixa chama: 2 Caixa chama: 3 Caixa chama: 4
As senhas foram chamadas na ordem em que chegaram, mesmo com a senha 4 chegando no meio do atendimento. O valgrind -q não reclamou de nada: nenhum vazamento.
- Fila banco = {NULL, NULL};
- Fila vazia: os dois ponteiros em
NULL. - if (f->fim == NULL) f->ini = n;
- enfileirar numa fila vazia: o nó novo é o começo e o fim ao mesmo tempo.
- else f->fim->prox = n;
- Senão, o último de agora aponta para o novo. Sem andar pela lista: o
fimjá está guardado. - f->fim = n;
- Nos dois casos, o novo vira o fim.
- f->ini = n->prox;
- desenfileirar: o segundo da fila passa a ser o primeiro.
- if (f->ini == NULL) f->fim = NULL;
- Se a fila esvaziou, o
fimtambém precisa voltar aNULL. Sem esta linha, ele fica apontando para o nó que ofreevai devolver (veja Quando dá errado).
ini andou uma casa e o nó da 2 (tracejado) foi liberado. Embaixo, chegou a senha 5: ela entrou depois do antigo fim. Cada operação mexe só numa ponta.4. Fila com array circular
Dá para fazer fila com array, mas tem uma pegadinha. Se o começo da fila andar para a direita a cada atendimento, logo ele chega ao fim do array, mesmo com o array quase vazio. Empurrar todo mundo para a esquerda a cada atendimento resolveria, mas é lento.
O truque é tratar o array como um círculo, como as cadeiras numa mesa redonda: depois da última posição vem a primeira de novo. Quem faz a volta é o resto da divisão, o % (veja Operadores Aritméticos): com 4 posições, (3 + 1) % 4 dá 0.
#include <stdio.h>
#define CAP 4
typedef struct {
int itens[CAP];
int ini;
int qtd;
} Fila;
bool enfileirar(Fila *f, int valor) {
if (f->qtd == CAP) {
return false;
}
int fim = (f->ini + f->qtd) % CAP;
f->itens[fim] = valor;
f->qtd++;
printf(" entra %d na posição %d\n", valor, fim);
return true;
}
bool desenfileirar(Fila *f, int *saida) {
if (f->qtd == 0) {
return false;
}
*saida = f->itens[f->ini];
printf(" sai %d da posição %d\n", *saida, f->ini);
f->ini = (f->ini + 1) % CAP;
f->qtd--;
return true;
}
int main(void) {
Fila f = {.ini = 0, .qtd = 0};
int x;
for (int s = 1; s <= 4; s++) {
enfileirar(&f, s);
}
if (!enfileirar(&f, 99)) {
printf(" fila cheia: 99 não entrou\n");
}
desenfileirar(&f, &x);
desenfileirar(&f, &x);
enfileirar(&f, 5);
enfileirar(&f, 6);
while (desenfileirar(&f, &x)) {
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra circular.c -o circular $ ./circular entra 1 na posição 0 entra 2 na posição 1 entra 3 na posição 2 entra 4 na posição 3 fila cheia: 99 não entrou sai 1 da posição 0 sai 2 da posição 1 entra 5 na posição 0 entra 6 na posição 1 sai 3 da posição 2 sai 4 da posição 3 sai 5 da posição 0 sai 6 da posição 1
- int ini; int qtd;
- A posição do primeiro da fila e quantos itens existem. Com os dois, dá para calcular tudo.
- int fim = (f->ini + f->qtd) % CAP;
- A posição livre depois do último. Com
ini = 2eqtd = 2:(2 + 2) % 4 = 0. A fila "dá a volta" e usa a posição 0, que o 1 tinha deixado livre. - f->ini = (f->ini + 1) % CAP;
- Depois de tirar o primeiro, o começo anda uma casa, também dando a volta: de 3 vai para 0.
- if (f->qtd == CAP)
- Cheia. Com
qtdguardado à parte, "cheia" e "vazia" nunca se confundem (as duas teriaminiigual aofim).
ini está na 2 e a próxima vaga (fim) é a 0. Embaixo, o 5 e o 6 entraram nas posições 0 e 1 (verde). Lida a partir do ini, dando a volta, a fila é 3, 4, 5, 6.Experimente: troque CAP para 3 e veja em que posições o 5 e o 6 entram. Depois tire o % CAP das duas contas e rode de novo (é o erro da seção Quando dá errado).
#include <stdio.h>
#define CAP 4
typedef struct {
int itens[CAP];
int ini;
int qtd;
} Fila;
bool enfileirar(Fila *f, int valor) {
if (f->qtd == CAP) {
return false;
}
int fim = (f->ini + f->qtd) % CAP;
f->itens[fim] = valor;
f->qtd++;
printf(" entra %d na posição %d\n", valor, fim);
return true;
}
bool desenfileirar(Fila *f, int *saida) {
if (f->qtd == 0) {
return false;
}
*saida = f->itens[f->ini];
printf(" sai %d da posição %d\n", *saida, f->ini);
f->ini = (f->ini + 1) % CAP;
f->qtd--;
return true;
}
int main(void) {
Fila f = {.ini = 0, .qtd = 0};
int x;
for (int s = 1; s <= 4; s++) {
enfileirar(&f, s);
}
if (!enfileirar(&f, 99)) {
printf(" fila cheia: 99 não entrou\n");
}
desenfileirar(&f, &x);
desenfileirar(&f, &x);
enfileirar(&f, 5);
enfileirar(&f, 6);
while (desenfileirar(&f, &x)) {
}
return 0;
}
Formas diferentes
| Estrutura | Com array | Com lista ligada |
|---|---|---|
| Pilha | Array + topo. Simples, rápido, sem malloc; tamanho fixo (ou use um array que cresce). | Push = inserir no início; pop = remover o primeiro. Cresce sem limite; um malloc e um free por item. |
| Fila | Array circular: ini, qtd e o %. Rápido, tamanho fixo. | Ponteiros ini e fim: entra no fim, sai do começo. Cresce sem limite. |
Qual usar
- Sabe o máximo de itens (uma pilha de até 100 parênteses, uma fila de até 50 pedidos)? Use o array: menos código, nenhum
malloc, nada para liberar. - Não sabe quantos vão ser (o histórico do desfazer, mensagens chegando)? Use a lista ligada, ou um array que cresce com
realloc. - Qualquer que seja a escolha, esconda-a atrás das funções (
push,pop,enfileirar…). Quem usa a pilha não precisa saber se ela é array ou lista, e você pode trocar depois sem mexer no resto do programa.
Quando dá errado
Um pop que não confere se a pilha está vazia:
#include <stdio.h>
#define MAX 5
typedef struct {
int itens[MAX];
int topo;
} Pilha;
int pop(Pilha *p) {
p->topo--;
return p->itens[p->topo];
}
int main(void) {
Pilha pratos = {.topo = 0};
int x = pop(&pratos);
printf("Tirei %d; agora topo = %d\n", x, pratos.topo);
return 0;
}
$ gcc -std=c23 -Wall -Wextra pop_vazia.c -o pop_vazia $ ./pop_vazia Tirei 0; agora topo = -1
O que significa: nenhum erro, nenhum aviso, e um valor inventado. Com topo em 0, o pop leu itens[-1], uma posição antes do array, que pertence a outra coisa. O 0 foi sorte; poderia ser qualquer número. E o topo ficou em -1: o próximo push vai escrever fora do array. Com o AddressSanitizer, o erro aparece na hora:
$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address pop_vazia.c -o pop_vazia
$ ./pop_vazia
=================================================================
==62258==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x76ef711f001c at pc 0x5e09084ec2f2 bp 0x7ffda4d8a480 sp 0x7ffda4d8a470
READ of size 4 at 0x76ef711f001c thread T0
#0 0x5e09084ec2f1 in pop /home/ana/c/pop_vazia.c:12
#1 0x5e09084ec418 in main /home/ana/c/pop_vazia.c:17
« (… mais linhas: o mapa da memória em volta do erro …)
SUMMARY: AddressSanitizer: stack-buffer-underflow /home/ana/c/pop_vazia.c:12 in pop
Stack-buffer-underflow = "leitura antes do começo de um array na pilha". READ of size 4: leu um int (4 bytes), na linha 12 (return p->itens[p->topo];), chamada pela linha 17. O editor do guia não tem sanitizers e mostraria só o valor errado.
Como consertar: o pop confere a pilha vazia e diz se deu certo, como na seção 1: bool pop(Pilha *p, int *saida). Do mesmo jeito, o push confere a pilha cheia.
%: posições que não existem
O mesmo circular.c, com int fim = f->ini + f->qtd; e f->ini = f->ini + 1;, sem o resto:
$ gcc -std=c23 -Wall -Wextra sem_resto.c -o sem_resto $ ./sem_resto entra 1 na posição 0 entra 2 na posição 1 entra 3 na posição 2 entra 4 na posição 3 fila cheia: 99 não entrou sai 1 da posição 0 sai 2 da posição 1 entra 5 na posição 4 entra 6 na posição 8 sai 4 da posição 5 sai 44602368 da posição 6 sai -914074094 da posição 7 sai 6 da posição 8
O que significa: o array tem as posições 0 a 3. O 5 foi escrito na "posição 4", que fica fora do array: ali mora o campo seguinte da struct, o ini. O ini virou 5, e daí em diante tudo desandou: o 6 foi para a "posição 8", o "4" que saiu nem é a senha 4 (é o qtd, lido como se fosse item), e os números gigantes são lixo da memória (mudam a cada execução). O gcc não avisou nada.
Como consertar: toda conta de posição numa fila circular termina com % CAP. Na dúvida, compile com -fsanitize=address e teste os casos de "dar a volta".
fim ficou apontando para um nó liberado
Uma versão de desenfileirar sem as linhas if (f->ini == NULL) f->fim = NULL;. O main enfileira 1, desenfileira (a fila fica vazia) e enfileira 2:
$ gcc -std=c23 -Wall -Wextra -g fim_solto.c -o fim_solto $ ./fim_solto Primeiro da fila: ninguém $ valgrind -q ./fim_solto ==62531== Invalid write of size 8 ==62531== at 0x4001213: enfileirar (fim_solto.c:24) ==62531== by 0x40012E9: main (fim_solto.c:46) ==62531== Address 0x4aa6048 is 8 bytes inside a block of size 16 free'd ==62531== at 0x48538BF: free (vg_replace_malloc.c:990) ==62531== by 0x4001282: desenfileirar (fim_solto.c:37) ==62531== by 0x40012D8: main (fim_solto.c:45) ==62531== Block was alloc'd at ==62531== at 0x4850858: malloc (vg_replace_malloc.c:447) ==62531== by 0x40011C5: enfileirar (fim_solto.c:15) ==62531== by 0x40012C5: main (fim_solto.c:44) ==62531== Primeiro da fila: ninguém
O que significa: a senha 2 entrou, mas a fila diz "ninguém". Depois que a senha 1 saiu, o fim continuou apontando para o nó dela, já liberado. No enfileirar seguinte, f->fim não era NULL, então a linha 24 (f->fim->prox = n;) escreveu dentro do nó morto (Invalid write = "escrita inválida", num bloco free'd, liberado), e o ini nunca recebeu o nó novo, que também vazou.
Como consertar: quando a fila esvaziar, zere os dois ponteiros. Regra geral: a fila está vazia se e só se ini e fim são NULL juntos.
false do push
Se o programa chama push(&p, x); sem olhar o retorno, o item que não coube some sem ninguém saber. Não há erro nem aviso: o bool foi devolvido e jogado fora.
Como consertar: confira (if (!push(&p, x)) { … }), como o pilha_array.c faz. No C23, marque a função com [[nodiscard]] e o compilador avisa quem esquecer (veja Atributos).
Referência rápida
| Operação | Pilha com array | Pilha com lista |
|---|---|---|
| Vazia? | p->topo == 0 | topo == NULL |
| Push | p->itens[p->topo++] = v; (se não estiver cheia) | n->abaixo = topo; topo = n; |
| Pop | v = p->itens[--p->topo]; (se não estiver vazia) | v = topo->valor; aux = topo; topo = topo->abaixo; free(aux); |
| Ver o topo | p->itens[p->topo - 1] | topo->valor |
| Operação | Fila com array circular | Fila com lista |
|---|---|---|
| Vazia? | f->qtd == 0 | f->ini == NULL |
| Enfileirar | f->itens[(f->ini + f->qtd) % CAP] = v; f->qtd++; | fim->prox = n; fim = n; (ou ini = fim = n se vazia) |
| Desenfileirar | v = f->itens[f->ini]; f->ini = (f->ini + 1) % CAP; f->qtd--; | ini = ini->prox; + free (e fim = NULL se esvaziou) |
| Ver o primeiro | f->itens[f->ini] | f->ini->valor |
Exercícios
1. A regra da pilha
Complete a sigla: numa pilha, o último a entrar é o primeiro a sair, em inglês last in, first out:
2. Dar a volta
Numa fila circular com CAP 5, ini é 3 e qtd é 4. Em que posição entra o próximo item? (3 + 4) % 5 =
3. Push na pilha de array
Complete o push numa linha só (guarda na posição livre e depois sobe o topo):
4. Você empilhou 1, 2 e 3 (nessa ordem) e deu dois pop. O que ficou na pilha?
5. A senha 7 entrou na fila do banco, depois a 8, depois a 9. O caixa chama uma pessoa. Quem?
6. Por que a fila com lista ligada guarda um ponteiro para o fim?
Missão final: conferir os parênteses
Missão: todo compilador confere se cada (, [ e { foi fechado pelo par certo, na ordem certa. O jeito é uma pilha: cada abertura vai para a pilha (push); cada fechamento tira o de cima (pop) e confere se ele é o par certo (a função par diz qual é). Se a pilha estiver vazia num fechamento, ou o par não combinar, devolva false. No fim, a pilha precisa estar vazia (isso o código já faz). Complete o laço de balanceado.
(a[b]{c}) -> ok
(] -> erro
((x) -> erro
{[()]} -> ok
) -> erro
#include <stdio.h>
#define MAX 100
typedef struct {
char itens[MAX];
int topo;
} Pilha;
bool push(Pilha *p, char c) {
if (p->topo == MAX) {
return false;
}
p->itens[p->topo++] = c;
return true;
}
bool pop(Pilha *p, char *saida) {
if (p->topo == 0) {
return false;
}
*saida = p->itens[--p->topo];
return true;
}
// Devolve o par de abertura de um fechamento: ')' -> '(', ']' -> '[', '}' -> '{'
char par(char fecha) {
switch (fecha) {
case ')': return '(';
case ']': return '[';
default: return '{';
}
}
bool balanceado(const char *s) {
Pilha p = {.topo = 0};
for (int i = 0; s[i] != '\0'; i++) {
char c = s[i];
// COMPLETE: abertura ( [ { -> push
// fechamento ) ] } -> pop e confira se combina com par(c)
(void)c;
}
return p.topo == 0;
}
int main(void) {
const char *testes[] = {"(a[b]{c})", "(]", "((x)", "{[()]}", ")"};
for (int i = 0; i < 5; i++) {
printf("%s -> %s\n", testes[i], balanceado(testes[i]) ? "ok" : "erro");
}
return 0;
}
#include <stdio.h>
#define MAX 100
typedef struct {
char itens[MAX];
int topo;
} Pilha;
bool push(Pilha *p, char c) {
if (p->topo == MAX) {
return false;
}
p->itens[p->topo++] = c;
return true;
}
bool pop(Pilha *p, char *saida) {
if (p->topo == 0) {
return false;
}
*saida = p->itens[--p->topo];
return true;
}
// Devolve o par de abertura de um fechamento: ')' -> '(', ']' -> '[', '}' -> '{'
char par(char fecha) {
switch (fecha) {
case ')': return '(';
case ']': return '[';
default: return '{';
}
}
bool balanceado(const char *s) {
Pilha p = {.topo = 0};
for (int i = 0; s[i] != '\0'; i++) {
char c = s[i];
if (c == '(' || c == '[' || c == '{') {
if (!push(&p, c)) {
return false;
}
} else if (c == ')' || c == ']' || c == '}') {
char aberto;
if (!pop(&p, &aberto) || aberto != par(c)) {
return false;
}
}
}
return p.topo == 0;
}
int main(void) {
const char *testes[] = {"(a[b]{c})", "(]", "((x)", "{[()]}", ")"};
for (int i = 0; i < 5; i++) {
printf("%s -> %s\n", testes[i], balanceado(testes[i]) ? "ok" : "erro");
}
return 0;
}
Repare nos três jeitos de errar: (] fecha com o par errado; ((x) sobra um ( na pilha no fim; ) tenta tirar de uma pilha vazia. O seu gcc faz algo parecido quando diz expected ‘}’ at end of input (veja Lendo Erros do Compilador).
Resumo
- Pilha: o último que entra é o primeiro que sai (LIFO). Operações:
push,pope ver o topo. Usos: desfazer, voltar, conferir parênteses. - Fila: o primeiro que entra é o primeiro que sai (FIFO). Operações: enfileirar (no fim) e desenfileirar (do começo). Usos: atendimento por ordem de chegada, filas de tarefas.
- Pilha com array:
itens[]+topo(quantos itens = a próxima posição livre). Pilha com lista: push = inserir no início, pop = remover o primeiro. - Fila com lista: ponteiros
iniefim; quando esvaziar, zere os dois. Fila com array: circular, comini,qtde toda conta de posição terminando em% CAP. - Sempre confira vazia antes de tirar e cheia antes de pôr; devolva
boole entregue o valor por ponteiro. Na lista, cada item tirado ganha o seufree.