Вы находитесь на странице: 1из 23

Apontadores - Estruturas - Listas

APONTADORES

 Um apontador é uma variável que contém o endereço de outra variável.


Ou
Apontadores são dados que contêm endereços para outros dados

Exemplo:

Endereço Posição de memória


5000
5001 5102
5002
5003
5004
.
.
.
5102

Se considerarmos o esquema acima como uma série de posições de memória


iniciadas no endereço 5000, poderemos dizer que o conteúdo da posição de
memória 5001 aponta para a posição de memória 5102; logo, a variável que
corresponde aquela posição de memória é uma variável do tipo apontador.

Os apontadores são tipos de dados fundamentais em C:

• são os apontadores que suportam as operações com memória dinâmica

Um apontador pode ser um apontador para caracter, um apontador para inteiro, um


apontador para cadeias de caracteres ou até um apontador para uma estrutura de
dados.

Prof. Armindo Macedo 1


Apontadores - Estruturas - Listas

Declaração de apontadores

Uma variável ao ser declarada como apontadora deve conter antes o operador *.

Forma geral de declaração de uma variável tipo apontador:

tipo *nome_variável;

Exemplo:

int *ender, i, j;


ender = &i;
j = *ender;

A variável ender foi declarada como apontadora para dados tipo inteiro.

No comando ender = &I, a variável ender, vai receber o endereço em memória da


variável I, diz-se então que ender “aponta” para i.

Por outro lado, a variável j vai receber o conteúdo da posição de memória apontada
por ender, isto é, o mesmo valor que a variável i.

Dizemos que uma variável aponta para outra variável quando a primeira contém o
endereço da segunda.

Um apontador para cadeias de caracteres pode ser inicializado da seguinte forma:

char *p = “Olá Mundo”

Esta operação equivale a dizer que o apontador p vai ficar com o endereço do
primeiro caracter da cadeia de caracteres.

Prof. Armindo Macedo 2


Apontadores - Estruturas - Listas

Exemplo:

#include<stdio.h>
#include<string.h>

char *p = “Olá Mundo”;


void main();
{
int i;
printf(p);
for(i = strlen(p) – 1; i > -1; i--) printf(“%c”, p[i]);
}

O que faz ?
Convém realçar o seguinte:

- o apontador p é indexado pela variável índice, a variável i, como se de um


vector se tratasse.
- Por outro lado, o próprio nome de um vector ou matriz pode ser usado como
apontador, pois esse nome sem índice, equivale ao endereço da primeira
posição da estrutura que representa.

Por exemplo, se se definisse o vector:

Char p1[10]

então p1 ou &p1[0] seriam apontadores para o endereço do primeiro caracter da


cadeia de caracteres definida.

Exemplo:

int *apont, a[10];




apont = a;
apont[4] = 10;
(apont + 4) = 10;

As duas últimas linhas fazem exactamente a mesma coisa, isto é, colocam no


quinto elemento do vector o valor 10.

Prof. Armindo Macedo 3


Apontadores - Estruturas - Listas

Na penúltima linha, essa operaração é feita por indexação; e na última, a operação


é feita por adição do valor 4 ao nome da variável apont, que representa o endereço
base do vector.
Esta última operação de acesso por aritmética de apontadores é mais rápida que a
operação de acesso por indexação da estrutura.

Exemplos de operações aritméticas com apontadores:

- se p1 aponta para a posição 55002, então com a instrução:

p1++; (incremento)

p1 passa a apontar para 55004, se se tratar de um apontador para


inteiros.

- p1--; (decremento)

e agora, para onde aponta p1?

O incremento é feito pelo tamanho do tipo base que é apontado.

Prof. Armindo Macedo 4


Apontadores - Estruturas - Listas

Passar endereços para a função

Exemplo:

/* testa função que altera dois valores */

main( )
{
int x, y;
altera(&x, &y);
printf(“O primeiro é %d, o segundo é %d.”, x,y);
}

/* altera dois números da função que chama */

altera(px, py)

int *px, *py;


