Sei sulla pagina 1di 7

Lista Encadeada Dinâmica

Conceitos de Ponteiro e implementação de Ponteiros em Pascal

As linguagens de programação modernas tornaram possível explicitar não apenas o acesso aos dados, mas
também aos endereços desses dados. Isso significa que ficou possível a utilização de ponteiros explicitamente.
Assim, existe uma distinção de notação entre um dado e a sua referência (ou endereço). Um ponteiro armazena o
endereço de memória onde um dado 9de um determinado tipo) está armazenado.

A notação introduzida por Wirth, com a introdução de Pascal, é:

Type
Dado = Seu Tipo de Dado;
TPPonteiro = ^Dado;
Esta declaração expressa que valores do tipo TPPonteiro são ponteiros para dados do tipo Dado. Ele armazena um
endereço de memória. Esse endereço deve ser a posição de início do armazenamento de um dado do tipo Dado.

Portanto, lemos o simbolo ^ como sendo ponteiro para... , e na declaração acima lemos 'TPPonteiro é um ponteiro
para variáveis do tipo Dado'.

O fato de que o tipo dos elementos apontados ser evidente na declaração do ponteiro tem importância
fundamental. Isso distingue ponteiros de linguagens de alto nível de endereços em Assembly.

Valores para ponteiros são gerados quando dados correspondentes a seus tipos são alocados/desalocados
dinamicamente. Em Pascal, os procedimentos dispose existem com esse propósito.

Portanto, deixa-se a cargo do programa (via linguagem de programação), e não do programador, prover e devolver
espaço para inserções e eliminações em tempo de execução (Observe mais abaixo os procedimentos de
manutenção da Lista Dinâmica).

Custo: Tempo de execução comprometido.

Idéia: O programador declara apenas o tipo de registro que contém um elemento da lista, e avisa que a alocação
será dinâmica. Sempre que requisitar um registro novo para a inserção, ou liberar um registro a ser eliminado, o
programador lança mão de duas rotinas pré-definidas para este fim.

Registros com ponteiros

Dizemos que uma variável do tipo TpPonteiro aponta para, ou é um ponteiro para um registro do tipo TpDado .
Ponteiro é o único tipo que pré-referencia outro em Pascal.

Type
TPDado = Record
Codigo : Integer;
Nome : String[30];
Salario : Real;
End;
TPPonteiro = ^TPDado;
Var
P : TPPonteiro;

Neste caso, a variável P é um ponteiro para um registro do tipo TPDado. P armazena o endereço de memória onde
o registro foi alocado. Se mandarmo apresentar o valor de P, ocorrerá um erro do tipo: "Error 64: Cannot read or
write variables of this type.". Isto significa que não é possivel apresentar o endereço do ponteiro. O Pascal não
permite que se leia nem escreva variáveis do tipo ponteiro. Quem fornece o valor do ponteiro é a própria
linguagem de programação. Em Pascal, para criarmos uma um novo ponteiro, utilizamos o procedimento New ();.
Veja o Exemplo:

Type
TPDado = Record
Codigo : Integer;
Nome : String[30];
Salario : Real;
End;
TPPonteiro = ^TPDado;
Var
P : TPPonteiro;
Begin
New(P); {cria um novo registro do tipo TPDado que será "apontado" por P}
End.

No exemplo acima, a instrução New(P), cria (aloca espaço) um novo registro apontado pelo ponteiro P.

Como acessar os dados armazenados em uma variável do tipo ponteiro?

Entendemos então que P é um ponteiro que não permite que seja apresentado nem lido o seu valor. Quem fornece
o valor do ponteiro é a própria linguagem de programação. Em Pascal, para criarmos uma um novo ponteiro,
utilizamos o procedimento New();. Assim, para acessarmos os dados armazenados, precisamos designar o
ponteiro, o objeto e os campos.

Notação
Ponteiro Objeto (Registro) Campos
P^.Codigo
P P^ P^.Nome
P^.Salario

Endereço nulo (terra)

Pascal provê uma constante pré-definida para denotar o endereço nulo nil. Podemos utilizá-la para atribuições e
testes, como nos exemplos abaixo:

1: P := nil;
2: If (P = nil) Then ...

Ponteiro x Objeto Apontado

Nos exemplos abaixo, é ilustrada a diferença entre as operações de atribuição entre ponteiros (por exemplo, p : =
q ) e a atribuição entre o conteúdo dos registros apontados pelos ponteiros (isto é: p^ : = q^).
Type
TPDado = Record
Codigo : Integer;
Nome : String[30];
Salario : Real;
End;
TPPonteiro = ^TPDado;
Var
P, Q : TPPonteiro;

Dada a situação abaixo, chamada de (a):

Dada a situação (a), após a atribuição p : = q temos a representação abaixo (b). Esta atribuição só é válida para
ponteiros do mesmo tipo e é a única operação entre ponteiros. Isso significa que o ponteiro P recebe o ponteiro Q.
Como os ponteiros armazenam endereços de memória, entende-se que a partir desta instrução, o ponteiro P passou
a apontar para o mesmo endereço de memória apontado pelo ponteiro Q. Assim, P e Q apontam para a mesma
informação.

Dada a situação (a), após a atribuição p^ : = q^ temos a representação abaixo (c), onde o conteúdo do ponteiro Q é
atribuido ao pontiero P. Assim, P e Q possuem o mesmo conteúdo, mas endereços diferentes.
Manipulação de Registros com ponteiros

