Ir para o conteúdo

Ponteiros

As variáveis ditas “normais”, têm uma característica comum, são entidades estáticas no que concerne à memória e são alojadas na stack memory.

Uma variável dinâmica é criada (na heap memory) e destruída durante a execução do programa.

A variável é referenciada por um apontador que aponta para um local da memória, isto é, enquanto uma variável pode ser alocada em qualquer local da memória que para nós é indiferente, a variável do tipo apontador vai apontar para um local específico da memória.

A título de exemplo, imaginem uma televisão na horizontal. Se colocarmos em cima do ecrã um copo (a nossa variável) com uma bola dentro (conteúdo da variável) nós vimos sempre a bola. Podemos trocar a bola por outro objecto qualquer que caiba no copo. Se en vez de um copo colocarem um rolo de cozinha (ponteiro) e olharmos lá para dentro vimos a imagem que no momento estiver na zona do ecrã onde se encontra o rolo de cozinha, podendo variar. É claro que este exemplo é discutível. A ideia é que o ponteiro mostra-nos a informação da zona de memória para onde ele está a apontar.

Declaração

Type Nome_do_ponteiro=^Tipo_base

Todas as variáveis declaradas como sendo de um tipo dinâmico, vão apontar para dados do tipo base.

Exemplo como se podem declarar apontadores para inteiros

Type
  IntPtr=^Integer;
Var
  V1: IntPtr;

No caso de querem 3 variaveis do tipo IntPtr declarariam assim:

Var
        V1, V2, V3: IntPtr;

Existem dois procedimentos standard em Pascal que permitem criar e destruir variáveis dinâmicas:

  • New(v1); – Aloca espaço na memória e cria uma variável dinâmica do tipo base declarado para v1.
  • Dispose(v1); – Destrói a variável dinâmica apontada por v1 e liberta o espaço em memória para uso futuro.

Aproveitando o exemplo acima, vamos criar, atribuir um valor e destruir a variável

begin
  Write('Indique um número: ');
  New(v1);
  Readln(v1^);
  V1^ := v1^ + 5;
  Write(' O seu número adicionado de 5 unidades:  ',V1^);
  Dispose(v1);
end.

De realçar que os apontadores apenas levam ^ para se aceder à informação por ele apontada.

Poderão dizer pelo que até agora que os ponteiros são iguais às variáveis, só que com mais trabalho... Efectivamente nesta primeira parte, sim, mas o melhor vem a seguir.

Listas ligadas

Quem já não teve de declarar um array enorme e mesmo assim pensar que, numa situação particular qualquer pode não chegar? Pois é, com os ponteiros podemos criar listas ligadas sem nos preocuparmos com o tamanho. É só alocar espaço para o próximo “indice”... haja memória, claro! Para o fazermos, temos que recorrer a um tipo composto: os records. Neste record, colocamos um tipo especial... o apontador. Assim teríamos na declaração:

Type
  Ptr = ^Rec;
  Rec = record
    Marca: string[25];
    Tamanho: Integer;
    NextPtr: Ptr; {Serve para nos indicar o campo seguinte. Se for o último deve apontar para NIL}
  End;

Var
  Inicio, NovoReg , Aux1, RegAnterior: IntPtr;

Importante: Deve-se sempre colocar o último elemento da lista a apontar para NIL, caso contrário estamos sujeitos ao ponteiro ficar a apontar para um qualquer local da memória e termos surpresas desagradáveis... Querem fazer um vírus com os ponteiros? EASY!!! O S.O. carrega no início da memória

Continuando, quando se trata de construir listas ligadas ordenadas temos de ter em atenção 3 aspectos:

  • Descobrir onde pertence o registo;
  • Criar um nó (ajustar os ponteiros);

Lista ligada

Inserindo um Registo B

Inserção de novo elemento na lista ligada

Então pegando na declaração anterior, vamos inserir um registo numa lista ligada ordenada pela marca.

Procedure le_insere_reg;
Begin
  New(NovoReg);
  Write('Marca do tubo: ');
  readln(NovoReg^.marca);
  Write('Tamanho: ');
  readln(NovoReg^.tamanho);
  Aux1:=Inicio;
  if (Incio=NIL) OR  (NovoReg^.marca<Aux1^.marca) then begin {1º elemento da lista se a lista está vazia, ou se marca menor que o primeiro elemento da lista}
    NovoReg^.NextPtr:=Inicio;
    Inicio:=NovoReg {NovoReg Não leva ^ porque queremos que Inicio aponte para a zona de memória onde está o NovoReg e não o seu conteúdo}
  end
  else begin   {Vamos percorrer a linha que tem inicio em Inicio e não queremos perder a referencia de memória de Inicio para o 1º registo da lista, então usa-se uma variável auxiliar atribuida anteriormente em Aux1:=Inicio;}
      While (NovoReg^.marca>=Aux1^.marca) AND (Aux1<>NIL) do begin
        RegAnterior:=Aux1;
        Aux1:=Aux1^.NextPtr;
      end;
      NovoReg^.NextPtr:=Aux1;
      RegAnterior^.NextPtr:=NovoReg;
  end;
end;

Foi este principio que me fez passar de um tempo de listagem de 3 Horas para 3 minutos!

Ainda havia mais para dizer acerca das listas ligadas tal como listas duplamente ligadas, listas sem fim em que o último está ligado ao primeiro, mas o essencial está aqui.