Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores PASCAL Apontadores Definição: Um apontador ou ponteiro é uma variável que armazena o endereço de outra variável. Sua utilização é vasta em programas por dois motivos: 1. Algumas computações só são possíveis com a utilização de ponteiros 2. A utilização de ponteiros em geral simplifica a programação e aumenta a eficiência do código executável, tornando-o mais rápido. Esses motivos podem não estar muito claros agora, mas na medida em que o conteúdo for avançando perceberemos com mais clareza a importância do uso de ponteiros. Apontadores e Endereços Vejamos alguns exemplos para ilustrar os conceitos. Seja x uma variável inteira e px uma variável ponteiro. Em PASCAL, o operador addr fornece o endereço de uma variável. Assim a atribuição abaixo é válida: px := addr(x); Suponha a seguinte seqüência de código: x := 7; px := addr(x); A figura abaixo mostra esquematicamente a situação em uma memória hipotética de 256 posições endereçadas de 0 (0000) a 255 (FFFF). Suponha que a variável x tenha sido alocada no endereço 2 da memória e px na posição 5 da memória. Assim, após a execução das linhas acima teremos a seguinte situação: Variável Endereço Dado 0000 X 000F 7 F000 00FF px FF00 000F FFFF O operador ^ serve para acessar o valor da área de memória (variável) apontada pelo ponteiro. Por exemplo, a atribuição: px := addr(x); px^ := 6; 1 Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores coloca o valor 6 na variável apontada por px (coloca o valor 6 na área de memória cujo endereço está armazenado em px), ou seja em x. Isto quer dizer que fazer x = 6 ao invés de px^ = 6 surtiria o mesmo efeito. Seja y uma variável inteira. Então a seqüência: x := 20; px := addr(x); y := px^; Surte o mesmo efeito de x := 20; y := x; Quando temos uma variável do tipo apontador (px) que guarda o endereço de uma outra variável (x), dizemos que px aponta para x. Declaração de Apontadores Assim como qualquer outra variável, um apontador necessita ser declarado: x, y : integer; px : ^integer; Como sabemos, x, y : integer; declaram x e y como duas variáveis inteiras. Então x, por exemplo, representará um espaço de memória para armazenar um inteiro. A declaração px : ^integer; é a declaração do ponteiro (da variável) px. Esta declaração significa que px é uma variável que guardará um endereço de um espaço de memória e esta memória, por sua vez, é para armazenamento de números inteiros. De maneira mais compacta, o que se diz nesta declaração é que px é um ponteiro para um inteiro. Podemos ter ponteiros para qualquer tipo válido. O trecho: P : ^real; está declarando a variável p, como sendo ponteiro para real, ou seja, como uma variável que guardará um endereço de um espaço de memória que armazena um valor do tipo real. Perceba que o ^ na declaração só serve para indicar que a variável é um ponteiro (guarda um endereço de memória). O uso do operador ^ em uma variável ponteiro, fora de uma declaração, tem o significado dado anteriormente de obter o conteúdo do espaço de memória apontado. Por exemplo, veja o código abaixo: program ponteiro1; var x, y : integer; px : ^integer; { aqui o ‘^’ soh serve para indicar que px eh um ponteiro } begin x := 20; px := addr(x); y := px^ + 10; { aqui o ‘^’ serve para pegar o valor apontado por px end. Neste caso o que se faz é que y receba o conteúdo de x somado com 10. 2 Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores Não podemos esquecer que apontadores são variáveis. Assim sendo serão também válidas expressões envolvendo variáveis apontadores. Por exemplo, se py é uma variável do tipo ponteiro, então a expressão: py := px; copia o conteúdo de px (não o conteúdo do endereço apontado por px) para dentro de py fazendo com que py passe a apontar para o mesmo local para onde px aponta. Por exemplo, na seqüência abaixo, o conteúdo de x é atribuído indiretamente a y através dos ponteiros: program ponteiro2; var x, y : integer; px, px2 : ^integer; begin x := 10; px := addr(x); px2 := px; y := px2^; end. Vejamos esquematicamente na memória. Suponha que o endereço de memória de cada variável é o número que está entre parênteses. x := 10; px := addr(x); px2 := px; y := px2^; 3 Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores Exercícios 1) Escreva um programa que leia um número do teclado, guarde este número em uma variável. Depois faça um ponteiro apontar para a variável que guardou este número e imprima-o na tela, acessando o valor através do ponteiro. 2) Analise as seguintes seqüências de código. Verifique se estão corretas ou incorretas. Caso estejam incorretas, apontem os prováveis erros. Para tanto, considere a declaração das variáveis abaixo: var a, b : integer; c, d : ^integer; a) c := a; erro b) c := addr(b); d := c; d^ := 10; c) c := a^; erro d := 10 + c^; erro i) b := 1; c := b^; erro c^ := a + c^; j) a := 1; c := addr(a); for b:=1 to 10 do c^ := c^ + 1; d) d := c; c := addr(a); a := 10; b := d^; write(b); k) c := addr(b); b := 10; write(c^); e) c := addr(a); readln(c); erro d := c; b := d^; m) a := 100; c := addr(a); while (a>0) do begin c^ := c^ - 1; write(a); end; f) b := 10; c := addr(b); write(c); erro g) c^ := 20; h) b := 2; c := addr(b); c^ := c^ + 2; l) read(c^); n) c^ := 10; d := c; d^ := d^ + 10; write(d^); o) d := addr(c); erro 4 Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores Qual o valor inicial de um apontador? Observe o seguinte programa: program ponteiro3; var x : integer; px : ^integer; begin px^ := 10; x := px^; end. O que há de errado neste programa? Basta construir o esquema na memória e observar: px^ := 10; Logo na primeira instrução percebemos que não há coerência na atribuição. Isso porque: se px não aponta para nenhuma área de memória válida, como poderemos atribuir um conteúdo para dentro desta área? Devemos ter em mente que inicialmente em um apontador não há endereço válido algum, mas sim um valor lixo, ou seja, este ponteiro aponta para uma posição desconhecida da memória. Isso significa que se usarmos um ponteiro sem a sua prévia inicialização tornamos a manipulação desse ponteiro uma provável fonte de erros graves no programa. Situações como essas podem facilmente derrubar o programa ou mesmo o sistema operacional. Assim, para manipularmos apontadores, estes devem apontar sempre para um endereço de memória previamente alocada pelo programa (através da declaração de variáveis ou dinamicamente como veremos mais adiante). Ponteiros para Registros Como já citado anteriormente, quando se cria um registro em PASCAL, um novo tipo é criado. Já que podem haver ponteiros para qualquer tipo válido, ponteiros para um tipo definido em um registro é perfeitamente possível. Abaixo é dado um exemplo. Program Ponteiro4; Type Ponto = record x : real; y : real; end; var p1 : Ponto; ps : ^Ponto; { ps é um ponteiro para Ponto } begin ps := addr(p1); { faco ps apontar para p1 } end. 5 Unidade de Ensino Descentralizada de Colatina Tecnólogo em Redes de Computadores Neste exemplo, foram declaradas as variáveis p1 e ps. A variável p1 foi declarada como sendo do tipo Ponto e ps como sendo um ponteiro para Ponto. Em seguida fez-se ps apontar para p1. Acesso aos Campos de um Registro através de um Ponteiro Seguindo ainda o exemplo anterior, se quiséssemos preencher os campos x e y de p1 com os valores 5 e 8, por exemplo, poderíamos fazer de maneira direta, como já conhecemos: p1.x := 5; p1.y := 8; Mas poderíamos também fazê-lo através do ponteiro ps, que aponta para p1, da seguinte forma: ps^.x := 5; ps^.y := 8; Percebe-se então que o uso do operador ^ é da mesma forma que já vimos, de modo que se pegue o elemento apontado. A diferença é que o elemento apontado é do tipo de um registro criado e para acessar um campo de uma variável registro, utilizamos, como já sabido, o ‘.’ seguido do nome do campo. Vetores de Ponteiros Se podemos ter vetores de qualquer tipo válido, incluindo os tipos criados com registros, é totalmente possível declarar um vetor de ponteiros. Isto significa que teríamos um vetor de endereços de memória e, portanto, cada posição do vetor apontaria para uma determinada área de memória. Supondo um vetor de ponteiros para real, poderíamos ter o seguinte programa: Program Ponteiro5; Type Ponteiro = ^real; Var v : array [1..3] of Ponteiro; { Vetor de ponteiros para real } x, y, z, soma : real; begin x := 3.5; y := 5.6; z := 8.0, v[0] := addr(x); v[1] := addr(y); v[2] := addr(z); soma := v[0]^ + v[1]^ + v[2]^; write(soma); end. Este programa imprimirá 17.1, que é a soma de x, y e z. 6