PONTEIROS


  1. Ponteiros

  Foi visto até aqui, que as variáveis armazenam dados na memória e esses dados podem ser de diversos tipos. Elas localizam-se em uma área separada do código executável, onde cada variável é um bloco de memória contendo informações como o tipo de dado, nome (ou identificador) e o dado em si.
  As varáveis que foram apresentadas até o presente momento são do tipo estáticas. Entretanto, elas podem ser de dois tipos: estáticas ou dinâmicas.
  Uma variável do tipo estática é uma variável que contém o valor do dado e este está sempre no mesmo lugar da memória. Por outro lado, uma variável do tipo dinâmica contém o endereço para o dado, em vez do dado em si. Dessa forma, a localização do dado pode variar livremente.
  Um exemplo para ilustrar a diferença entre variáveis estáticas e dinâmicas é o seguinte: uma variável estática é a A_CASA que contém o dado, enquanto que uma variável dinâmica é o ENDEREÇO_PARA_A_CASA que contém o dado.
  Um ponteiro declara uma variável do tipo dinâmica e sua representação em Pascal é o sinal de circunflexo "^".


  Sintaxe

  Um pointeiro, como toda variável em Pascal, também deve declarar o tipo de dado que irá referenciar. A sintaxe para declaração de ponteiros é a seguinte:
var nome_ponteiro : ^tipo_de_dado;
  Por exemplo, a criação de um ponteiro do tipo integer é:
var p : ^integer;

  Alocação de memória

  Nas variáveis estáticas, a alocação de memória para o dado é feita no ato da criação. No caso dos ponteiros, a alocação de memória deverá ser feita manualmente através do comando new(). Ex:
new(p);
  A seguir, observe o comportamento de alocação de memória para uma variável estática (i) e outra dinâmica (p).
Antes:
- var i : integer;
-     p : ^integer;

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** **             |

- i está locado em 04
- p aponta para 00 (ou nulo)


Depois:
- new(p)

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** **       ** ** |

- i está locado em 04
- p passa a apontar para 08

Obs: o sinal * indica espaço alocado na memória.
  Ao darmos o comando new(), um espaço em memória é alocado (o tamanho depende do tipo de dado declarado no ponteiro) e o ponteiro passa a conter o endereço inicial desse local na memória.
  Observe no esquema anterior, que o endereço de memória 08 agora está alocado e pronto para receber dados do tipo inteiro (2 bytes).
  Ao declararmos o ponteiro p, ele é inicializado apontando para uma posição de memória nula (endereço 0). Isso indica que esse ponteiro está "vazio". A palavra reservada NIL é utilizada para testar se o ponteiro está "vazio". Ex:
var p : ^integer;

procedure testa_ptr;
begin
  if p = nil then
    writeln('Ponteiro nulo.')
  else
    writeln('Ponteiro referenciado.');
end;

begin
  testa_ptr;
  new(p);
  testa_ptr;
end.
  Saída:
  Ponteiro nulo.
  Ponteiro referenciado.


  Leitura / escrita de dados com ponteiros

  Confome dito, o ponteiro é uma variável que contém o endereço de memória para o dado. Assim, para referenciar o dado na memória, deve-se acrescentar o sinal de circunflexo "^" ao final do nome do ponteiro.
  Um exemplo de atribuição de valor com ponteiro é o seguinte:
p^ := 4;
  Esse comando irá alterar o conteúdo da memória apontado por p para o valor igual a 4.
Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** **       04 00 |
  Para recuperar a informação de um dado apontado pelo ponteiro, devemos fazer do mesmo modo:
var i : integer;
    p : ^integer;

begin
  new(p);
  p^ := 4;
  i := p^;
end.
  No exemplo acima, a variável i recebe o dado apontado por p. Na memória:
Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             04 00       04 00 |
  Obs: Se o ponteiro estiver apontando para um endereço de memória que não esteja alocado para armazenamento de dados e uma tentativa de acesso aos dados for feita, um erro acontecerá. Ex:
p aponta para 00

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** **             |

A instrução writeln(p^) gerará um erro.

  Obtendo o endereço de variáveis estáticas

  Podemos também obter o endereço no qual uma variável estática está localizada, através do função addr(). E através do resultado dessa função, podemos modificar o endereço de um ponteiro para o endereço de uma variável estática. Veja o programa a seguir.
var i : integer;
    p : ^integer;

begin
  i := 10;
  p := addr(i);

  writeln('Conteudo apontado por p: ', p^);