{
*px = 3;
*py = 5;
}

A saída será:

O primeiro é 3, o segundo é 5.

A instrução int *px, *py; declara que *px e *py são do tipo int e que px e py são
ponteiros, isto é, px e py contém endereços de variáveis do tipo int.

Prof. Armindo Macedo 5


Apontadores - Estruturas - Listas

/* testa função que soma constante a 2 valores */

main( )
{
int x = 4, y = 7;
adcon(&x, &y);
printf(“O primeiro é %d, o segundo é %d.”, x,y);
}

adcon(px, py)

int *px, *py;


{
*px = *px + 10;
*py = *py + 10;
}

A saída será:

O primeiro é 14, o segundo é 17.

Aqui os valores 4 e 7 são atribuídos às variáveis x e y no programa chamador e


este passa os endereços destas variáveis para a função adcon(), que adiciona a
constante 10 a elas.

As instruções:
*px = *px + 10;
*py = *py + 10;

Aqui o operador indirecto (*) é usado dos dois lados do sinal de igual. O símbolo
*px indica o conteúdo do endereço px. Pode-se usar *px onde se usaria x. A
primeira instrução toma o conteúdo do endereço px (isto é, 4) e soma 10 a ele,
armazenando o total no endereço px.

Prof. Armindo Macedo 6


Apontadores - Estruturas - Listas

Ponteiros sem funções

/* mostra o uso de ponteiros dentro do próprio programa */

main()
{
int x = 4, y = 7;
int *px, *py;

printf(“x é %d, y é %d.\n”, x, y);


px = &x; /* inicializa ponteiro atribuindo endereços*/
py = &y;
*px = *px + 10;
*py = *py + 10;
printf(“x é %d, y é %d.\n”, x, y);
}

Os ponteiros são sempre inicializados com o valor 0 ou NULL e C garante que


NULL não é um endereço válido, então antes de usá-los devemos atribuir algum
endereço válido a eles, o que é feito pelas instruções:

px = &x;
py = &y;

As instruções:

*px = *px + 10;


*py = *py + 10;

ou escritas como:

*px + = 10;
*py + = 10;

são equivalentes a:

x + = 10;
y + = 10;

O operador & pode ser aplicado somente a variáveis e elementos de matrizes;


construções tais como &(x + 1) e &3 são ilegais.

Prof. Armindo Macedo 7


Apontadores - Estruturas - Listas

Funções de apontadores

Neste item do programa, iremos tratar das funções que na linguagem C permitem
utilizar memória dinâmica.
Antes, porém, será melhor definir o que se entende por memória dinâmica.
As estruturas de dados até agora estudadas (vectores, matrizes e cadeias de
caracteres) podem ser consideradas estruturas estáticas em memória.

Isto significa exactamente que, ao serem definidas, o compilador reserva o espaço


total que a estrutura vai utilizar em memória. Por exemplo, ao definirmos um
vector numérico de 50 posições

int a[50];

estaríamos a reservar 100 bytes para o vector a.


Já sabemos que um inteiro ocupa 2 bytes em memória.

Estas estruturas empregam-se precisamente em situações nas quais à partida é


conhecido o número máximo de elementos que a estrutura vai comportar em toda a
execução do programa. Ora, existem situações de programação em que tal
conhecimento antecipado não é possível ou não é viável.

Nessas circunstâncias existem duas possibilidades:

- ou se usam estruturas estáticas, maximizando o número de


elementos que a estrutura poderá conter;
- ou se usam estruturas em memória dinâmica.

Neste tipo de estruturas não é necessário conhecer à partida o número máximo de


elementos que ele vai conter. Isto porque a reserva de espaços vai ser feita
dinamicamente, isto é, à medida do necessário no decurso do programa. Chama-se
a isto alocação dinâmica.
Claro que existe um limite para o número de elementos a serem criados em
memória dinâmica. Esse limite depende apenas da quantidade de memória que o
compilador utilizado disponibiliza para trabalhar em memória dinâmica.

Essa região de memória disponível para trabalhar com alocação dinâmica


