É estranho pensar que até poucos meses atrás eu não fazia idéia do que eram ponteiros ou de como usá-los. Talvez por eu nunca ter precisado realmente usar ponteiros, mas isso mudou quando eu comecei a estudar seriamente desenvolvimento de jogos em pascal. Eu queria alternativas rápidas para efeitos em bitmaps, pois até então eu acessava diretamente os pixels e isso era um processo muito lento.
Foi então que alguém me perguntou: “Por que você não usa ScanLine?”.
Naquela época eu não sabia o que eram ponteiros, então eu procurei na internet explicações sobre a tal propriedade e descobri que ela não era nada mais do que o endereço na memória de uma array de bytes.
Sim, ponteiros são variáveis que guardam endereços da memória. Eles podem apontar para posição na memória (e conseqüentemente qualquer tipo de dados), eles podem ou não ser tipados para indicar o tipo do dado para o qual estão apontando.
Exemplo:
var
X, Y: Integer; //X e Y são inteiros
P: ^Integer; //P aponta para um inteiro
begin
X:= 17; //coloca um valor em X
P:= @X; //coloca o endereço de X em P
Y:= P^; //coloca o valor no endereço P em Y
end;
Vamos agora às explicações. Primeiro declaramos duas variáveis inteiras (X e Y), como vocês já devem estar acostumados a fazer. Logo depois nós declaramos outra variável, P, que NÃO é um inteiro.
Não é um inteiro? E por que aquele integer ali?
Lembra que eu falei de ponteiros tipados antes? É o que fizemos aqui. Para declarar ponteiros gerais nós usamos o tipo pointer, mas para declarar um ponteiro para um tipo específico nós usamos o símbolo ^ (circunflexo) seguido do tipo (no caso um inteiro).
Neste exemplo P pode apontar tanto para X quanto para Y.
Seguindo um pouco mais nós colocamos um valor em X, algo muito simples não? Logo depois nós colocamos o endereço de X em P.
E por quê você não colocou P:= X?
Acontece que ponteiros não guardam valores, apenas locais da memória. Aquele @ indica que ao invés do valor de X, nós queremos o endereço dele. O @ funciona também com métodos, funções e classes. Mas não funciona com interfaces, pois o endereço não é conhecido em tempo de compilação e não pode ser extraído em tempo de execução.
Por fim nós colocamos o valor que está no endereço armazenado em P na variável Y.
Mas aquele “^” não era para especificar o tipo de ponteiro?
Também, mas o ^ tem duas funções. Quando ele vem antes de um identificador de tipo (^tipo) significa que aquele ponteiro aponta para variáveis do tipo especificado. Mas quando ele aparece depois de uma variável de ponteiro (ponteiro^) significa que ao invés do endereço da memória, deve ser retornado o valor naquele endereço.
Depois disso tudo você me pergunta: “Mas pra quê eu vou escrever tudo isso se eu posso fazer simplesmente Y:= X? Não é bem mais fácil?”
Realmente, mas os ponteiros são muito úteis por várias razões. Primeiro porque entendendo ponteiros você poderá entender melhor a linguagem do Delphi (Object Pascal), já que os ponteiros normalmente estão implícitos no código. Qualquer dado que precise alocar blocos largos de memória dinamicamente irá usar ponteiros. Strings são um bom exemplo de ponteiros que atuam nos bastidores. Enquanto você usa texto:= “ABC…”, o Delphi aloca o espaço necessário para a variável e armazena um ponteiro para o local onde o valor foi alocado. Quando você precisa usar o dado novamente o Delphi procura por aquele local na memória e retorna o valor armazenado. É por isso que as strings em Delphi são tão poderosas.
Além disso, técnicas de programação avançadas irão exigir o conhecimento de ponteiros. E finalmente, os ponteiros são, algumas vezes, a única forma de contornar a restrição de tipos do Delphi.
Exemplo:
type
PInteger = ^Integer;
var
R: Single;
I: Integer;
P: Pointer;
PI: PInteger;
begin
R:= 1.41;
P:= @R;
PI:= PInteger(P);
I:= PI^;
end;
Claro que números decimais e inteiros são armazenados de formas diferentes. Neste exemplo nós só copiamos os dados binários de R para I sem convertê-los.
Alem do operador @ existem vários outros para colocar um valor em um ponteiro. O New e o GetMem colocam um endereço da memória em um ponteiro existente, enquanto o Addr e o Ptr retornam um ponteiro para um endereço ou variável específicos.
Você pode usar ponteiros qualificados, como na expressão P1^.Data^
A palavra reservada nil é uma constante especial que pode ser usada por qualquer ponteiro. Quando um ponteiro é setado para nil ele não aponta para nenhum lugar na memória.