end;
Saída:
Conteudo apontado por p: 10

  Diagramação:
p := addr(i);

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             0A 00             |

- i está locado em 04
- p passa a apontar para 04 e pode também ler o conteúdo em memória, 
pois o espaço já fora alocado por i.
  Lembre-se que:
  Obtendo o valor do endereço de uma variável

  O programa a seguir exibe também o valor de memória do ponteiro. Esse valor não é um endereço físico de memória, e sim um endereço relativo, circunscrito ao programa em questão.
var num : integer;
    p : ^integer;
    mem_addr : ^word;

begin
  num := 10;
  p := addr(num);
  mem_addr := addr(p);

  writeln('Valor da variavel num: ', num);
  writeln('Valor do ponteiro p: ', p^);
  writeln('O endereco das variaveis num e p: ', mem_addr^);
end.

  Lendo ponteiros de tipos diferentes

  Podemos também utilizar um ponteiro de um determinado tipo para apontar para uma variável de outro tipo. O programa a seguir faz isto, visto que a variável é do tipo inteiro (2 bytes) e iremos analisar o conteúdo do dado em memória, byte a byte.
var num, i : integer;
    p : ^byte;
    
begin
  num := 10;
  p := addr(num);

  writeln('Valores dos bytes armazenados na memoria para a variavel num:');

  for i:=0 to 1 do
  begin
    writeln('Endereco ', i, ' - ', p^);
    p:= p + 1;
  end;
end.
Saída:
Valores dos bytes armazenados na memoria para a variavel num:
Endereço 0 - 10
Endereço 1 - 0

  Eis o conteúdo da variável "num" em memória: 10 00 (sistema little endian, onde a ordem dos números de 16 bits são invertidos na memória. Assim, o normal fica 00 10).

  O programa a seguir analisa o conteúdo de memória onde a variável do tipo string[10] armazena os dados.
var i : integer;
    p : ^byte;
    nome : string[10];

begin
  nome := 'Poliana';
  p := addr(nome);

  writeln('Valores dos bytes armazenados na memoria para a variavel num:');

  for i:=0 to 9 do
  begin
    write('Endereco ', i, ' - ', p^, ' - ');
    if (p^ > 13) then writeln(chr(p^)) else writeln;
    p:= p + 1;
  end;
end.
Saída:
Endereco 0 - 7 -
Endereco 1 - 80 - P
Endereco 2 - 111 - o
Endereco 3 - 108 - l
Endereco 4 - 105 - i
Endereco 5 - 97 - a
Endereco 6 - 110 - n
Endereco 7 - 97 - a
Endereco 8 - 0 -
Endereco 9 - 0 -

  Ao lado do valor de cada byte, está o código ASCII correspondente.
  Repare na posição 0 de memória: ela contém o comprimento da string, que é 7. O conteúdo de string não utilizado é marcado com o valor 0 (endereços 8 e 9).
  Obs: o comando em Pascal length() exibe o comprimento da string. Ex:
writeln('O nome ', nome, ' possui ', length(nome), 'caracteres.');

  Referência perdida

  Como podemos dar o comando new() para alocar memória várias vezes, temos que tomar cuidado com o seguinte problema: ao ser dado um novo comando new(), uma nova área de memória é alocada, o endereço do ponteiro p é alterado e o endereço anterior é perdido. Veja o exemplo a seguir.
1o. new(p)

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** **       ** ** |

- p aponta para 08


2o. new(p)

Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |             ** ** ** ** ** ** |

- p passa a apontar para 06 e a referência para 08 é perdida!
  Conforme pode ser observado no diagrama anterior, o endereço de p é alterado para 06 e o dado contido no endereço 08 passa a não ter mais qualquer varável apontada para ele, ou seja, este dado está "perdido".
  Para resolver essa situação, deve-se criar outra variável do tipo "ponteiro" para armazenar o endereço 08.
var : p, q : ^integer;

begin
  new(p);

  q := p;  { Salva endereço }

  newp(p);
end.
  Nesse caso, q apontaria para 08, p apontaria para 06 e ambos os dados estariam referenciados.

  Outra questão é: e se eu não quisesse mais aquele dado contido no endereço 08? É só deixar prá lá? Não!
  Foi visto que o comando new() aloca posições na memória. Esses locais ficam "marcados" e indisponíveis para a utilização. Assim, o "lixo" deverá ser apagado da memória, de forma a disponibilizar mais espaço. Do contrário, a memória pode "estourar".
  O comando dispose() libera a memória alocada por new(). Entretanto, este comando deverá ser feito antes do novo comando new(), senão, o dado apagado será o atual e não o antigo. Ex:
var : p : ^integer;

begin
  new(p);

  dispose(p); { Descarta posição de memória atual }

  newp(p);
end.
  Diagrama:
1o. new:
Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |                         ** ** |

dispose:
Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |                               |

2o. new:
Endereço de memória: | 00 01 02 03 04 05 06 07 08 09 |
Conteúdo da memória: |       ** **                   |

  Ponteiros como parâmetros de funções e procedimentos

  Não é possível declarar diretamente uma variável do tipo ponteiro na lista de parâmetros de uma função/procedimento. Ex:
procedure recebe_ptr(p : ^integer);
  Ao compilarmos o código acima, geraria um erro.
  Entretanto, é possível passar ponteiros como parâmetro. Para isso, deve-se criar um tipo de variável ponteiro. Ex:
type PInt = ^integer;

procedure recebe_ptr(p : PInt);
  Podemos reescrever o programa da seção de alocação de memória, de forma a receber qualquer ponteiro.
type PInt = ^integer;
var p, q : PInt;

procedure testa_ptr(p : PInt);
begin
  if p = nil then
    writeln('Ponteiro nulo.')
  else
    writeln('Ponteiro referenciado.');
end;

begin
  testa_ptr(p);
  new(p);
  testa_ptr(p);
  testa_ptr(q);
end.
  Saída:
  Ponteiro nulo.
  Ponteiro referenciado.
  Ponteiro nulo.


  Criação de listas dinâmicas - introdução

  Conforme observado nesses últimos exemplos, uma variável dinâmica é capaz de alocar e desalocar diversos espaços na memória, diferentemente da variável estática, que somente aloca o seu espaço. Esses recursos da variável dinâmica são úteis para a criação de listas dinâmicas. Por exemplo:
var p : ^integer;
    i : integer;

begin
  for i:=1 to 4 do
  begin
    new(p);
  end;
end.
  O programa acima irá criar "variáveis" (espaço alocado para um número inteiro) na memória:
 +---+  +---+  +---+  +---+
 |   |  |   |  |   |  |   |
 |   |  |   |  |   |  |   |
 +---+  +---+  +---+  +---+
                        ↑
                        p
  As "variáveis" criadas em memória são representadas pelas caixinhas, e a ordem de introdução é da esquerda para a direita.
  Visto que o programa criou quatro "variáveis" do tipo inteiro, cada uma delas é um elemento de uma lista. Uma vez que se trata de uma variável dinâmica, podemos acrescentar o retirar elementos dessa lista. Isto representa uma vantagem sobre os vetores, pois os vetores possuem tamanho fixo.
  Entretanto, observa-se que a lista criada pelo programa possui elementos não referenciados (ou perdidos na memória). Da forma como essa lista foi criada, não podemos acessar todos os elementos. Podemos acessar somente o último elemento, que é referenciado pelo ponteiro p.
  O truque para contornar esse problema é adicionar um ponteiro a cada elmento da lista, referenciando o próximo elemento. Isto é feito, criando-se uma variável do tipo composta (type ou tipo).
  Na seção seguinte, que aborda as listas dinâmicas em detalhe, veremos como resolver esse problema, bem como gerenciar as listas dinâmicas.



  2. Listas Encadeadas (ou Dinâmicas)

  Foi apresentado até agora uma maneira de armazenar múltiplos dados - em uma lista estática ou vetor. As principais características de um vetor são:
  Existe também a possibilidade de criar uma lista dinâmica, chamada de lista dinâmica ou encadeamento. As principais características de uma lista encadeada são:
  Observa-se que uma lista do tipo encadeada cria elemento a elemento, conforme a necessidade, e que cada elemento é criado em uma posição aleatória na memória. Assim, torna-se necessário registrar em algum lugar a localização de cada elemento. De outra forma, a localização desse elemento não será possível, uma vez que o seu endereço é desconhecido.

  Normalmente, cada elemento de um lista encadeada armazena o endereço para o próximo elemento da lista (isso é desnecessário no vetor, pois estão em posições consecutivas na memória), solucionando o problema da localização, e uma variável qualquer irá conter o endereço inicial da lista, ou seja, será o ponto de entrada da lista. É por essa razão, que o acesso a um elemento na posição i da lista faz com que seja necessário o percorrimento de i posições lista.

  Um ponteiro é um tipo de variável que armazena endereço de memória. Este é o tipo de dado que será utilizado para armazenar a localização de cada elemento de uma lista encadeada.

  No capítulo de tipos, vimos uma variável composta chamada varficha, que é uma ficha que armazena dados. Vimos também que esta variável podia ser armazenada em um vetor. Agora, vamos criar uma lista encadeada do tipo "ficha", para ilustrar o funcionamento de listas dinâmicas.

  Os passos necessários são:
  1. Criar tipo "ficha".
  2. Criar tipo ponteiro para ficha.
  3. Criar variável do tipo ponteiro para ficha.
  4. Criar uma ficha na memória.
  5. Guardamos o endereço dela.
  6. Criamos uma nova ficha.
  7. Apontar o elemento atual para o anterior.

  Os passos 1 e 2 são resolvidos assim:
