Lecture 20: Apontadores


Apontador

Um apontador é um tipo de variável especial. O apontador não contem informação útil, mas só contem o endereço da informação.
 Um apontador contem o endereço de um lugar na memória 

Isto é parecido com guardar um endereço de um apartemento no meu livro de endereços. É só um endereço e nada mais.
Para declarar um apontador usamos a sintaxe seguinta:
  Var p: pointer;


Instruções para apontadores

Existem duas instruções básicas para apontadores em PASCAL
 
 
 @x ou Addr(x) retorna o endereço do objecto x
 p^ é o conteúdo do endereço p

 
No exemplo aqui ao lado, x é uma variável do tipo real com valor 3.0. Nós queremos que o apontador p aponta à esta variável. Por isso usamos a instrução
  p := @x;
ou
  p := Addr(x);
O valor de p é agora um endereço, nomeademente o endereço da variável x. Para mostrar o conteúdo deste endereço a idea poderia ser usar a instrução
  WriteLn(p^);
Isto tem um problema. O compilador não sabe o que é que o p está a apontar. Qual o tipo de informação fica no endereço p. Quando o programa corre, o p apontará um ponto na memória sem saber o tipo de conteúdo deste endereço. Imagine a seguinta situação: O apontador p está a apontar um endereço. Se o conteúdo é um byte, o valor será diferente do que se o conteudo é um word. No exemplo abaixo, p^ aponta um byte com valor 129 (binário: 10000001), se o mesmo p^ apontarava um valor de tipo word o valor seria 25473 (binary: 0110001110000001), ou se p^ apontarava um longint (4 bytes, 32 bits) o valor seria 743924609 (binário: 00101100010101110110001110000001). (Nota que nos computadores baseados nos precessadores Intel os números são armazenado com o bit menos significativo ao primeiro [LSB=least significant bit]).

Por isso temos de especificar o tipo de informação o apontador aponta. Para especifiacr isso temos de regressar à declaração do apontador. Em vez de só dizer que p é um apontador temos de especificar também o tipo:


Declaração de um apontador

A declaração de um apontador fazemos assim:
 
 Var p: pointer; 
 Var p: ^type;

A primeira forma declara um apontador geral, sem especificar o tipo de informação no endereço p.
A segunda forma também especifica o tipo da informação no endereço p. O tipo (type) pode ser qualquer tipo que nós sabemos: tipos simples (integer, real, etc), tipos complicados (record, array), e mesmo combinações de tipos (arrays de records, records de arrays).
Exemplos:
  Var byteptr:  ^byte;
      wordPtr:  ^word;
      real6arrayptr: ^array[1..6] of real;

Agora vamos verificar o nosso saber. Vamos declarar um apontador que aponta um tipo word e atribuimos o endereço de um word ao apontador:

  Var wordptr: ^word;
      w: word;

  begin
     (* atribuir um valor ao word w: *)
    w := 25473;
     (* atribuir o endereço ao apontador: *)
    wordptr := Addr(w);
     (* Mostrar o conteudo do endereco: *)
    WriteLn(wordptr^);
  end.

output:
  25473

Agora vamos complicar coisas. Vamos declarar um apontador do tipo 'apontar um byte', e atribuimos o endereço de um word. Vamos ver o que acontecerá:

  Var byteptr: ^byte;
      w: word;

  begin
     (* assign a value to the word *)
    w := 25473;
     (* let a 'pointer to word' point to our word *)
    byteptr := Addr(w);
     (* show the contents of the memory wordptr points to *)
    WriteLn(byteptr^);
  end.

output:
 129

Este exemplo mostra que temos de haver cuidade com os apontadores. O valor de conteúdo de endereço p depende do tipo de p!


Pourquê?


Porquê usar apontadores? As coisas são mais complicados, mas há várias razões para usar apontadores. As mais importantes são
 
  • Rapidez
  • Flexibilidade

Rapidez: Imagine que quer escrever um procedimento com um parâmetro um array. Cada vez o programa chama este procedure, o array será copiado. Se, em vez disso, chamamos o procedimento com o endereço do array o programa correrá muito mais rápido. Isto implica só copiar uma única pequena variável (um apotador ocupa só 4 bytes [Intel computadores]). Análise os programas a seguir. O programa esquerde usa um array, o programa direito usa um apontador para chamar o procedure.
 

PROGRAM TestSpeed;

Type ra = array[1..1000] of real; 
Var r: ra;
    i: longint;

PROCEDURE SlowProc(a: ra);
begin
  a[1] := 1.0;
end;

begin
  for i := 1 to 4000000 do
    SlowProc(r)
end.

PROGRAM TestSpeed;

(* define an array and a pointer to
  that arrray: *)
Type ra = array[1..1000] of real;
     rap = ^ra;
Var r: ra;
    i: longint;

PROCEDURE FastProc(a: rap);
begin
  (* a is a pointer to an array *)
  (* a^ is what it points to, the *)
  (* the array. a^[1] is the first *)
  (* element of that array *)
  a^[1] := 1.0;
end;

begin
  for i := 1 to 4000000 do
    FastProc(@r)
   (* pass only a pointer to *)
   (* the procedure *) 
end.


tempo de execução (Pentium II 450 MHz,
TURBO PASCAL 6): 115 s.


tempo de execução (Pentium II 450 MHz,
Turbo PASCAL 6): 1 s.

(De facto o programa de lado direito é igual a um programa que usa a técnica de passing by reference (veja aula 15). PROCEDURE SlowProc(Var a: ra);  também seria rápido. Passing by reference significa o apontador é dado ao procedimento)

Flexibilidade: Se, no começo do programa não sabemos quantas variáveis preciçamos, temos de declarar o máximo possível para garantir de ser capaz de correr o programa. Se nós queremos escrever um programa para calcular os primeiros N números primos, com N dado pelo utilizador, temos de declarar um array do máximo tamanho. Assim:
  Var prime: array[1..10000000] of longint;
Este programa vai ocupar a memória inteira do computador, mesmo quando vamos só calcular 10 números. Não haverá mais lugar para os outros programas ou outras variáveis no mesmo programa. Mais bonito seria usar um array de tamnho flexível para declarar as variáveis dinamicamente; especificar as variáveis precisas, nada mais e nada menos. Com apontadores isto é possível.

Os apontadores e a idea de criação dinâmica é a base de object-oriented programming (programação orientada pelos objectos), o que é a forma de programação moderna. Este forma de programar fica fora do âmbito desta cadeira.


NIL

Um apontador que não aponta nada tem o valor NIL. NIL é uma contante predefinida em PASCAL.


 

Quick Test

Para testar o seu conhecimento, sobre o que aprendeu nesta aula, clique aqui para um teste on-line.

Peter Stallinga. Universidade do Algarve, 26 Abril 2002