Professional Documents
Culture Documents
Apontadores
t d
Apontadores
Em C as variáveis podem ser acedidas através do
seu nome ouou, indirectamente,
indirectamente através da sua
localização, ou seja do seu endereço na memória
O acesso indirecto é conseguido através de
apontadores
A utilização de apontadores em C é geralmente
extensa, pois a linguagem suporta diversas
operações com apontadores
Há várias operações que são efectuadas de forma
mais eficiente através da utilização de apontadores
1
Operadores & e *
Existem dois operadores que é importante conhecer
no contexto da utilização de apontadores
Operador de indirecção & - aplicado a uma variável
devolve o endereço dela
Operador de desreferenciação * - aplicado a um
endereço devolve a variável cuja localização é esse
endereço
Exemplo
int n; Cria uma variável inteira de nome n
&n Endereço da variável n
*&n Variável n
Tipos apontador
Em C, os endereços constituem um novo tipo de
dados que os programas podem manipular
Faz sentido declarar variáveis cujos valores são
endereços de outras variáveis
Para cada tipo T definido, existe o tipo dos
endereços de variáveis do tipo T, ou seja dos
apontadores para T
Este tipo é referido por T
T*
2
Tipos apontador (cont.)
Exemplo
D
Declaração
l ã de
d uma variável
iá l de
d nome p que pode d
conter endereços de variáveis inteiras (diz-se que é
um apontador para int ou ponteiro para int)
int *p;
Depois desta declaração é possível fazer
p=&n;
Enquanto p não for alterado, a expressão *p designa
a variável n, dizendo-se que p aponta para n
Se p aponta para n então é possível fazer, por
exemplo
(*p)++ é o mesmo que n++
3
Tipos apontador (cont.)
Considere-se uma situação em que existem três
variáveis inteiras,
inteiras i,
i nej
Existe ainda um apontador para inteiros de nome
iptr
Se, inicialmente, fizermos:
i = 0; iptr = &i; n = 17; j = 23;
será obtida a seguinte situação:
iptr i n j
0 17 23
E fazendo
*iptr=j;
fica
iptr i n j
23 0 23
4
Tipos apontador (cont.)
Finalmente, fazendo
*iptr=*iptr+10;
iptr= iptr+10;
fica
iptr i n j
33 0 23
Vectores e apontadores
Em C o nome de um vector representa um
apontador para o seu elemento de índice 0
Se v for um vector com elementos do tipo T, então a
expressão v (sózinha) representa &v[0], ou seja o
endereço do 1º elemento de v
A expressão &v[0], tal como a expressão v, é do tipo
T*, ou seja um apontador para T
Portanto, se p for de tipo T*, então p = v é válida e
significa
i ifi o mesmo que p = &v[0]& [0]
No entanto, v = p não faz sentido. Porquê?
5
Aritmética de apontadores
Em C é possível fazer algumas operações
aritméticas com apontadores
Adicionar ou subtrair um inteiro a um apontador,
obtendo um apontador
Calcular a diferença entre dois apontadores, obtendo
um inteiro
Aritmética de apontadores
O significado destas operações pode ser explicado
em termos de vectores
Se v for um vector de elementos do tipo T e p um
apontador para T
Se p aponta para v[k] ou seja p = &v[k]
Então p+i aponta para v[k+i]
Pelo que tal como v[k] pode ser acedido por *p,
v[k+i] pode ser acedido por *(p+i)
A subtração de dois apontadores funciona de modo
análogo
Se p e q forem apontadores para T e p=&v[k] e
q=&v[j]
Então p – q vale k - j
6
Aritmética de apontadores
Pode dizer-se que
v[i]
[i] é o mesmo que *(v+i)
*( i)
&v[i] é o mesmo que v+i
Estão também definidos os operadores ++ e --
para apontadores
Assim, ++p é o mesmo que p=p+1 e --p é p=p-1
Para que os resultados destas operações com
apontadores façam sentido é necessário que os
seus resultados sejam apontadores para elementos
existentes no vector
Aritmética de apontadores
É também possível utilizar operadores relacionais
com apontadores
Por exemplo, se p=&v[k] e q=&v[j] então p<q será
verdade se k<j
A igualdade == e a não igualdade != podem também
ser usados, tanto no contexto de vectores como fora
dele
p==q será verdade se ambos os ponteiros tiverem o
mesmo valor
valor, ou seja se referenciarem a mesma
variável
7
Percorrer vectores com apontadores
É comum utilizar ponteiros em vez dos índices para
percorrer vectores
Por exemplo, uma função para calcular a média dos
elementos de um vector pode ser
double vector_average(int v[], int n)
{
double sum = 0.0; /* total */
int *ptr; /* ponteiro de travessia*/
int *endptr = v + (n-1); /* ponteiro para o último
elemento*/
8
Percorrer vectores com apontadores
É importante perceber como a linguagem trata dos
espaços de memória imediatamente a seguir e
antes de um vector
Se for declarado int vector[MAX]; é possível
definir ptr = &vector[MAX];, podendo afirmar-se
que &vector[MAX] > &vector[MAX-1]
Não é possível aceder ao elemento, mas o endereço
pode ser utilizado
Pelo contrário, não é legal aceder ou utilizar o
endereço anterior a vector[0], não podendo ser
assumida qualquer relação entre esse endereço
e o endereço dos elementos do vector
9
Vectores e apontadores
Do exposto sobre a relação entre vectores e
apontadores resulta que os protótipos seguintes
são equivalentes
double vector_average(int t[], int n)
e
double vector_average(int *t, int n)
O compilador transforma o primeiro no segundo
automaticamente.
A segunda versão permite chamar a função apenas
sobre uma parte do vector
Para calcular a média dos k elementos a partir do
elemento i, ficaria:
m = vector_average(&t[i], k);
ou m = vector_average(t+i, k);
Vectores e apontadores
É possível fazer uma nova versão da função que
encontra a posição (índice) do máximo de um vector
int vector_max (double *v, int n) /* pre n>0 */
{
double *maior = v;
double *p = v+1;
n--;
while (n--)
{
if (*maior < *p)
maior
i = p;
p++;
}
return maior-v;
}
10
Vectores e apontadores
Um outro exemplo interessante é uma função para
somar dois vectores,
vectores elemento a elemento,
elemento ficando
o resultado no primeiro deles
void vector_add (double *v, double *u, int n)
{
while (n--)
*v++ += *u++;
}
Vectores e apontadores
Muitas vezes é necessário copiar um vector para
outro
Por exemplo, se estiverem definidas:
double f[MAX]; e double g[MAX];
Para fazer uma cópia de g poderia haver a tentação
de fazer
f = g; /* ERRADO!!! */
Esta instrução está errada por dois motivos:
Tenta alterar um ponteiro constante (f)
Mesmo que isso fosse possível, estaria a
atribuir ponteiros e não os elementos da tabela
11
A função memcpy
A cópia de dois vectores pode ser feita de duas
formas:
Um ciclo que atribua os elementos de g
individualmente aos correspondentes de f
Utilizando a função memcpy
A função memcpy copia um conjunto de bytes de
uma zona de memória para outra, pelo que pode
ser usada p
para copiar
p vectores de q
qualquer
q tipo
p
Para a utilizar é necessário incluir o ficheiro string.h
A função memcpy
A função memcpy recebe apontadores para a zona
destino e para a zona origem e ainda o número de
bytes a copiar
Assim a cópia dos vectores pode ser feita com a
instrução:
memcpy (f, g, sizeof (g));
Em que sizeof é uma função que devolve o tamanho
em bytes do tipo de dado que lhe é passado como
argumento
12
Apontadores genéricos
A função memcpy pode receber apontadores para
elementos de qualquer tipo,
tipo o que só é possível
através da utilização de apontadores genéricos
Um apontador genérico é definido como um
apontador para void
É um endereço, mas não um endereço para um tipo
de dados particular
Por definição é garantido que um apontador genérico
ocupa um espaço que é suficiente para albergar um
apontador
d para qualquer
l tipo
i de
d variável
iá l
É ainda garantido que se pode converter qualquer
apontador em apontador genérico ou vice-versa, sem
qualquer perda de informação.
Apontadores genéricos
A função memcpy tira partido das características
dos apontadores genéricos,
genéricos de modo a permitir que
lhe sejam passados apontadores de qualquer tipo
Assim, esta função declara os seus dois primeiros
parâmetros como apontadores genéricos (void *)
Os apontadores passados como parâmetros (f e g no
exemplo) são convertidos automaticamente para
apontadores para void
Internamente, a função
f ã memcpy converte os
apontadores para void em apontadores para byte,
efectuando depois a cópia de um byte de cada vez.
13
A função memmove
A memcpy não pode ser utilizada em situações em
que as zonas de memória se sobreponham
Esta situação acontece quando se pretende
copiar parte de um vector para outra posição
dentro do mesmo vector
Nestas situações deve ser usada a função
memmove, que recebe os mesmos parâmetros e
funciona da mesma maneira q que a memcpy,
py,
sem apresentar a limitação indicada
A vantagem do memcpy reside na sua eficácia,
enquanto que o memmove é mais geral
A função malloc
Uma das limitações da utilização dos vectores é a
necessidade de especificar o seu tamanho máximo
antes da compilação
Isto pode resultar em desperdício de memória ou em
programas limitados quanto aos dados que podem
armazenar
A linguagem C integra nas suas bibliotecas funções
que permitem ultrapassar estas limitações
s mais
As a s usadas são a malloc
a oc (pe
(permite
e reservar
ese a umu
bloco de memória) e a free (permite libertar um
bloco de memória)
Para utilizar estas funções é necessário incluir o
ficheiro stdlib.h
14
A função malloc
A função malloc recebe um único parâmetro: o
número de bytes a reservar
Devolve um apontador para o bloco ou NULL caso
não consiga efectuar a reserva da quantidade de
memória pedida
Assim, caso se pretenda um vector para guardar n
doubles, podemos fazer:
tptr
p = malloc ((n * sizeof(double));
( ));
Dado que é possível que malloc devolva NULL (se
não houver memória suficiente), é necessário
testar o valor devolvido antes de se assumir que
dispomos do vector pretendido
A função malloc
Por exemplo, a reserva de espaço para um vector
de n doubles será:
tptr = malloc (n * sizeof(double));
if (tptr == NULL)
{
printf (“Memória não disponível\n”);
return 1; /* Assinala erro */
}
A memória reservada com o malloc não é
iniciali ada pelo sistema,
inicializada sistema devendo
de endo essa tarefa
ta efa ser
se
feita pelo programa
O apontador devolvido pela malloc não deve ser
alterado pelo programa, uma vez que tal impediria
o acesso posterior à memória reservada
PPP António José Mendes 183
15
As funções calloc e free
A função calloc é semelhante à malloc, excepto nos
parâmetros que recebe (número de itens e
tamanho de cada um deles) e no facto de inicializar
a zero toda a memória reservada
A memória reservada com malloc ou com calloc
tem que ser libertada explicitamente
Para isso usa-se a função free que recebe um
ponteiro para o início do bloco de memória em causa
Na utilização desta função há que ter em conta o
seguinte:
i t
Só pode ser utilizada com blocos de memória
reservados com a malloc ou a calloc
Após a chamada de free deixa de ser possível aceder ao
bloco de memória
16
Apontadores como parâmetros
Uma solução é utilizar apontadores como
p
parâmetros
Sendo passados por valor, as alterações feitas aos
apontadores dentro da função não se refletem fora
dela
Mas o mesmo não se aplica às variáveis por eles
apontadas
Assim, podem ser colocadas nestas variáveis valores
que se pretende transmitir para fora da função
17
Apontadores como parâmetros
Esta função poderia ser chamada a partir de outra
g
como se segue
void TestRoots(void)
{
double a, b, c;
double r1, r2;
while (scanf ("%lf%lf%lf", &a, &b, &c) == 3)
if (roots(a, b, c, &r1, &r2))
printf (("%lf
%lf %lf\n",
%lf\n , r1, r2);
else
printf ("Não tem raízes reais.\n");
}
Parâmetros vectoriais
Muitas vezes é preciso usar funções que têm
parâmetros vectoriais
p
Na chamada dessas funções coloca-se o nome do
vector a usar no local do argumento apropriado
Se estiver definida
int vector_min(double v[], int n)
Uma chamada poderá ser
i = vector_min (t, n);
Sendo t um vector de doubles, i e n inteiros
Mas t, sendo o nome de um vector, é um apontador
para o 1º elemento do vector, pelo que na realidade
está a ser passado à função um apontador
Isso leva a que o cabeçalho da função pudesse ser
int vector_min(double *v, int n)
PPP António José Mendes 189
18
Funções que devolvem apontadores
Os apontadores podem também ser resultado de
ç
funções
Por exemplo, a função vector_min() poderia devolver
um apontador para o menor elemento em vez de
devolver o seu índice
O cabeçalho ficaria
double *vector_min(double *v, int n)
A uma chamada podia ser
pmin = vector_min(t, n);
em que pmin seráá apontador para double
O menor valor podia depois ser escrito com
printf (“Menor valor do vector é %lf”, *pmin);
19
Voltando às cadeias de caracteres
Todas as funções de manipulação de cadeias de
caracteres esperam que:
As cadeias de caracteres envolvidas terminem com ‘\0’
Que a cadeia de caracteres destino tenha espaço
suficiente para o resultado
Que as cadeias de caracteres utilizadas não sejam
sobrepostas
20
Vectores de vectores = matrizes
O acesso a cada elemento de uma matriz é feito pela
indicação dos respectivos índices
Tendo em conta a definição anterior são válidos os
índices desde notas [0] [0] e notas [99] [3]
O nome da matriz é um apontador para o primeiro
elemento da tabela
Neste caso notas é um apontador para um vector
Deste modo, notas e ¬as[0][0] não são
equivalentes
q
Apesar de conterem o mesmo endereço, têm tipo
diferente: notas é um apontador para um vector de
inteiros, enquanto que ¬as[0][0] é um apontador
para um inteiro
21