Lecture 20: Apontadores |
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;
@x ou Addr(x) retorna o endereço do objecto x |
|
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: |
Var p: pointer; |
|
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!
Porquê usar apontadores? As coisas são
mais complicados, mas há várias razões para usar apontadores.
As mais importantes são
|
|
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;
PROCEDURE SlowProc(a: ra);
begin
|
PROGRAM TestSpeed;
(* define an array and a pointer to
PROCEDURE FastProc(a: rap);
begin
|
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.