chama-se HEAP.
Se considerarmos um mapa de memória principal, como o do esquema que
se segue, verificamos que o HEAP vai existir entre a área de programa e a área de
STACK (pilha - estrutura do tipo FIFO usada internamente, pelo compilador e
programas de utilizador, para armazenamento temporário de dados e endereços).

Prof. Armindo Macedo 8


Apontadores - Estruturas - Listas

Esta estrutura, que será estudada mais tarde, embora como estrutura
definida pelo utilizador, é utilizada automaticamente em operações de retorno de
funções e subrotinas, assim como no uso da recursividade.

Zona de memória Alta

Pilha

HEAP

Programa

S.O.

Zona de memória Baixa

A área disponível de HEAP não tem um valor conhecido à partida pois


cresce na medida em que a área do STACK o permite.
O limite é, portanto, que os apontadores de STACK e de HEAP não se
encontrem.

São duas as funções mais importantes de locação dinâmica em C:

FUNÇÃO SIGNIFICADO
malloc() executa a alocução dinâmica de memória
free() liberta a memória anteriormente alocada

Estas funções operam em conjunto e trabalham sobre uma lista de endereços


disponíveis para armazenamento dinâmico.
Os cabeçalhos de definição destas funções encontram-se no ficheiro "stdlib.h".

Forma geral de definição da função malloc()

void * malloc(size _t numero_de_bytes);

O tipo size_t é equivalente ao tipo inteiro sem sinal e o argumento


numero_de_bytes corresponde exactamente ao número de bytes que pretendemos
alocar.

Prof. Armindo Macedo 9


Apontadores - Estruturas - Listas

Esta função, ao ser invocada, devolve (retorna) um apontador para o primeiro byte
da memória dinâmica, portanto da região do HEAP reservada para o elemento
objecto da alocação.
Se não existir memória disponível no HEAP, ocorre uma falha de alocaçào e é
retornado o valor nulo.

Exemplo:

int *apont;
apont = malloc(50 *sizeof(int));

Neste exemplo efectuou-se uma alocação de espaço para 50 valores inteiros.


De notar a utilização da função sizeof() para assegurar que o valor correcto de
bytes era utilizado.

Não existindo memória disponível, o retorno da função seria um apontador nulo,


como já foi referido atrás. Logo, é necessário testar o valor retornado pela função
para verificar que a alocação foi bem sucedida.

O exemplo seguinte ilustra essa operação:

Exemplo:

if (!apont = malloc(50 *sizeof(int)))


