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):

logicabr.dev (Anti-Padrão: Vetores Paralelos)
// 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];
Comparativo entre o caos dos vetores paralelos desincronizados e o registro único de entidade
Figura 1: Vetores Paralelos vs Registro Coeso. Quando múltiplos vetores tentam descrever a mesma coisa, qualquer troca de posição desincroniza a base de dados inteira.

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] com precos[i+1], se você se esquecer de trocar simultaneamente nomes[i], estoques[i] e disponiveis[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 como codigoBarras obrigará 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.

A Metáfora da Ficha Cadastral: Pense em um Registro como uma ficha de papel de um prontuário médico: a ficha em si é uma única folha física (uma entidade), mas dentro dela existem caixas de texto com rótulos diferentes: "Nome Completo" (texto), "Data de Nascimento" (data), "Peso em KG" (real) e "Fumante?" (booleano). Todos esses campos viajam juntos dentro da mesma pasta.

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.

logicabr.dev (Estrutura de Objeto / Registro)
// 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:

logicabr.dev (Atribuição e Acesso a Campos via Notação de Ponto)
// 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:

Diagrama mostrando vetor de registros com alunos[0], alunos[1] e alunos[2] indexados com atributos internos
Figura 2: Vetor de Registros na Memória. A sintaxe composta 'alunos[i].campo' permite varrer listas com centenas de entidades ricas de forma segura.
logicabr.dev (Vetor de Registros / Coleção de Objetos)
// 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:

logicabr.dev (Ordenação de Objetos com Troca Atômica de Referência)
// 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):

logicabr.dev (Composição de Objetos Estruturados)
// 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:

Linha do tempo evolutiva de variáveis primitivas para vetores, registros e objetos com métodos
Figura 3: A Evolução Histórica da Modelagem de Dados. Das variáveis atômicas soltas às Classes com dados e comportamentos encapsulados.

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:

C (struct nativa do ANSI C)
#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;
}
JavaScript / TypeScript (Objetos e Interfaces)
// 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}`));
Python (Dataclasses do Python 3.7+)
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

  1. Confundir a Sintaxe de Vetores de Registros: Escrever produto.nomes[i] em vez de produtos[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.
  2. Copiar Registros com Ponteiros (Shallow Copy): Em linguagens de alto nível, atribuir objetoB = objetoA muitas vezes não cria uma cópia independente dos dados, mas apenas copia a referência na memória. Modificar objetoB.nome acaba alterando também o nome em objetoA!
  3. Esquecer de Inicializar Registros Aninhados: Tentar acessar cliente.endereco.rua sem antes ter instanciado a estrutura endereco, gerando o infame erro de NullPointerException ou undefined.
  4. 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.
  5. 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:

logicabr.dev (Sistema de Almoxarifado Central - 100% Funcional)
// =========================================================================
// 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 →