type 
  p_varficha = ^varficha;  { ponteiro para varficha }
  varficha = record
      nome : string[40];
      idade : integer;
      peso : real;
      prox : p_varficha;
    end; 
  O símbolo de circunflexo "^", seguido do tipo de dado é utilizado para indicar uma variável do tipo ponteiro.

  Passo 3:
var p, q : pont_varficha; 

  Criar uma ficha na memória - Passo 4:
new(p);

  Guardar o endereço da ficha criada - Passo 5:
q := p;

  Criar uma nova ficha na memória - Passo 6:
new(p);

  Apontar o elemento atual para o anterior - Passo 7:
p^.prox := q;
  Foi visto nos vetores, que o acesso à ficha é feito através do nome da variável, seguida da posição no vetor e por um ponto seguido do nome da variável interna do tipo composto. Exemplo ficha[1].nome acessa o nome contido na ficha do primeiro elemento do vetor.

  Na lista encadeada, o acesso aos elementos é feito através dos ponteiros. Assim, o comando acima referente aos vetores, seria equivalente para listas encadeadas a seguinte forma: p^.nome.

  Observa-se também que a variável q armazenou o endereço do primeiro elemento. Assim, quando informamos à ficha atual o endereço q, indicamos o endereço da primeira ficha criada.

  Abaixo é ilustrada a seqüência de criação de fichas usando ponteiros. O endereço de cada ficha, neste exemplo, é meramente ilustrativo.






  Observa-se que a lista criada é uma lista invertida ou do tipo "pilha", onde os "últimos serão os primeiros".

  No exemplo acima, ao utilizarmos o ponteiro p, estaremos acessando a segunda ficha criada, e ao utilizarmos o ponteiro q, estaremos acessando a primeira ficha. Ou seja, temos o poder de acessar as duas fichas ao mesmo tempo. Assim, podemos inverter a ordem da lista, substituindo
p^.prox := q;
por
q^.prox := p;

  Com isso, o sentido das setas na ilustração é invertido.

  O ponteiro da primeira ficha não contém qualquer endereço. Na verdade, deveríamos reportar isto na sua criação (ela era a primeira ficha). O valor NIL indica que o ponteiro não aponta para lugar algum. Isto nos ajuda a achar o final da lista. Assim, no ato da criação da primeira ficha, deve-se fazer:
p^.prox := nil;

LISTAS SIMPLESMENTE ENCADEADAS

  São listas que contém o endereço de apenas uma ficha.



LISTAS DUPLAMENTE ENCADEADAS

  São listas que contém endereço da ficha anterior e posterior.



  Vejamos agora um exemplo completo de criação de listas encadeadas.
type
  p_varficha = ^varficha;
  varficha = record
      nome : string[40];
      idade : integer;
      peso : real;
      prox : p_varficha;
    end;

var p,q : p_varficha;
      i : integer;

begin
  q := nil;
  for i:=1 to 10 do
  begin
    new(p);

    write('Nome: ');
    readln(p^.nome);
    write('Idade: ');
    readln(p^.idade);
    write('Peso: ');
    readln(p^.peso);

    if (q=nil) then
      p^.prox:=nil
    else
      p^.prox:=q;

    q:=p;
  end;
end.
  Este programa cria 10 fichas simplesmente encadeadas. A primeira ficha aponta para o vazio, a segunda para a primeira, e assim por diante.

  Se desejarmos uma lista duplamente encadeada, faríamos:
type 
  p_varficha = ^varficha;
  varficha = record
      nome : string[40];
      idade : integer;
      peso : real;
      prox : p_varficha;
      ant : p_varficha;  { novo }
    end;

var p,q : p_varficha;
      i : integer;