1) Declaração de Variável

Type
TPDado = Record
Codigo : Integer;
Nome : String[30];
Salario : Real;
End;
TPPonteiro = ^TPDado;
Var
P : TPPonteiro;

2) Criação de um registro

Substitui o procedimento ObterNo(j) dada uma variável ponteiro do tipo TPPonteiro (^TDDado).

new(p);

a) efetivamente aloca uma variável do tipo TPDado


b) gera um ponteiro do ^TPDado apontando para aquela variável
c) atribui o ponteiro à variável P

A partir daí:

a) o ponteiro pode ser referenciado como P


b) variável referenciada por p é denotada por P^

3) Atribuição de conteúdo ao registro:

p^.codigo := valor;

4) Liberação de um registro

Substitui o DevolverNo(j) - dispose(p)

dispose(p);

a) operação libera o espaço apontado por P


b) P passa a ter valor indefinido

Lista Encadeada Dinâmica

Anteriormente, estudamos algumas estruturas de dados estáticas (lista, pilha e fila circular). Elas tinham esta
denominação porque os dados eram armazenados em um array. Assim, tinhamos de definir um tamanho máximo
de registros que seriam armazenados, tornando assim, as estruturas limitadas. A partir de agora, os dados serão
alocados dinamicamente na memória. Isso significa dizer que a medida em que for necessária a inclusão de um
novo dado, será criado um novo nó na memoria, atraves de um ponteiro e este novo nó será associado a lista
encadeada. Da mesma, quando da necessidade de se excluir um nó, a memória será liberada.
Em uma implementação de lista através de ponteiros, cada item (nó) da lista é encadeado com o seguinte através
de uma variável do tipo ponteiro. Isso significa dizer que cada nó da lista, contém o registro de dados
(informações) e uma variável que armazena o endereço do próximo nó. Este tipo de implementação permite
utilizar posições não contíguas de memória, sendo possível inserir e retirar elementos sem haver a necessidade de
deslocar os nós seguintes da lista.

A Figura 1 ilustra uma lista representada desta forma. Observe que existe um nó inicial denominado "Nó Cabeça".
Apesar no nó cabeça não conter dados válidos é conveniente fazê-lo com a mesma estrutura que um outro nó
qualquer para simplificar as operações sobre a lista.

A lista é constituída de "nós", onde cada nó contém um dado da lista (registro) e um ponteiro (prox) para o
proximo elemento da lista. O registro do tipo TPLista contém um apontado para o nócabeça (prim) e um
apontador para o último nó da lista (ult). O último nó da lista não tem próximo. O ponteiro próx do último nó
possui valo NIL (nulo).

A implementação através de ponteiros permite inserir ou remover dados em qualquer posição da lista a um custo
constante, aspecto importante quando a lista tem que ser mantida ordenada (no nosso caso a lista estará ordenada
pelo campo chave primária). Em aplicações em que não existe previsão sobre o crescimento da lista é conveniente
usar listas encadeadas por ponteiros (lista encadeada dinâmica), porque neste caso o tamanho máximo da lista não
precisa ser definido. A maior desvantagem deste tipo de implementação é a utilização de memória extra para
armazenar os apontadores.

Figura 1 - Representação de uma lista encadeada dinâmica.

Program Estoque;
Uses Crt, Dos;
Type
TpChave = integer;
TpDado = record
Codigo : TpChave;
Nome : String[50];
Preco : Real;
QtdeEst : Real;
QtdeMin : Real;
End;
TpPonteiro=^TpNo;
TpNo = record
Dado : TpDado; Figura 2 - Representação de um Nó da lista
Prox : TpPonteiro;
End;
TpLista = record
Prim : TpPonteiro;
Ult : TpPonteiro;
End;

Var {Declaração das variáveis globais}


Produto : TpLista;
Dado : TpDado;
P : TpPonteiro;

Op : Char;

Criando uma lista dinâmica vazia

Este procedimento recebe via prâmetro por referência uma lista dinâmica indefinida, cria o
ponteiro

Procedure IniciaLista (Var L:TpLista);


Begin
New(L.Prim);
L.Ult:=L.Prim;
L.Prim^.Prox:=Nil;
End;

Funtion ListaVazia (L : TpLista): Boolean;


Begin
ListaVazia:=L.prim=L.ult;
End;

Procedure Insere (Var L: TpLista; Dado: Tpdado; P: TpPonteiro);


Var
Aux : TpPonteiro;
Begin
New(Aux);
Aux^.Dado:=Dado;
Aux^.Prox:=P^.Prox;
P^.Prox:=Aux;
If Aux^.Prox=nil Then
Begin
L.ult := Aux;
End;
End;

Procedure Remove (Var L : TpLista; P: TpPonteiro; Var Dado : TpDado);


Var
Aux : TpPonteiro;
Begin
If P^.Prox = L.Ult Then
Begin
L.Ult := P;
End;
Aux:=P^.Prox;
P^.Prox:=Aux^.Prox;
Dado:=Aux^.Dado;
Dispose(Aux);
End;

Function Busca(L: TpLista; Cod: TpChave; Var P: TpPonteiro): Boolean;


Begin
P:=L.Prim;
While (P^.prox <> Nil) And (P^.prox^.Dado.Codigo < Cod) Do P:=P^.prox;
BuscaDin:=(P^.Prox <> Nil)And (P^.Prox^.Dado.código=Cod);
End;