Você está na página 1de 21

A

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

PPP António José Mendes 155

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

PPP António José Mendes 156

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*

PPP António José Mendes 157

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++

PPP António José Mendes 158

Tipos apontador (cont.)


† Exemplo
„ E
Em (*p)++
(* ) os parentesis
t i são
ã necessários
á i pois
i os
operadores * e ++ têm a mesma precedência e
associam da direita para a esquerda
„ Assim, *p++ é o mesmo que *(p++) o que é bem
diferente de (*p)++

PPP António José Mendes 159

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

PPP António José Mendes 160

Tipos apontador (cont.)


„ Fazendo agora
n= iptr;
n=*iptr;
„ fica
iptr i n j
0 0 23

„ E fazendo
*iptr=j;
„ fica
iptr i n j
23 0 23

PPP António José Mendes 161

4
Tipos apontador (cont.)
„ Finalmente, fazendo
*iptr=*iptr+10;
iptr= iptr+10;
„ fica
iptr i n j
33 0 23

PPP António José Mendes 162

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ê?

PPP António José Mendes 163

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

PPP António José Mendes 164

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

PPP António José Mendes 165

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

PPP António José Mendes 166

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

PPP António José Mendes 167

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*/

for (ptr = v; ptr <= endptr; ptr++)


sum += *ptr;
return (n != 0) ? sum / n : 0.0;
}

PPP António José Mendes 168

Percorrer vectores com apontadores


† Um outro exemplo é uma função para calcular a posição do
máximo de um vector

int vector_max(int v[], int n)


{
int *max_ptr; /* total */
int *ptr; /* ponteiro de travessia*/
int *endptr = v + (n-1); /* ponteiro para o último */

max_p ptr = v;;


for (ptr = v+1; ptr <= endptr; ptr++)
if (*ptr > *max_ptr)
max_ptr = ptr;
return max_ptr - v;
}

PPP António José Mendes 169

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

PPP António José Mendes 170

Percorrer vectores com apontadores


† Por exemplo, para escrever os elementos de um
vector por ordem inversa,
inversa poderíamos pensar em
for (ptr = &vector[MAX-1]; ptr >= vector; ptr--)
printf (“%d\n”, *ptr);
„ No entanto, tal não é possível uma vez que o ciclo só
pára quando ptr for menor que vector, o que pode
não ser um endereço legal
„ A forma correcta seria:
ptr = &vector[MAX];
p [ ];
while (ptr-- > vector)
printf (“%i\n”, *ptr);

PPP António José Mendes 171

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);

PPP António José Mendes 172

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;
}

PPP António José Mendes 173

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++;
}

PPP António José Mendes 174

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

PPP António José Mendes 175

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

PPP António José Mendes 176

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

PPP António José Mendes 177

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.

PPP António José Mendes 178

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.

PPP António José Mendes 179

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

PPP António José Mendes 180

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

PPP António José Mendes 181

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

PPP António José Mendes 182

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

PPP António José Mendes 184

Apontadores como parâmetros


† Uma das limitações das funções é apenas poderem
devolver um resultado
† Por exemplo, caso se pretenda uma função capaz
de calcular e devolver as raízes de uma equação do
2º grau, será necessário devolver o valor das raízes
e ainda uma indicação se as mesmas existem
† É portanto necessário devolver 3 valores, o que
aparentemente não seria possível
„ As funções
ç só devolvem um valor
„ Os parâmetros são passados por valor (ou seja é
passada uma cópia dos mesmos), o que significa que
as alterações feitas dentro da função não são
reflectidas fora dela

PPP António José Mendes 185

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

PPP António José Mendes 186

Apontadores como parâmetros


† Exemplo da equação do 2º grau
int roots ((double a,, double b,, double c,, double *x1,, double *x2))
{
double delta = b*b – 4.0*a*c;
int result = delta >= 0;
if (result)
{
*x1 = (-b + sqrt(delta))/(2.0*a);
*x2 = -b/a - *x1;
}
return result; /*Indica se há raizes reais
}
„ a, b e c funcionam como parâmetros de entrada
„ x1 e x2 funcionam como parâmetros de saida
PPP António José Mendes 187

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");
}

PPP António José Mendes 188

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);

PPP António José Mendes 190

Voltando às cadeias de caracteres


† As cadeias de caracteres são vectores de caracteres, pelo
que podem ser manipuladas com apontadores
„ De facto, as funções de manipulação de cadeias de
caracteres utilizam apontadores na sua definição:
„ char *strcpy(char *s, const char *t)
† copia t para s e devolve s
„ int strcmp(const char *s, const char *t)
† compara s e t, devolve 0 se forem iguais, <0 se s < t e >0
se s > t
„ int strlen (const char *s)
† devolve o nº de caracteres antes do terminador
„ char *strcat(char *s, const char *t)
† concatena t no fim de s e devolve s
„ char *strchr(const char *s, char x)
† devolve apontador para a primeira ocorrência de x (um
caracter) em s ou NULL se não ocorrer
„ char *strstr(const char *s, const char *t)
† devolve apontador para a primeira ocorrência de t (uma
cadeia de caracteres) em s ou NULL se não ocorrer

PPP António José Mendes 191

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

PPP António José Mendes 192

Vectores de vectores = matrizes


† Os vectores em C podem conter elementos de
qualquer tipo
„ Em particular, se os elementos de um vector
forem também vectores, obtemos uma matriz
„ Por exemplo, para armazenar as notas obtidas
por cada estudante em vários testes, poderíamos
declarar:
#define MAX_ESTUD 100
#define
de e MAX_TESTES
S S4
int notas [MAX_ESTUD] [MAX_TESTES];

PPP António José Mendes 193

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 &notas[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 &notas[0][0] é um apontador
para um inteiro

PPP António José Mendes 194

21