begin
  q:=nil;
  for i:=1 to 10 do
  begin
    new(p);

    write('Nome: ');
    readln(p^.nome);
    write('Idade: ');
    readln(p^.idade);
    write('Peso: ');
    readln(p^.peso);

    if (q=nil) then
      p^.prox:=nil
    else
    begin           { novo }
      p^.prox:=q;
      q^.ant:=p;    { novo }
    end;            { novo }

    q:=p;

    if (i=10) then  { novo }
      p^.ant:=nil;  { novo - Última ficha criada }
  end;
end. 
  Vamos ver no esquema abaixo, o que a dupla associação faz:


  Obs: conforme dito anteriormente, o número de elementos aqui é livre (até onde a memória permitir, é claro!). Observe no programa anterior que, cada elemento é criado dinamicamente (função new), conforme a necessidade.

  REMOÇÃO DE ELEMENTOS

  É possível remover elmentos da lista. Para apagar um elemento, utiliza-se a função dispose(ponteiro). Aqui está a simplicidade em relação aos vetores. Em vez de deslocar os elementos na lista, basta indicar aos elemento anterior e posterior, relativos ao elemento atual que será removido, os novos endereços de anterior e posterior.

  Passos para remover um elemento na posição i:
  1. Indicar ao elemento da posição i-1 o novo endereço de próximo, que é i+1, em vez de i.
  2. Indicar ao elemento da posição i+1 o novo endereço de anterior, que é i-1, em vez de i.
  3. Apagar elemento na posição i.
  Procedimento para remover um elemento:
procedure remove_no(p : p_varficha);
var ant, prox : p_varficha;
begin
  ant := p^.ant;
  prox := p^.prox;

  ant^.prox := prox;
  prox^.ant := ant;

  dispose(p);
end;
  Obs: É necessário fazer a verificação se um elemento é o primeiro ou o último da lista. Assim, o código fica:
procedure remove_no(p : p_varficha);
var ant, prox : p_varficha;
begin
  ant := p^.ant;
  prox := p^.prox;

  if (ant <> nil) then ant^.prox := prox;
  if (prox <> nil) prox^.ant := ant;

  dispose(p);
end;
  A seguir um programa completo que exibe a lista completa e também remove um elemento da lista.
type
  p_varficha = ^varficha;
  varficha = record
      nome : string[40];
      idade : integer;
      peso : real;
      prox : p_varficha;
      ant : p_varficha;
    end;

var p, q, prim : p_varficha;
      i, tam_lista : integer;

procedure lista_nos;
var i : integer;
  pos : p_varficha;
begin
  pos := prim;

  for i:=1 to tam_lista do
  begin
    writeln('Pessoa ', i);
    writeln(' Nome: ', pos^.nome);
    writeln(' Idade: ', pos^.idade);
    writeln(' Peso: ', pos^.peso);
    writeln;
    pos := pos^.prox;
  end;
end;

procedure remove_no(p : p_varficha);
var ant, prox : p_varficha;
begin
  ant := p^.ant;
  prox := p^.prox;

  if (ant <> nil) then ant^.prox := prox;
  if (prox <> nil) then prox^.ant := ant;

  dispose(p);

  tam_lista := tam_lista - 1;
end;

begin
  q:=nil;

  for i:=1 to 3 do
  begin
    new(p);

    write('Nome: ');
    readln(p^.nome);
    write('Idade: ');
    readln(p^.idade);
    write('Peso: ');
    readln(p^.peso);

    if (q=nil) then
      p^.prox:=nil
    else
    begin
      p^.prox:=q;
      q^.ant:=p;
    end;

    q:=p;

    if (i=10) then
      p^.ant:=nil;
  end;

  prim := p;

  tam_lista := 3;

  lista_nos;

  remove_no(prim^.prox); { remove o segundo elemento }

  lista_nos;
end.
Saída:
Nome: Pedro
Idade: 21
Peso: 68
Nome: Fatima
Idade: 23
Peso: 55
Nome: Gilda
Idade: 22
Peso: 56

Pessoa 1
Nome: Gilda
Idade: 22
Peso: 5.60000000000000E+001

Pessoa 2
Nome: Fatima
Idade: 23
Peso: 5.50000000000000E+001

Pessoa 3
Nome: Pedro
Idade: 21
Peso: 6.80000000000000E+001

Pessoa 1
Nome: Gilda
Idade: 22
Peso: 5.60000000000000E+001

