INF01203 - Estruturas de Dados - Instituto de Informática (UFRGS) - Prof. Dennis Giovani Balreira



Material Extra - Lista Linear por Contiguidade Física (Dinâmica)

Na Aula 2c vimos a implementação estática da lista linear por contiguidade física (ListaContEst), com um vetor de capacidade fixa (Produto dados[CAPACIDADE];) — essa é a versão padrão usada no restante da disciplina. Este material apresenta a versão dinâmica dessa mesma lista, chamada ListaContDin, em que o vetor é alocado em tempo de execução com malloc(). Para os detalhes de cada operação (inserção, remoção, acesso, busca) e suas complexidades, veja a Aula 2c — aqui focamos apenas no que muda entre as duas versões.


O que muda

A ideia central é simples: em vez de um vetor de tamanho fixo dentro da própria struct, o campo dados passa a ser um ponteiro, apontando para um bloco de memória alocado no heap. Isso permite que a capacidade da lista seja definida em tempo de execução (por exemplo, lida do usuário), em vez de fixada em tempo de compilação.

Estática (Aula 2c) Dinâmica (este material)
Campo dados Produto dados[CAPACIDADE]; Produto *dados;
Onde mora Pilha (dentro da própria struct). Heap (apontado pela struct).
Capacidade Fixa, constante CAPACIDADE. Definida em tempo de execução, guardada em um campo capacidade.
Quem libera Ninguém — desaparece com a struct. O programador, com free().

A estrutura

typedef struct {
    int cod;
    char nome[50];
    float preco;
} Produto;

typedef struct {
    int tamanho;
    int capacidade;
    Produto *dados; // vetor alocado dinamicamente, em vez de tamanho fixo
} ListaContDin;

Note que, comparado à versão da Aula 2c, a única mudança na struct é trocar o vetor de tamanho fixo por um ponteiro, mais um novo campo capacidade para guardar o tamanho do bloco alocado (já que não existe mais uma constante CAPACIDADE fixa para consultar).


As funções permanecem praticamente iguais

Todas as operações (inicializar, tamanho, inserir, acessar, buscar, remover) têm exatamente a mesma lógica vista na Aula 2c — a única diferença de código é trocar toda ocorrência da constante CAPACIDADE pelo campo l->capacidade. Por exemplo, a verificação de lista cheia em inserir passa a ser:

if (l->tamanho == l->capacidade) return 0; // lista cheia

E o acesso a um elemento continua sendo l->dados[pos], idêntico ao da versão estática — o compilador trata l->dados[pos] da mesma forma esteja dados declarado como vetor ou como ponteiro. Duas funções realmente mudam, pois são as únicas que lidam diretamente com a alocação: inicializar (que agora aloca o vetor) e destruir (que agora precisa liberá-lo).

inicializar passa a receber a capacidade desejada como parâmetro, e aloca o vetor com malloc():

void inicializar(ListaContDin *l, int capacidade) {
    l->tamanho = 0;
    l->capacidade = capacidade;
    l->dados = (Produto *) malloc(capacidade * sizeof(Produto));

    if (l->dados == NULL) {
        printf("Erro: memoria insuficiente.\n");
        exit(1);
    }
}

destruir precisa efetivamente liberar a memória alocada, e não apenas zerar o tamanho como na versão estática:

void destruir(ListaContDin *l) {
    free(l->dados);
    l->dados = NULL;
    l->tamanho = 0;
    l->capacidade = 0;
}

Exemplo de uso

O uso é praticamente idêntico ao da versão estática — a única diferença visível para quem usa a lista é que inicializar agora recebe a capacidade como argumento, e é obrigatório chamar destruir ao final para não vazar memória:

int main() {
    ListaContDin l;
    Produto p1 = {1, "Caneta", 2.50};
    Produto p2 = {2, "Caderno", 15.90};
    Produto p3 = {3, "Lapis", 1.20};

    inicializar(&l, 10); // aloca espaço para 10 produtos, em tempo de execução

    inserir(&l, p1, 0); // lista: [Caneta]
    inserir(&l, p2, 1); // lista: [Caneta, Caderno]
    inserir(&l, p3, 1); // lista: [Caneta, Lapis, Caderno]

    printf("Produto na posicao 1: %s\n", acessar(&l, 1).nome);

    destruir(&l); // libera o vetor alocado — obrigatório na versão dinâmica

    return 0;
}

Resumo

  • A lista dinâmica troca Produto dados[CAPACIDADE]; por Produto *dados;, alocado com malloc(), e ganha um campo capacidade.
  • Todas as operações mantêm a mesma lógica da Aula 2c — apenas trocando CAPACIDADE por l->capacidade.
  • Apenas inicializar (que aloca) e destruir (que libera) realmente mudam de implementação.
  • O acesso a um elemento (l->dados[pos]) é idêntico nas duas versões.
  • Para detalhes de cada operação e suas complexidades, consulte a Aula 2c.