Академический Документы
Профессиональный Документы
Культура Документы
APONTADORES
Exemplo:
Declaração de apontadores
Uma variável ao ser declarada como apontadora deve conter antes o operador *.
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.
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.
Esta operação equivale a dizer que o apontador p vai ficar com o endereço do
primeiro caracter da cadeia de caracteres.
Exemplo:
#include<stdio.h>
#include<string.h>
O que faz ?
Convém realçar o seguinte:
Char p1[10]
Exemplo:
p1++; (incremento)
- p1--; (decremento)
Exemplo:
main( )
{
int x, y;
altera(&x, &y);
printf(“O primeiro é %d, o segundo é %d.”, x,y);
}
altera(px, py)
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.
main( )
{
int x = 4, y = 7;
adcon(&x, &y);
printf(“O primeiro é %d, o segundo é %d.”, x,y);
}
adcon(px, py)
A saída será:
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.
main()
{
int x = 4, y = 7;
int *px, *py;
px = &x;
py = &y;
As instruções:
ou escritas como:
*px + = 10;
*py + = 10;
são equivalentes a:
x + = 10;
y + = 10;
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.
int a[50];
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.
Pilha
HEAP
Programa
S.O.
FUNÇÃO SIGNIFICADO
malloc() executa a alocução dinâmica de memória
free() liberta a memória anteriormente alocada
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));
Exemplo:
É 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.
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);
}
O programa termina com o libertar da memória alocada, operação que deve ser
sempre executada.
As funções são:
• calloc()
• realloc()
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);
}
.
.
.
}
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.
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:
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 data d;
imprime_data(d);
em que aniversario é uma função que retorna uma estrutura struct data.
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.
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.
Exemplo 2: É agora apresentada uma função que lê datas para um array de estruturas passado
como argumento.
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++;
}
}
Uma lista linear é um conjunto de elementos ordenados, de um determinado tipo de dados, e que
podem ser de número variável.
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
Início
5 4 1 7 18 \
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.
Os nós são constituídos por dois ou mais elementos. O último elemento é, naturalmente, um
apontador para o nó seguinte.
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.
• 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:
3 - O elemento novo é maior que todos os elementos da lista; logo, é adicionado no fim.
O exemplo que se segue é o de uma função C para efectuar a inserção ordenada numa lista de
endereços.
Exemplo:
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:
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.
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:
Início
2 4
Início
novo
1
22 40
anterior
10
15 novo
37 40 \
45 novo
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:
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:
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.
Chama-se à atenção que existem dois pontos de saída da função, pontos do programa associados
com a instrução return.
A última função, também necessária para operar com a lista, é a de eliminação de um elemento
da lista.
É 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.
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.