Pessoa 2
Nome: Pedro
Idade: 21
Peso: 6.80000000000000E+001

  INSERÇÃO DE ELEMENTOS

  Para inserir um novo elemento no vetor, o procedimento é similar ao da remoção. Note que não existe posição fixa em uma lista encadeada. Por exemplo, para inserir um novo elemento entre a posição 4 e 5, deve-se fazer:
  1. Criar o elmento novo: new(p).
  2. Indicar à variável de próximo do elemento 4 o endereço desse novo elemento.
  3. Indicar à variável de anterior do elemento 5 o endereço desse novo elemento.
  4. Notificar ao elemento novo os elementos anteriores e poesteriores.
  Assim, o quarto elemento continua a ser o quarto, o quinto elemento passa a ser o sexto e o novo elemento passa a ser o quinto.

  O código de inserção é o seguinte:
procedure insere_no(p : p_varficha);
var ant, q : p_varficha;
begin
  ant := p^.ant;

  q := cria_no;

  if (ant <> nil) then ant^.prox := q;
  p^.ant := q;

  if (ant <> nil) then q^.ant := ant else q^.ant := nil;
  q^.prox := p;

  tam_lista := tam_lista + 1;
end;
  Vamos incorporar essas mudanças ao código completo de remoção apresentado, melhorando um pouquinho o código. Na execução, foram inseridos 3 nomes (Pedro, Fatima e Gilda), removida a Fatima e acrescentado o Paulo entre o primeiro e o segundo elemento.
type
  p_varficha = ^varficha;
  varficha = record
      nome : string[40];
      idade : integer;
      peso : real;
      prox : p_varficha;
      ant : p_varficha;
    end;

var p, q, prim : p_varficha;
      i, tam_lista : integer;

procedure lista_nos;
var i : integer;
  pos : p_varficha;
begin
  pos := prim;

  for i:=1 to tam_lista do
  begin
    writeln('Pessoa ', i);
    writeln(' Nome: ', pos^.nome);
    writeln(' Idade: ', pos^.idade);
    writeln(' Peso: ', pos^.peso);
    writeln;
    pos := pos^.prox;
  end;
end;

function cria_no : p_varficha;
var p : p_varficha;
begin
  new(p);

  write('Nome: ');
  readln(p^.nome);
  write('Idade: ');
  readln(p^.idade);
  write('Peso: ');
  readln(p^.peso);

  cria_no := p;
end;

procedure remove_no(p : p_varficha);
var ant, prox : p_varficha;
begin
  ant := p^.ant;
  prox := p^.prox;

  if (ant <> nil) then ant^.prox := prox;
  if (prox <> nil) then prox^.ant := ant;

  dispose(p);

  tam_lista := tam_lista - 1;
end;

procedure insere_no(p : p_varficha);
var ant, q : p_varficha;
begin
  ant := p^.ant;

  q := cria_no;

  if (ant <> nil) then ant^.prox := q;
  p^.ant := q;

  if (ant <> nil) then q^.ant := ant else q^.ant := nil;
  q^.prox := p;

  tam_lista := tam_lista + 1;
end;

begin
  q:=nil;

  for i:=1 to 3 do
  begin
    p := cria_no;

    if (q=nil) then
      p^.prox:=nil
    else
    begin
      p^.prox:=q;
      q^.ant:=p;
    end;

    q:=p;

    if (i=10) then
      p^.ant:=nil;
  end;

  prim := p;

  tam_lista := 3;

  lista_nos;

  remove_no(prim^.prox);

  lista_nos;

  insere_no(prim^.prox);

  lista_nos;

end.
Saída:
Nome: Pedro
Idade: 21
Peso: 68
Nome: Fatima
Idade: 23
Peso: 55
Nome: Gilda
Idade: 22
Peso: 56
Pessoa 1
Nome: Gilda
Idade: 22
Peso: 5.60000000000000E+001

Pessoa 2
Nome: Fatima
Idade: 23
Peso: 5.50000000000000E+001

Pessoa 3
Nome: Pedro
Idade: 21
Peso: 6.80000000000000E+001

Pessoa 1
Nome: Gilda
Idade: 22
Peso: 5.60000000000000E+001

Pessoa 2
Nome: Pedro
Idade: 21
Peso: 6.80000000000000E+001

Nome: Paulo
Idade: 20
Peso: 64
Pessoa 1
Nome: Gilda
Idade: 22
Peso: 5.60000000000000E+001

Pessoa 2
Nome: Paulo
Idade: 20
Peso: 6.40000000000000E+001

Pessoa 3
Nome: Pedro
Idade: 21
Peso: 6.80000000000000E+001


/MARMSX/CURSOS/PASCAL