{
printf("Não há memória disponível \n");
exit(1);

A função free() é o oposto da função malloc(), como também já se referiu.

O cabeçalho da definição da função é:

void free(void *p);

É necessário algum cuidado com o uso desta função, pois a sua utilização com o
argumento inválido pode destruir a lista de endereços livres em memória e
provocar um crash no programa. Dito de outra maneira "estoirar com tudo".

Este tipo de utilizações erradas não são passíveis de serem detectadas na fase de
compilação do programa. Só estando em execução é que o erro acontece, e nessa
altura é tarde de mais.

Prof. Armindo Macedo 10


Apontadores - Estruturas - Listas

Costumam ser erros de muito difícil detecção, que podem estragar todo um
trabalho de programação de semanas ou meses.

Exemplo:

# indude "stdlib.h"
# indude "stdio.h"
# indude "string.h"

void main(void)
{
char *st;
int i;
st = malloc(100);
if (!st)
{
printf ("não há memória disponível \n");
exit(1);
}
gets(st);
for (i = strlen(st) - 1; i >= 0; i--)
putchar(st[i]);
free(st);
}

Verificámos, neste último exemplo, a utilização de uma alocação de 100 bytes no


HEAP, para conter uma cadeia de caracteres. Logo de seguida, foi colocado o teste
para verificar se a função malloc() retornou um apontador nulo. Nesse caso, o
programa terminaria com uma mensagem de erro.

O resultado prático deste programa é:

- é lida uma cadeia de caracteres;


- os caracteres são escritos no monitor pela ordem inversa da que estavam.

O programa termina com o libertar da memória alocada, operação que deve ser
sempre executada.

Mais algumas funções de alocação dinâmica estão disponíveis em C. As duas que


se seguem, conjuntamente com as já apresentadas, encontram-se também definidas
no padrão ANSI e os seus cabeçalhos existem também no ficheiro "stdlib.h".

Prof. Armindo Macedo 11


Apontadores - Estruturas - Listas

As funções são:

• calloc()
• realloc()

Formato geral de definição da função calloc():

void *calloc(size_t num, size_t tamanho);

Esta função aloca uma quantidade de memória igual a num * tamanho, que é nem
mais nem menos que a memória suficiente para uma estrutura de num elementos e
o tamanho de cada um igual a tamanho.

Tal como malloc(), esta função retorna um apontador para o primeiro byte da
região alocada ou um apontador nulo, se não conseguir efectuar a operação de
alocação.

Exemplo:

# indude "stdlib.h"
# indude "stdio.h"
void main(void)
{
float *apont;
apont = calloc(100, sizeof(float));
if (!apont)
{
printf ("a alocação falhou. \n");
exit(1);
}
.
.
.
}

Neste último exemplo efectua-se uma alocação de um vector de 100 elementos do


tipo float.

Forma geral de definição da função realloc():

void *realloc(void *ptr, size_t tamanho);

Esta função modifica o tamanho da memória previamente alocada, apontada pelo


apontador ptr para o valor especificado em tamanho.

Prof. Armindo Macedo 12


Apontadores - Estruturas - Listas

Se o apontador ptr é um apontador nulo, esta função aloca tamanho bytes de


memória e retorna um apontador para essa área de memória, isto é, funciona como
a função malloc().

Se tamanho é zero, a memória apontada por ptr é libertada.

Se não existir memória suficiente para a realocação no HEAP, é retornado um


apontador nulo.

Exemplo:

# include "stdlib.h"
# include "stdio.h"
# include "string.h"

void main(void)
{
char *apont;
apont = malloc(24) ;
if (!apont)
{
printf("Erro de alocação. ~n");
exit(1);
}
strcpy(apont, "Temos aqui 24 caracteres. \n");
apont = realloc(apont, 30);
if (!apont)
{
printf("Erro de realocação.~n");
exit(1);
}
strcat(apont, ".");
printf(apont);
free(apont);
}

Observando atentamente o exemplo atrás escrito, notamos que existe uma primeira
alocação de 24 caracteres. Posteriormente existe uma tentativa de realocação de
uma cadeia de comprimento de 30 caracteres. No final é concatenado o caracter "."
com a cadeia apontada por apont.

Termina o programa com a libertação da memória anteriormente alocada.

Prof. Armindo Macedo 13


Apontadores - Estruturas - Listas

Estruturas
Definição

Uma estrutura é um tipo de dado composto, que consiste em uma ou mais variáveis agrupadas
sob um nome. As variáveis na estrutura podem ter tipos diferentes, mas normalmente existe uma
relação entre eles que faz com que seja conveniente tratá-los conjuntamente como um objecto no
programa. Em Pascal a noção análoga é um tipo de dado, definido pelo utilizador, chamado
record. Os componentes individuais de uma estrutura são chamados membros. Uma declaração
de estrutura tem a forma

struct nome_estrut {
declarações_dos_membros
};

Isto tem o efeito de definição de um novo tipo de dado. Aqui nome_estrut é um nome arbitrário e
struct nome_estrut pode ser pensado como o nome de um novo tipo de dado. As
declarações_dos_membros são como quaisquer outras declarações, excepto que não definem
variáveis mas nomes de componentes de uma instância da estrutura nome_estrut.
Alguns exemplos:

struct num_complexo {
double real;
double imaginário;
}

struct data {
int ano;
char mês[10];
int dia;
};

struct venda {
char comprador[SIZE];
double quantidade;
struct data quando;
};

Neste último caso existe uma estrutura imbricada (encaixada) noutra. Estas declarações não
definiram quaisquer variáveis, elas meramente definiram novos tipos de dados e portanto, não
lhes foi reservado espaço em memória. Qualquer definição de variáveis do tipo destas anteriores
utiliza as mesmas regras que para qualquer outro tipo de dado. Como exemplo:

struct data d; /* d é uma variável do tipo struct data */


struct venda a[20]; /* a é um array de 20 estruturas */
struct num_complexo x, y, z;

Prof. Armindo Macedo 14


Apontadores - Estruturas - Listas

nestes casos são definidas variáveis e consequentemente é-lhes reservado espaço em memória.
Os membros das estruturas são acedidos da seguinte forma:

variável_estrutura.membro

Exemplos:

struct data d;
d.ano = 1993;
strcpy(d.mês,”Fevereiro”);
d.dia = 4;

struct venda s;
s.quantidade = 24;
s.quando.ano =1994;

Repare-se que o membro da estrutura imbricada foi acedido da mesma forma. Outros exemplos
são:

struct complexo x, y;
x.real += y.real;
x.imaginario += y.imaginario;

As variáveis do tipo estrutura podem ser inicializadas na altura da sua definição, colocando-se a
lista de inicializações a seguir à definição. Por exemplo:

struct complexo z = {1, -1};

Operações com Estruturas

Algumas operações fundamentais que podem ser aplicadas às estruturas são:

- Atribuição de variáveis de estrutura, por exemplo,

struct data d1, d2;


d1 = d2;

- Passar uma estrutura para uma função, por exemplo,

struct data d;
imprime_data(d);

- Retorno de uma estrutura por uma função, por exemplo,

struct data d, aniversario();


d = aniversario(“PORTUGAL”);

em que aniversario é uma função que retorna uma estrutura struct data.

Prof. Armindo Macedo 15


Apontadores - Estruturas - Listas

Exemplo: A função soma_complexos, adiciona dois números complexos e retorna o resultado.

struct complexo soma_complexos(struct complexo n1, struct complexo n2)


{
struct complexo soma;
soma.real = n1.real + n2.real;
soma.imaginario = n1.imaginario + n2.imaginario;
return soma;
}
Apesar de não existir qualquer conflito entre membros e variáveis com o mesmo nome, é
necessário ter o maior cuidado com esta prática, de forma a que não prejudique a clareza dos
programas.

Vectores de Estruturas

As estruturas, como qualquer outro tipo de dado podem ser agrupadas em vectores. Voltando ao
exemplo dos alunos pode-se agora definir um vector de alunos:

struct ALUNO {
long int numero;
float media;
} alunos[3000];

Exemplo 1: Neste exemplo é criado um novo tipo de dado struct data e definida uma variável
feriado do tipo array de 5 estruturas struct data.

struct data { int dia, char mês[10], int ano };


struct data feriado[5] = {{ 1, “Janeiro”, 1993},
{ 10, “Junho”, 1993},
{ 25, “Dezembro”, 1993}};

Para simplificar as declarações muito extensas, é conveniente atribuir nomes aos tipos de
estruturas. Por exemplo:

typedef struct data { int dia, char mês[10], int ano } DATA;

agora DATA representa o tipo de dado struct data, passando a poder ser usado em lugar deste.

Prof. Armindo Macedo 16


Apontadores - Estruturas - Listas

Exemplo 2: É agora apresentada uma função que lê datas para um array de estruturas passado
como argumento.

void le_data(DATA vdata[])


{
char temp[10];
int i=0;

while (1)
{ /* condição sempre verdadeira */
printf (“/nDia (0 para terminar):”);
gets(temp);
vdata[i].dia=atoi(temp); /* converte para inteiro */
if (!vdata[i].dia) /* se vdata[i] igual a zero quebra */
break; /* o ciclo com break. */
printf(“\nMes:”);
gets(vdata[i].mês);
printf(“\nAno:”);
gets(temp);
vdata[i].ano=atoi(temp);
i++;
}
}

Prof. Armindo Macedo 17


Apontadores - Estruturas - Listas

Listas lineares simplesmente encadeadas

Uma lista linear é um conjunto de elementos ordenados, de um determinado tipo de dados, e que
podem ser de número variável.

A representação de listas lineares pode ser feita utilizando um vector.


Nesse caso, os elementos situar-se-ão de uma forma adjacente em endereços contíguos em
memória.
Para tal era necessário definir um vector de dimensão apropriada, para conter todos os elementos
possíveis da lista.

No entanto, a forma mais corrente de utilização de listas é denominada de listas encadeadas.


Listas encadeadas são normalmente implementadas à custa de apontadores para dados simples
ou blocos de dados, em memória dinâmica.

Por exemplo, se quisermos criar uma lista de valores numéricos, a lista poderia ser representada
como o esquema seguinte:

5 4 1 7 18

Inicio Fim

Lista de valores numéricos criados com um vector

ou, utilizando elementos criados dinamicamente na HEAP:

Início

5 4 1 7 18 \

Lista simplesmente encadeada de valores inteiros.

Uma lista encadeada requer que cada item de dados contenha um apontador (elo de ligação) para
o próximo endereço da lista.

O fim da lista é marcado por um apontador nulo. Não existe, portanto, posicionamento
fisicamente sequencial em memória dos elementos da lista.

Eles podem situar-se em posições de memória muito diferentes dentro do HEAP. São os
apontadores que mantêm a ligação entre os seus elementos.

Uma definição central às listas e às estruturas de memória dinâmica é a de nós da lista. Nós são
os blocos que constituem a lista.

Prof. Armindo Macedo 18


Apontadores - Estruturas - Listas

Os nós são constituídos por dois ou mais elementos. O último elemento é, naturalmente, um
apontador para o nó seguinte.

Consideremos um exemplo de uma lista de endereços.

Um registo é um conjunto de campos identificadores de uma pessoa, um evento ou um objecto


que pretendemos descrever através dos dados que o definem.

O código em C para a definição da estrutura para conter os endereços será como se segue:

struct endereco {
char nome[40] ;
char morada[40] ;
char localidade[20] ;
char codigo_p[4] ;
struct endereco *proximo;
} Dados_endereco;

Importa talvez analisar com um pouco mais de cuidado a definição desta estrutura, uma vez que
vai ser utilizada várias vezes em diferentes exemplos daqui para a frente.

É importante notar que o último item de dados é um apontador para um bloco do mesmo tipo,
como se impunha para se poder implementar a ligação para o bloco seguinte, como acontece
com as listas lineares.

As operações que correntemente é necessário definir em termos de programação para se poder


operar com listas são:

• Inserir elementos
• Eliminar elementos
• Localizar elementos
• Exibir todos os elementos

No que respeita à inserção de elementos na lista, convém notar que essa inserção deve ser feita
de forma ordenada, pois disso tiramos vantagens quando da pesquisa ou da localização de
elementos.

Considerando o problema da inserção ordenada numa lista de endereços, três situações podem
ocorrer:

1 - O novo elemento a inserir é menor que o primeiro da lista; logo, tornar-se-á o


primeiro.

2 - O elemento é inserido entre dois elementos já existentes na 1ista.

3 - O elemento novo é maior que todos os elementos da lista; logo, é adicionado no fim.

Prof. Armindo Macedo 19


Apontadores - Estruturas - Listas

O exemplo que se segue é o de uma função C para efectuar a inserção ordenada numa lista de
endereços.

Exemplo:

/* **inicio e **fim são apontadores para apontadores */

void insere_elemento (struct endereco *novo, struct endereco **inicio, struct endereco **fim)

{
struct endereco *anterior, *p;
p = *inicio;
if (!*fim)
{
novo  proximo = NULL;
*fim = novo;
*inicio = novo;
return
}

anterior = NULL;
while (p)
{
if (strcmp(p  nome, novo  nome) < 0)
{
anterior = p;
p = p  proximo;
}

else
{
if (anterior)
{ /* coloca elemento no meio de dois já existentes */
anterior  proximo = novo;
novo  proximo = p;
return;
}
novo  proximo = p; /* coloca o elemento no inicio */
*inicio = novo;
return;
}
}
(*fim)  proximo = novo; /* coloca o elemento no fim */
novo  proximo = NULL;
*fim = novo;
}

Em primeiro lugar, para a função insere_elemento, são passados três argumentos, a saber:

- o apontador para o elemento novo;


- o apontador para o início da lista;
- o apontador para o fim da lista.

Prof. Armindo Macedo 20


Apontadores - Estruturas - Listas

Dentro da função, o apontador p é utilizado como apontador de trabalho que se desloca na lista à
procura de um elemento maior que o elemento que se quer inserir.

O apontador anterior é inicializado a NULL e é deslocado exactamente atrás do apontador p.

As três diferentes situações de inserção referidas antes desta função estão assinaladas com
comentários no próprio programa.

Esquematicamente teríamos:

SITUAÇÃO 1 - Elemento novo inserido no inicio da Lista

Início

2 4

Início

novo
1

SITUAÇÃO 2 - Elemento novo inserido no meio

22 40

anterior

10

15 novo

SITUAÇÃO 3 - Elemento novo adicionado no fim

37 40 \

45 novo

Prof. Armindo Macedo 21


Apontadores - Estruturas - Listas

Verificamos que, com simples operações de mudanças de endereços, podemos facilmente


estabelecer uma ordem entre os elementos de uma lista criados em memória dinâmica.

Uma outra função que é possível definir, e que corresponde a uma operação frequentemente
executada, é a listagem de todos os elementos presentes na lista.

Exemplo:

void listagem(struct endereco *p)


{
while (p)
{
printf (p  nome);
p = p  proximo;
}
}

Para que esta última função funcione correctamente é necessário que o apontador a passar em
argumento seja o apontador inicial da lista.

O apontador p, que inicialmente assume o valor do apontador para o início da lista, vai
sucessivamente passando a apontar para o próximo elemento em cada iteração.

A pesquisa ou localização de um elemento é uma função muito parecida com a que se acaba de
representar.

Exemplo:

struct endereco *pesquisa(struct endereco *p, char *n)


{
while (p)
{
if (!strcmp(n, p  nome))
return p;
p = p  proximo;
}
return NULL;
}

Esta função retorna o apontador NULL, caso não tenha encontrado o elemento, ou o apontador
do elemento, se ele existir na lista.

De notar que o apontador para a cadeia de caracteres que corresponde ao nome que se pretende
pesquisar é passado em argumento juntamente com o apontador de início da lista.

A função strcmp() permite a comparação entre o campo nome do nó correntemente apontado e o


nome a pesquisar.

Chama-se à atenção que existem dois pontos de saída da função, pontos do programa associados
com a instrução return.

Prof. Armindo Macedo 22


Apontadores - Estruturas - Listas

A última função, também necessária para operar com a lista, é a de eliminação de um elemento
da lista.

É importante notar, no entanto, que a eliminação de um elemento, após a sua identificação na


lista, resume-se a uma simples mudança de um apontador, seguido da libertação de memória
alocada ao elemento a eliminar.

É necessário, porém, considerar as mesmas três situações que foram consideradas para a inserção
de um elemento na lista, ou seja, o elemento a eliminar:

- é o primeiro da lista;
- encontra-se no meio da lista;
- é o do fim da lista.

Listas lineares duplamente encadeadas

Para além das listas simplesmente encadeadas, existem as listas duplamente encadeadas, nas
quais os apontadores de ligação estabelecem ligação nos dois sentidos.

Esquematicamente:

Inicio Fim

\ \

Estas estruturas assim definidas permitem, portanto, que a lista possa ser percorrida nos dois
sentidos, do início para o fim e do fim para o início.

Os algoritmos de operação com uma lista encadeada são em tudo parecidos com os das listas
simplesmente encadeadas.

O que se torna necessário é ter em linha de conta mais um apontador.

Prof. Armindo Macedo 23

Вам также может понравиться