Estruturas de Dados Compostas: Registros, Structs e Objetos
Até aqui, você aprendeu a manipular variáveis atômicas e vetores homogêneos. Mas como modelar uma entidade rica do mundo real — como um Aluno com nome, matrícula, notas e status financeiro — sem recorrer a vetores paralelos caóticos? Conheça os Registros (Structs) e dê o salto definitivo rumo à modelagem de sistemas profissionais.
1. A Limitação dos Vetores Homogêneos e o Caos dos Vetores Paralelos
Nas Aulas 21 a 24, exploramos com profundidade o poder dos vetores e das matrizes. Porém, essas estruturas possuem uma restrição física intransponível: são homogêneas. Isso significa que, se você declara um vetor numérico, você só pode guardar números em todas as suas posições. É proibido misturar o nome em texto de um paciente com a sua temperatura decimal no mesmo vetor.
Quando confrontados com a tarefa de cadastrar 50 produtos para uma loja, programadores iniciantes frequentemente recorrem ao perigoso anti-padrão dos Vetores Paralelos (Parallel Arrays):
// Anti-padrão de múltiplos vetores paralelos para a mesma entidade:
vetor nomes = ["Teclado", "Mouse", "Monitor"];
vetor precos = [249.90, 89.90, 1199.00];
vetor estoques = [15, 30, 8];
vetor disponiveis = [verdadeiro, verdadeiro, falso];
Por que essa abordagem é uma armadilha fatal em sistemas reais?
-
Risco Extremo de Desincronização: Imagine que você queira ordenar a lista por preço usando o Bubble Sort (Aula 23). Ao trocar
precos[i]comprecos[i+1], se você se esquecer de trocar simultaneamentenomes[i],estoques[i]edisponiveis[i], o preço de um monitor de R$ 1.500 passará a pertencer a um mouse de R$ 50! O banco de dados foi irremediavelmente corrompido. -
Proliferação de Parâmetros em Funções: Cada função que precise processar um produto terá de receber 4 parâmetros separados (
processar(nomes, precos, estoques, disp)). Adicionar um novo campo comocodigoBarrasobrigará você a alterar a assinatura de dezenas de rotinas no sistema. - Falta de Semântica e Coesão: O computador não sabe que o nome na posição 3 pertence ao preço na posição 3. Essa associação existe apenas na cabeça do programador.
2. O Que É um Registro (Struct)? Definindo Novos Tipos de Dados
Um Registro (conhecido em C/C++ e Rust como struct, e em linguagens modernas como tipo de registro, record ou objeto) é uma estrutura de dados heterogênea definida pelo programador.
Ele permite criar um novo Tipo de Dado que não existia na linguagem original. Assim como você tem os tipos primitivos numero, moeda e texto, você passa a estruturar entidades como Produto, Aluno, ContaBancaria ou PontoCartesiano.
3. Sintaxe no logicabr.dev e o Operador Ponto (.)
Na plataforma logicabr.dev e na engenharia de software moderna, a modelagem de entidades compostas adota a arquitetura de Objetos Estruturados. Em vez de exigir declarações burocráticas e rígidas de tipos como no Portugol antigo do século passado, você define e instancia registros de negócios de forma direta, clara e expressiva através de objetos com pares chave-valor:
Essa abordagem unifica a simplicidade pedagógica com o padrão internacional adotado por JSON, JavaScript, Python e linguagens modernas de microsserviços. Cada campo do objeto possui uma semântica clara e pode ser inspecionado ou modificado com máxima naturalidade.
// No logicabr.dev: Objetos Estruturados com campos nomeados
registro produto = { codigo: 101, descricao: "Teclado Mecânico RGB", preco: 249.90, estoque: 15 };
Acessando os Campos com o Operador Ponto (Dot Operator):
Para ler ou modificar um membro interno do registro, escrevemos o nome da variável acompanhado de um ponto e o nome do campo desejado:
// Atribuindo e lendo valores com notação de ponto (.)
registro item = { codigo: 0, descricao: "", preco: 0.0, quantidade: 0 };
item.codigo = 101;
item.descricao = "Mouse Gamer Sem Fio";
item.preco = 189.90;
item.quantidade = 25;
saida("Código: " + item.codigo);
saida("Descrição: " + item.descricao);
saida("Valor Unitário: R$ " + item.preco);
saida("Total em Estoque: R$ " + (item.preco * item.quantidade));
4. Mapeamento na Memória RAM e Alinhamento
Como o computador organiza uma struct dentro dos chips de silício?
Na memória RAM, um registro é alocado como um bloco único e contíguo de bytes. A variável aponta para o endereço inicial do bloco. Cada campo interno possui um deslocamento fixo (chamado de offset) em relação ao início:
- Endereço Base + 0 bytes →
codigo(4 bytes) - Endereço Base + 4 bytes → ponteiro do
nome(8 bytes) - Endereço Base + 12 bytes →
preco(8 bytes) - Endereço Base + 20 bytes →
quantidadeEstoque(4 bytes)
Isso significa que, quando você atribui prod2 = prod1, o computador copia o bloco de bytes inteiro de uma só vez com máxima eficiência de hardware!
5. Vetores de Registros (Arrays of Structs): O Poder Máximo
A verdadeira magia das estruturas compostas acontece quando combinamos Vetores com Registros.
Um Vetor de Registros é uma coleção indexada onde cada posição não guarda apenas um número solitário, mas sim uma entidade cadastral inteira:
// Vetor de Objetos Estruturados
vetor alunos = [];
alunos[0] = { nome: "Mariana Silva", matricula: 202601, nota: 9.5 };
alunos[1] = { nome: "Carlos Eduardo", matricula: 202602, nota: 7.0 };
alunos[2] = { nome: "Beatriz Ramos", matricula: 202603, nota: 8.5 };
para (numero i = 0; i < tamanho(alunos); i++) {
saida("Aluno(a): " + alunos[i].nome + " | Nota: " + alunos[i].nota);
}
Observe a clareza da sintaxe: turma[i] seleciona o aluno na posição i, e o .nome seleciona a propriedade daquele aluno específico. É a fusão perfeita entre a navegação sequencial dos laços e a semântica dos registros.
Ordenando um Vetor de Registros (Swap Atômico)
Lembra-se de como ordenar vetores paralelos era arriscado porque exigia trocar valores em 3 vetores diferentes ao mesmo tempo? Com vetores de registros, a troca é atômica e 100% segura!
Se quisermos ordenar os alunos por nota em ordem decrescente, comparamos apenas o atributo turma[j].notaFinal, mas a troca (swap) é realizada sobre a entidade Aluno completa usando uma variável auxiliar do tipo Aluno:
// Troca Atômica de Objetos Inteiros (Swap Limpo no Bubble Sort)
para (numero i = 0; i < tamanho(alunos) - 1; i++) {
para (numero j = 0; j < tamanho(alunos) - i - 1; j++) {
se (alunos[j].nota < alunos[j + 1].nota) {
// Troca atômica do objeto completo com todas as propriedades
registro temp = alunos[j];
alunos[j] = alunos[j + 1];
alunos[j + 1] = temp;
}
}
}
Ao trocar turma[j] = turma[j+1], o nome, a matrícula e a nota viajam juntos na mesma operação. O risco de misturar a nota de Alice com a de Bernardo cai a zero absoluto!
6. Registros Aninhados: Composição de Entidades
No mundo real, objetos raramente são planos. Um cliente possui um Endereço, e esse endereço possui rua, número, bairro e CEP.
Em programação, podemos colocar uma struct dentro de outra struct, um princípio arquitetural conhecido como Composição (Composition):
// Composição no logicabr.dev: um registro guardado dentro de outro
registro endereco = { rua: "Avenida Paulista", numero: 1500, cidade: "São Paulo" };
registro cliente = { nome: "Lucas Mendes", cpf: "123.456.789-00", endereco: endereco };
saida("Cliente: " + cliente.nome);
saida("Endereço: " + cliente.endereco.rua + ", nº " + cliente.endereco.numero);
7. A Ponte Natural para a Programação Orientada a Objetos (POO)
Compreender Registros é o maior divisor de águas na formação de um desenvolvedor, pois eles são o elo perdido entre a Lógica Estruturada e a Programação Orientada a Objetos:
Qual é a diferença exata entre uma struct e uma classe?
-
Struct / Registro: É um pacote passivo de dados. Ele apenas armazena informações (estado). Qualquer função externa pode ler e alterar os campos livremente, o que às vezes permite que alguém atribua uma idade negativa (
aluno.idade = -5) por engano. -
Classe / Objeto: É um pacote ativo de dados mais os comportamentos (funções/métodos) que operam sobre esses dados. Além disso, a classe oferece Encapsulamento (escondendo os dados sensíveis e exigindo que qualquer alteração passe por métodos de validação como
aluno.definirIdade(novaIdade)).
Quando você avança de Lógica de Programação para linguagens como Java, C#, Python ou TypeScript, você descobrirá que uma classe é simplesmente uma struct com superpoderes.
8. Laboratório Multi-Linguagem: Structs e Objetos no Mercado
Observe como diferentes linguagens modernas expressam o conceito de tipos de dados compostos:
#include <stdio.h>
typedef struct {
int id;
char nome[50];
double preco;
} Produto;
int main() {
Produto p = { 101, "Monitor 24pol", 899.90 };
printf("Item: %s | Preco: R$ %.2f\n", p.nome, p.preco);
return 0;
}
// TypeScript: Tipagem estrita com Interface
interface Produto {
id: number;
nome: string;
preco: number;
}
const catalogo: Produto[] = [
{ id: 1, nome: "Teclado", preco: 150.0 },
{ id: 2, nome: "Headset", preco: 280.0 }
];
catalogo.forEach(p => console.log(`${p.nome}: R$ ${p.preco}`));
from dataclasses import dataclass
@dataclass
class Produto:
id: int
nome: str
preco: float
estoque: int = 0
p1 = Produto(101, "Cadeira Gamer", 799.0, 10)
print(f"{p1.nome} possui {p1.estoque} unidades disponíveis.")
9. 5 Armadilhas Comuns ao Manipular Registros
-
Confundir a Sintaxe de Vetores de Registros: Escrever
produto.nomes[i]em vez deprodutos[i].nome. Lembre-se: o colchete de índice[i]acompanha quem é a coleção. Se a coleção é a lista de produtos, o índice vem primeiro:produtos[i]. O ponto seleciona o atributo específico dentro daquela posição. -
Copiar Registros com Ponteiros (Shallow Copy): Em linguagens de alto nível, atribuir
objetoB = objetoAmuitas vezes não cria uma cópia independente dos dados, mas apenas copia a referência na memória. ModificarobjetoB.nomeacaba alterando também o nome emobjetoA! -
Esquecer de Inicializar Registros Aninhados: Tentar acessar
cliente.endereco.ruasem antes ter instanciado a estruturaendereco, gerando o infame erro deNullPointerExceptionouundefined. - Passagem de Structs Pesadas por Cópia em Funções: Em linguagens compiladas como C e C++, passar um registro gigante por valor força o compilador a duplicar dezenas de bytes na pilha a cada chamada. O recomendado é passar por referência ou ponteiro para máxima performance.
- Campos Desnecessários: Criar um registro inflado com 40 campos soltos em vez de quebrar em pequenas structs menores e coesas via composição.
10. Projeto Prático Resolvido: Sistema Completo de Almoxarifado e Estoque
Abaixo apresentamos um algoritmo completo que simula a gestão de um almoxarifado de suprimentos de informática. Ele utiliza um vetor de registros, busca por código, alerta de reposição e relatório contábil:
// =========================================================================
// SISTEMA DE ALMOXARIFADO CENTRAL (logicabr.dev)
// =========================================================================
vetor estoque = [];
estoque[0] = { codigo: 101, descricao: "Cabo de Rede Cat6 3m", custoUnitario: 25.00, quantidade: 40, estoqueMinimo: 15 };
estoque[1] = { codigo: 102, descricao: "Conector RJ45 (Pacote)", custoUnitario: 45.00, quantidade: 8, estoqueMinimo: 10 };
estoque[2] = { codigo: 103, descricao: "Switch 8 Portas Gigabit", custoUnitario: 180.00, quantidade: 3, estoqueMinimo: 5 };
estoque[3] = { codigo: 104, descricao: "Patch Cord Fibra 1m", custoUnitario: 60.00, quantidade: 25, estoqueMinimo: 8 };
moeda patrimonioTotal = 0.0;
saida("================ RELATÓRIO GERAL DO ALMOXARIFADO ================");
saida("CÓDIGO | DESCRIÇÃO | QTD | UNIT (R$) | TOTAL (R$)");
saida("------------------------------------------------------------------");
// 1. Varredura com Cálculo Patrimonial e Alertas
para (numero i = 0; i < tamanho(estoque); i++) {
moeda subtotalItem = estoque[i].custoUnitario * estoque[i].quantidade;
patrimonioTotal = patrimonioTotal + subtotalItem;
saida(estoque[i].codigo + " | " + estoque[i].descricao + " | " + estoque[i].quantidade + " | R$ " + estoque[i].custoUnitario + " | R$ " + subtotalItem);
// Alerta de reposição se estoque estiver abaixo do mínimo
se (estoque[i].quantidade < estoque[i].estoqueMinimo) {
saida(" [ALERTA]: Estoque Crítico! Qtd (" + estoque[i].quantidade + ") abaixo do mínimo (" + estoque[i].estoqueMinimo + ")");
}
}
saida("------------------------------------------------------------------");
saida("VALOR PATRIMONIAL TOTAL IMOBILIZADO: R$ " + patrimonioTotal);
saida("==================================================================");
// 2. Busca de Item por Código de Barras
numero codigoBusca = 103;
numero indiceEncontrado = -1;
para (numero i = 0; i < tamanho(estoque); i++) {
se (estoque[i].codigo == codigoBusca) {
indiceEncontrado = i;
pare;
}
}
se (indiceEncontrado != -1) {
saida("[BUSCA] Busca pelo Código [" + codigoBusca + "]: Localizado!");
saida(" Produto: " + estoque[indiceEncontrado].descricao);
saida(" Disponibilidade imediata: " + estoque[indiceEncontrado].quantidade + " unidades.");
} senao {
saida("[ERRO] Item com código [" + codigoBusca + "] não encontrado no catálogo.");
}
11. Perguntas Frequentes sobre Registros e Structs (FAQ)
Posso colocar um vetor dentro de um registro?
Sim! É perfeitamente comum e incentivado. Por exemplo, um registro aluno pode ter um campo notas que é um vetor (ex: registro aluno = { nome: "Ana", notas: [8.5, 7, 9, 10] }) para armazenar as notas dos quatro bimestres do ano letivo. A sintaxe para acessar a terceira nota desse aluno é simplesmente aluno.notas[2].
Qual é a relação entre Registros e Bancos de Dados Relacionais (SQL)?
A correspondência é praticamente direta: a definição do Registro (tipo) equivale à tabela do banco de dados (as colunas e seus tipos), enquanto cada instância de Registro dentro de um vetor equivale a uma linha (tupla / registro) salva na tabela.
O que é o operador flecha (->) visto em C e C++?
Em C, quando você tem um ponteiro para uma struct em vez da struct direta, a sintaxe padrão seria (*ponteiro).campo (desreferenciar primeiro e acessar o campo depois). Para simplificar e evitar o excesso de parênteses, os criadores da linguagem criaram o atalho ponteiro->campo, que faz exatamente a mesma coisa.
Pratique com Registros e Structs na IDE Online!
Abra o editor interativo do logicabr.dev, execute o sistema de almoxarifado com vetor de structs e adicione novos itens e atributos diretamente no código.
Abrir IDE Online e Experimentar →