Tipos Abstratos de Dados (TAD)
Antes de definir um TAD, precisamos acompanhar o caminho completo: valores primitivos viram campos, campos formam registros ou objetos, e estruturas organizam muitos objetos. O TAD aparece quando descrevemos essa organização pelas operações e regras, sem depender da representação interna.
Da linguagem para o modelo
O que é um dado primitivo?
Um tipo primitivo representa uma categoria básica de valor oferecida diretamente pela linguagem. Ele define quais valores cabem ali e quais operações básicas fazem sentido. Em C++, exemplos fundamentais são inteiros, números de ponto flutuante, caracteres e booleanos.
intInteiro
Contagem, idade, índice e quantidade.
doubleDecimal
Média, distância, preço e medida.
charCaractere
Uma unidade de texto codificada.
boolLógico
Uma condição verdadeira ou falsa.
Atenção em C++: std::string não é um tipo primitivo; é uma classe da biblioteca padrão que organiza uma sequência de caracteres. Isso já mostra como uma abstração pode parecer “básica” para quem a utiliza.
Não é simplesmente “o contrário de primitivo”. É um valor visto por meio de um modelo que esconde detalhes de armazenamento.
string nomeint idadeapresentar()maiorDeIdade()Primeira organização
Variáveis numeradas não formam uma coleção
Criar uma variável nova para cada aluno funciona somente enquanto a quantidade é pequena e fixa. Os nomes das variáveis não permitem que um laço percorra automaticamente todos os alunos.
Uma variável por aluno
std::string nomeDoAluno1 = "Ana";
std::string nomeDoAluno2 = "Bia";
std::string nomeDoAluno3 = "Caio";
// E quando chegarem mais 100 alunos?
- Não existe um tamanho da coleção.
- Não podemos usar um índice variável.
- Inserir mais alunos exige declarar mais nomes.
Array de alunos
std::array<std::string, 3> nomes{
"Ana", "Bia", "Caio"
};
for (const auto& nome : nomes) { ... }
- Os valores possuem uma ordem.
- Índices identificam as posições.
- Um laço percorre toda a coleção.
Campos que pertencem juntos
Do aluno espalhado para struct e objeto
O array resolveu a repetição de nomes, mas um aluno não possui somente nome. Se criarmos uma variável separada para cada campo e para cada pessoa, voltamos ao mesmo problema.
nomeDoAluno1 = "Ana"idadeDoAluno1 = 19mediaDoAluno1 = 8.7
nome"Ana"idade19media8.7structstruct Aluno {
std::string nome;
int idade;
double media;
};
A struct declara o formato de um registro. Cada instância reúne os campos de um aluno.
classclass Aluno {
std::string nome;
int idade;
public:
void apresentar() const;
bool maiorDeIdade() const;
};
A classe pode esconder os campos e oferecer operações que mantêm o objeto em um estado válido.
Em termos de estruturas de dados: um objeto é uma unidade composta por estado — seus campos — e operações que atuam sobre esse estado. Orientação a objetos ajuda a modelar e proteger cada unidade; listas, árvores e grafos dizem como muitas unidades serão organizadas.
Organizando muitos objetos
Lista quando a ordem importa; grafo quando a relação importa
Depois de modelar um aluno, surge uma nova pergunta: como organizar muitos objetos Aluno? A resposta depende das operações e relações do problema.
Cada elemento ocupa uma posição na sequência. Percorremos primeiro, segundo, terceiro...
std::vector<Aluno> turma;
Cada objeto é um nó. As ligações registram quem se relaciona com quem.
Grafo<Aluno, Amizade> rede;
vector ou uma lista encadeada podem fornecer implementações diferentes desse conceito.Agora chegamos ao TAD
O TAD é o painel de controle, não o mecanismo interno
Ao dirigir, você usa volante, acelerador e freio sem precisar conhecer o motor. Carros diferentes preservam esse conjunto de operações, mesmo usando mecanismos internos diferentes. Um TAD aplica essa mesma separação ao software.
A ideia decisiva: o programa cliente depende das operações da pilha. Ele não deve depender do vetor ou da lista usados internamente.
Não confunda
TAD, estrutura de dados e classe não são sinônimos
Tipo Abstrato de Dados
Especifica os valores possíveis, as operações disponíveis e o comportamento esperado.
empilhar, desempilhar e topo.Estrutura de dados
Organiza fisicamente os dados e executa concretamente as operações definidas pelo TAD.
std::vector privado guarda os elementos da pilha.Classe em C++
É um recurso da linguagem que pode reunir a interface pública e uma implementação privada.
PilhaVetor<int> é uma classe que implementa o TAD Pilha.Especificação
As operações formam o contrato do TAD
Uma assinatura informa nome, entradas e saída. O contrato completa a informação dizendo o que precisa ser verdade antes da chamada e o que será garantido depois dela.
Criadora
Produz um novo valor do TAD.
criar()Observadora
Consulta sem alterar o valor abstrato.
topo() · vazia()Modificadora
Altera o estado do valor existente.
empilhar() · desempilhar()| Operação | Pré-condição | Pós-condição |
|---|---|---|
empilhar(x) | Nenhuma | x passa a ser o topo. |
topo() | A pilha não está vazia. | Retorna o último valor empilhado sem removê-lo. |
desempilhar() | A pilha não está vazia. | Remove e retorna o valor do topo. |
vazia() | Nenhuma | Informa se a pilha possui zero elementos. |
Todo elemento, exceto o topo, possui exatamente um elemento acima dele. Somente o topo pode ser removido. Cada operação pública deve preservar essa regra.
Experimente
Laboratório do contrato LIFO
LIFO significa last in, first out: o último valor que entra é o primeiro que sai. Use os controles e observe que a regra não depende de saber como a pilha foi armazenada.
O topo atual é “codificar”.
empilharaltera somente o topo.topoconsulta, mas não remove.desempilharsempre remove o último valor inserido.- Tentar remover uma pilha vazia viola a pré-condição.
Aplicação em C++
Uma interface, duas representações possíveis
A classe abstrata abaixo registra as operações públicas. Ela não decide onde os elementos serão guardados. Essa escolha pertence às classes concretas.
// Contrato do TAD Pilha
template <typename T>
class Pilha {
public:
virtual ~Pilha() = default;
virtual void empilhar(T valor) = 0;
virtual T desempilhar() = 0;
virtual const T& topo() const = 0;
virtual bool vazia() const = 0;
};
template <typename T>
class PilhaVetor : public Pilha<T> {
std::vector<T> dados;
public:
void empilhar(T valor) override {
dados.push_back(valor);
}
T desempilhar() override {
T valor = dados.back();
dados.pop_back();
return valor;
}
// topo() e vazia() seguem o contrato.
};
template <typename T>
class PilhaLista : public Pilha<T> {
struct No {
T valor;
std::unique_ptr<No> proximo;
};
std::unique_ptr<No> topoDaPilha;
public:
void empilhar(T valor) override {
topoDaPilha = std::make_unique<No>(
No{valor, std::move(topoDaPilha)}
);
}
// As mesmas operações públicas.
};
Pilha<std::string>& historicoTrocar a representação não exige reescrever quem usa o TAD, desde que o contrato seja preservado.
Método
Como projetar um TAD
Defina o valor abstrato
O que o tipo representa para quem vai usá-lo?
Escolha poucas operações
Elas devem ser simples, coerentes e suficientes para o propósito.
Escreva o contrato
Declare entradas, saídas, pré-condições e pós-condições.
Escolha a representação
Somente agora decida entre vetor, lista, tabela hash ou outra estrutura.
Declare os invariantes
Registre o que deve continuar verdadeiro após cada operação.
Teste pela interface
Os testes devem verificar o comportamento público, não detalhes internos.
Verifique se entendeu
Três perguntas rápidas
Se uma pilha troca vetor por lista, o código cliente deve mudar?
Não. Se o contrato foi preservado e a representação estava encapsulada, a troca é interna à implementação.
Uma pilha pode permitir remover o elemento da base?
Não dentro do contrato tradicional de pilha. Isso quebraria a regra LIFO e caracterizaria outro conjunto de operações.
Por que os atributos da implementação devem ser privados?
Para impedir que o cliente altere a representação diretamente, viole invariantes ou passe a depender de detalhes que deveriam poder mudar.
Conexões como dados
Grafos em redes sociais e bancos de dados
Uma lista responde bem “quais elementos estão na sequência?”. Um grafo foi feito para perguntas como “quem está conectado?”, “qual caminho liga duas pessoas?” e “quais relações existem entre estas entidades?”.
Ana, Bia, Caio e Davi são nós. Cada ligação SEGUE é uma aresta direcionada. Para sugerir contatos a Ana, o sistema pode percorrer os contatos seguidos por Bia e Davi.
Em um banco financeiro, clientes, contas e dispositivos podem ser nós. Relações como POSSUI, TRANSFERIU e ACESSOU tornam visíveis caminhos que ajudam a analisar transferências e conexões suspeitas.
Nós + relacionamentos + propriedades
Em vez de guardar apenas linhas isoladas, o modelo registra as conexões como parte central dos dados.
idade: 19SEGUE:PessoaBiaidade: 21Quando usar: grafos são especialmente úteis quando a pergunta principal exige percorrer relações — amigos de amigos, rotas, dependências, transferências ou entidades que compartilham um mesmo dispositivo.
Pesquisa utilizada
Fontes acadêmicas e técnicas
A aula foi construída a partir das definições e princípios apresentados nestas referências.