FIC/ Cu rs o d e B S I e LIC 2 0 10 – 2 º
P ro fe s s o r: Ro d rigo N e ve s Figu e ire d o d o s S a n to s
Au la 0 6 d e Arq u ite tu ra d e Co m p . – P o n te iro s e Lis ta s
Conceitos de Ponteiro
As linguagens de programação modernas tornaram possível explicitar não apenas o acesso aos dados, mas também aos endereços desses dados. Isso significa que ficou possível a utilização de ponteiros explicitamente. Assim, existe uma distinção de notação entre um dado e a sua referência (ou endereço). Um ponteiro armazena o endereço de memória onde um tipo está armazenado.
A notação é
Tipo
Dado = Alguma Estrutura;
TPPonteiro = ^Dado (ou *Dado);
Esta declaração expressa que valores do tipo TPPonteiro são ponteiros para dados do tipo Dado. Ele armazena um endereço de memória. Esse endereço deve ser a posição de início do armazenamento de um dado do tipo Dado.
Portanto, lemos o simbolo ^ ou * como sendo ponteiro para... , e na declaração acima lemos 'TPPonteiro é um ponteiro para variáveis do tipo Dado'.
Valores para ponteiros são gerados quando dados correspondentes a seus tipos são alocados
ou desalocados dinamicamente. Portanto, deixa-se a cargo do programa (via linguagem de
programação), e não do programador, prover e devolver espaço para inserções e eliminações
em tempo de execução.
• Custo: Tempo de execução comprometido.
• Idéia: O programador declara apenas o tipo de registro que contém um elemento da lista, e avisa que a alocação será dinâmica. Sempre que requisitar um registro novo para a inserção, ou liberar um registro a ser eliminado, o programador lança mão de duas rotinas pré-definidas para este fim.
Registros com Ponteiros
Dizemos que uma variável do tipo TpPonteiro aponta para, ou é um ponteiro para um
registro do tipo TpDado. Ponteiro é o único tipo de pré-referencia na maioria da linguagens de programação.
Exemplo de declaração:
Tipo
TPDado = Registro Codigo : Inteiro;
Nome : Alfanumérico[30]; Salario : Real;
Fim;
TPPonteiro = ^TPDado; Variáveis
Neste caso, a variável P é um ponteiro para um registro do tipo TPDado. P armazena o endereço de memória onde o registro foi alocado. Se mandarmos apresentar o valor de P, ocorrerá um erro. Isto significa que não é possível apresentar o endereço do ponteiro. As linguagens de programação não permitem que se leia nem escreva variáveis do tipo ponteiro. Quem fornece o valor do ponteiro é a própria linguagem de programação.
Para criarmos uma um novo ponteiro, utilizamos o procedimento Novo();. Veja o Exemplo:
Tipo
TPDado = Registro Codigo : Inteiro;
Nome : Alfanumérico[30]; Salario : Real;
Fim;
TPPonteiro = ^TPDado; Variáveis
P : TPPonteiro; Início
Novo(P); {cria novo registro TPDado que será "apontado" por P} End.
No exemplo acima, a instrução Novo(P), cria (aloca espaço de forma automática) um novo
registro apontado pelo ponteiro P.
Como acessar dados armazenados em uma variável do tipo ponteiro?
Entendemos então que P é um ponteiro que não permite que seja apresentado nem lido o seu valor. Quem fornece o valor do ponteiro é a própria linguagem.
Assim, para acessarmos os dados armazenados, precisamos designar:
• ponteiro,
• objeto e
• campos.
Notação:
Ponteiro Objeto (Registro) Campos
P P^
Endereço nulo (terra):
Existe uma constante pré-definida para denotar o endereço nulo nil. Podemos utilizá-la para atribuições e testes, como nos exemplos abaixo:
P ← nil;
Se (P = nil) Então ...
Ponteiro x Objeto Apontado
Nos exemplos abaixo, é ilustrada a diferença entre as operações de atribuição entre ponteiros (por exemplo, p ← q ) e a atribuição entre o conteúdo dos registros apontados pelos ponteiros (isto é: p^ ← q^).
Tipo
TPDado = Registro Codigo : Inteiro;
Nome : Alfanumérico[30]; Salario : Real;
Fim;
TPPonteiro = ^TPDado; Variáveis
P, Q : TPPonteiro;
Dada a situação abaixo, chamada de (a):
Dada a situação (a), após a atribuição p ← q temos a representação abaixo.
Dada a situação (a), após a atribuição p^ ← q^ temos a representação abaixo (b), onde o conteúdo do ponteiro Q é atribuído ao ponteiro P. Assim, P e Q possuem o mesmo conteúdo, mas endereços diferentes.
Manipulação de Registros com ponteiros
1) Declaração de Variável
Tipo
TPDado = Registro Codigo : Inteiro;
Nome : Alfanumérico[30]; Salario : Real;
Fim;
TPPonteiro = ^TPDado; Variáveis
P : TPPonteiro;
2) Criação de um registro
novo(p);
a) efetivamente aloca uma variável do tipo TPDado;
b) gera um ponteiro do ^TPDado apontando para aquela variável; c) atribui o ponteiro à variável P.
A partir daí:
a) o ponteiro pode ser referenciado como P b) variável referenciada por p é denotada por P^
3) Atribuição de conteúdo ao registro:
p^.codigo ← 29; p^.nome ← “Joao”;
p^.salario ← 1000;
4) Liberação de um registro
devolver(P);
Lista Encadeada Dinâmica
Podemos estudar algumas estruturas de dados estáticas (lista, pilha e fila circular). Elas tem esta denominação porque os dados são armazenados em um vetor. Assim, temos de definir um tamanho máximo de registros que seriam armazenados, tornando assim, as estruturas limitadas. A partir de agora, os dados serão alocados dinamicamente na memória. Isso significa dizer que a medida, em que for necessária a inclusão de um novo dado, será criado um novo nó na memória, através de um ponteiro e este novo nó será associado a lista encadeada. Da mesma, quando da necessidade de se excluir um nó, a memória será liberada.
Em uma implementação de lista através de ponteiros, cada item (nó) da lista é encadeado com o seguinte através de uma variável do tipo ponteiro. Isso significa dizer que cada nó da lista, contém o registro de dados (informações) e uma variável que armazena o endereço do próximo nó. Este tipo de implementação permite utilizar posições não contíguas de memória, sendo possível inserir e retirar elementos sem haver a necessidade de deslocar os nós seguintes da lista.
A Figura 1 ilustra uma lista representada desta forma. Observe que existe um nó inicial denominado "Nó Cabeça". Apesar no nó cabeça não conter dados válidos é conveniente fazê-lo com a mesma estrutura que um outro nó qualquer para simplificar as operações sobre a lista.
A lista é constituída de "nós", onde cada nó contém um dado da lista (registro) e um ponteiro (prox) para o próximo elemento da lista. O registro do tipo TPLista contém um apontador para o nócabeça (prim) e um apontador para o último nó da lista (ult). O último nó da lista não tem próximo. O ponteiro próx do último nó possui valor NIL (nulo).
A implementação através de ponteiros permite inserir ou remover dados em qualquer posição da lista a um custo constante, aspecto importante quando a lista tem que ser mantida ordenada (no nosso caso a lista estará ordenada pelo campo chave primária). Em aplicações em que não existe previsão sobre o crescimento da lista é conveniente usar listas encadeadas por ponteiros (lista encadeada dinâmica), porque neste caso o tamanho máximo da lista não precisa ser definido. A maior desvantagem deste tipo de implementação é a utilização de memória extra para armazenar os apontadores.
Figura 1 - Representação de uma lista encadeada dinâmica.
Programa Estoque;
Tipo
TpDado = registro Codigo : inteiro;
Nome : alfanumérico[50]; Preco : Real;
QtdeEst : Real; QtdeMin : Real; Fim;
TpPonteiro=^TpNo; TpNo = registro Dado : TpDado; Prox : TpPonteiro; Fim;
Variáveis {Declaração das variáveis globais} Produto : TpLista;
Dado : TpDado; P : TpPonteiro; Prim : TpPonteiro; Ult : TpPonteiro; Op : Char;