CES-10 INTRODUÇÃO À
COMPUTAÇÃO
Capítulo IX
Ponteiros
Capítulo IX – Ponteiros
9.1 – Introdução
9.2 – Relação entre ponteiros e variáveis indexadas
9.3 – Alocação dinâmica de memória
9.4 – Variáveis indexadas, ponteiros e estruturas
como parâmetros e elementos de retorno
9.5 – Subprogramas como parâmetros
9.6 – Encadeamento de estruturas
9.1 – Introdução
9.1.1 – Constantes e variáveis do tipo ponteiro
As variáveis dos programas apresentados nos capítulos
anteriores são usadas para guardar e manipular valores de
determinados tipos
Endereços também podem ser manipulados em C
Sendo a uma variável declarada em um programa, &a é seu
endereço
Ponteiros são endereços
Assim como existem constantes e variáveis do tipo
int, float, char, cadeias de caracteres, vetores, matrizes e
estruturas
também existem constantes e variáveis do tipo ponteiro
O endereço de uma variável a declarada num bloco qualquer
permanece o mesmo durante a execução do bloco
Então &a é uma constante do tipo ponteiro
Variável do tipo ponteiro armazena um endereço que pode
ser alterado durante a execução do programa
Variáveis-ponteiros podem guardar endereços de outras
variáveis
Diz-se que elas apontam para essas outras variáveis
Na declaração de uma variável-ponteiro, deve-se especificar
o tipo das variáveis para as quais ela pode apontar
Exemplo: na declaração
float *p;
p é uma variável-ponteiro destinada a guardar endereços de
variáveis do tipo float, ou a apontar para elas
Abreviadamente, p é um ponteiro para o tipo float
Exemplo: seja trecho de programa:
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
printf ("Endereco(p) = %d\n", &p);
printf ("
p = %d\n", p);
printf ("Endereco(a) = %d\n", &a);
printf ("Endereco(q) = %d\n", &q);
printf ("
q = %d\n", q);
printf ("Endereco(b) = %d\n", &b);
printf ("Endereco(c) = %d\n", &c);
printf ("
a = %g\n", a);
printf ("
b = %d\n", b);
printf ("
c = %d\n", c);
Resultado
Endereco(p)
p
Endereco(a)
Endereco(q)
q
Endereco(b)
Endereco(c)
a
b
c
=
=
=
=
=
=
=
=
=
=
1638204
1638208
1638208
1638216
1638220
1638220
1638224
13.5
15
237
Com este resultado,
pode-se desenhar o
mapa da memória a
seguir
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
Endereco(p)
p
Endereco(a)
Endereco(q)
q
Endereco(b)
Endereco(c)
a
b
c
Resultado
=
=
=
=
=
=
=
=
=
=
1638204
1638208
1638208
1638216
1638220
1638220
1638224
13.5
15
237
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
1638216
1638220
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
p
13.5
a
1638220
q
15
b
237
c
1638208
&a é uma constante-ponteiro
para double
&b é uma constante-ponteiro
para int
1638208
1638216
1638220
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
p
13.5
a
1638220
q
15
b
237
c
1638208
q é um ponteiro para o tipo int
p é um ponteiro para o tipo
double
p recebe o endereço de a
q recebe o endereço de b
1638216
p aponta para a
q aponta para b
1638220
a é apontada por p
b é apontada por q
1638208
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
1638208
p
13.5
a
1638220
q
15
b
237
c
1638208
Endereços, isto é ponteiros,
ocupam 4 bytes
Não é recomendável fazer:
p = &b , pois
1638216
b é um int e
p é um ponteiro para o tipo
double
1638220
Alguns compiladores não
aceitam isso
1638224
int c = 237, b = 15, *q;
double a = 13.5, *p;
p = &a; q = &b;
1638204
13.5
15
b
ou simplesmente:
p
q
13.5
a
1638220
q
15
b
237
c
a
1638216
q
p
1638208
Duas formas de representação
gráfica:
p
1638208
1638220
13.5
a
15
b
1638224
Endereços são números inteiros não-negativos
Então, esses números podem ser atribuídos às variáveis
ponteiros, desde que sejam convertidos para endereços
usando-se fator de conversão
Exemplo: seja a declaração double *p;
muitos compiladores não permitem
p = 48237;
O lado esquerdo deveria receber um endereço e não um
inteiro
No entanto, a seguinte atribuição é aceita:
p = (double *) 48237;
O inteiro 48237
é convertido no
endereço 48237
Muitos compiladores também não aceitam atribuição entre
ponteiros de tipos diferentes
Exemplo: pelas declarações
double *p; int *q;
muitos compiladores não aceitam p = q;
É necessário fazer
p = (double *)q;
nem
e
q = p;
q = (int *)p;
Se alguns compiladores aceitam certas irregularidades, mas
outros não, para melhorar portabilidade, é bom não
cometê-las
A palavra ponteiro poderá ser usada tanto para
variáveis como para valores do tipo ponteiro
Usando ponteiros, o endereço zero de memória é considerado
de forma muito especial
Em C há uma constante simbólica para ele: NULL
As seguintes atribuições são equivalentes:
p = 0;
e
p = NULL;
Diz-se que p está aterrado, ou então que aponta para lugar
nenhum
É diferente de dizer que p está indefinido, pois tal lugar é
perfeitamente definido
Aqui, lugar nenhum é diferente
de nenhum lugar
Representações gráficas para o aterramento de ponteiros:
p
p
p
•
p NULL
9.1.2 – Acesso ao local apontado por um ponteiro
Seja a declaração:
int a, b, *p;
p
O local apontado por p é referenciado por *p
a
*p
b
*p
Executando-se p = &a; então *p passa a coincidir com a
Depois, executando-se p = &b;
coincidir com b
Seja a declaração int a, *p = &a;
então *p passa a
Correto:
É
errado pdizer:
é ponteiro
local
apontado porcom
inicializado
p recebe
o
o endereço
endereço
dede
aa
Exemplo: Seja o seguinte trecho de programa:
int a, b = 2, *p;
p = &a;
*p = 1;
b = *p;
a
1
Inicialmente
Prosseguindo
b
21
p
Exemplo: Seja a seguinte declaração:
int a = 2, b = 5, *p = &a, *q = &b;
Inicialmente:
Voltando
Fazendo
Fazendo*p
àp situação
==q;
*q; inicial:
a
52
p
b
5
q
Apesar dos conteúdos de *p e *q serem iguais,
o conteúdo de p é diferente do conteúdo de q
(*p == *q)
e
(p ≠ q)
Exemplo: Seja o seguinte trecho de programa:
int *p, a;
p = (int *)134;
a = *p;
printf ("*p = %d", a);
Erro de execução:
p
Região
protegida
pelo
sistema
operacional
a
O endereço 134 é
protegido pelo sistema
operacional
O comando a = *p tenta
copiar o valor ali
guardado
RAM
134
O endereço zero também é protegido pelo sistema
operacional
Portanto, ao se aterrar um ponteiro, a simples referência ao
local apontado por ele gera o mesmo tipo de erro
Então a sequência de comandos
p = NULL; a = *p;
não pode ser usada
Sejam p e q dois ponteiros quaisquer
A divisão entre os conteúdos dos locais apontados por eles
não pode ser expressa por
*p/*q
A sequência de caracteres “/*” inicia um comentário
Deve-se deixar pelo menos um espaço em branco no meio
dessa sequência:
*p/ *q
Nos atuais ambientes de programação esse problema é
facilmente detectado
Ali os comentários são escritos com tipo e coloração das
letras distintos do resto do texto dos programas
Em ambientes mais antigos, isso já causou muitos atrasos
na programação
As principais utilidades dos ponteiros são:
Alocação dinâmica de variáveis indexadas
Passagem por referência de argumentos a parâmetros
Encadeamento de estruturas
Os dois primeiros itens eram realizados não por intermédio
de ponteiros nas linguagens de programação antes de C
Para o terceiro sim, tais linguagens já usavam ponteiros
Aliás, o encadeamento de estruturas foi a principal razão
para a criação de ponteiros
Ele é abordado no último tópico deste capítulo e usado
intensamente em Estruturas de Dados
Capítulo IX – Ponteiros
9.1 – Introdução
9.2 – Relação entre ponteiros e variáveis indexadas
9.3 – Alocação dinâmica de memória
9.4 – Variáveis indexadas, ponteiros e estruturas
como parâmetros e elementos de retorno
9.5 – Subprogramas como parâmetros
9.6 – Encadeamento de estruturas
9.2 – Relação entre Ponteiros e
Variáveis Indexadas
9.2.1 – O ponteiro-nome de variável indexada
Em C, diferentemente das linguagens anteriores, há uma forte
relação entre ponteiros e variáveis indexadas
O nome de uma variável indexada é o endereço do
primeiro de seus elementos
É uma constante-ponteiro apontando para seu elemento
zero
Não tem um local exclusivo na memória
O ponteiro-nome de variável indexada aponta para um local
fixo (elemento de índice zero)
Então ele tem valor constante durante a execução do
programa (ponteiro de valor fixo)
Não pode ser alterado como um ponteiro comum
Seja a declaração
int A[8];
Representação gráfica de A:
int A[8];
O nome A não tem local exclusivo; &A é o endereço do início
do vetor A na memória
O valor de A (ou seja, A, simplesmente) é o endereço de A[0]
(&A[0])
A escrita do valor do local apontado por A (ou seja, de *A)
resulta no conteúdo de A[0]
O nome de uma variável indexada pode ser atribuído a
um ponteiro de mesmo tipo
Exemplo: seja o seguinte trecho de programa:
int i, A[8], B[5], *p;
p = A;
p = B;
Inicialmente:
Após
p = B;
A;
A
Proibidos:
p tem localAexclusivo
= p;
B = p;
A e B não têm
A = B;
B = A;
A = &i;
B = &i;
A[0] A[1] A[2] A[3] A[4] A[5] A[6] A[7]
p ?
B
p tornou-se equivalente a B:
A:
B[0] B[1] B[2] B[3] B[4]
p[0] B[0],
A[0], p[1] A[1],
B[1], ... ,
p[7] B[4]
p[4]
A[7]
*A A[0] e *B B[0]
p se tornou variável indexada
9.2.2 – Aritmética de endereços e ponteiros
Seja a seguinte declaração: int *p, i;
A expressão p+i é o endereço do iésimo inteiro após o inteiro
apontado por p (p+i = &p[i])
*p *(p+1)
*(p+i)
p
p[i]
p[0] p[1]
Seja a seguinte declaração: int A[10], i;
Analogamente, A+i = &A[i] e *(A+i) A[i]
*A *(A+1)
*(A+i)
A
A[0] A[1]
A[i]
Exemplo: seja o seguinte
p = 1638204; &A[0] = 1638204;
de programa:
q trecho
= 1638212;
&A[1] = 1638212;
p
A[0]
r = 1638220; &A[2] = 1638220;
q-p = 1;
r-p = 2;
Diferenças de
índices
endereços
ender(q)-ender(p) = 8;
ender(r)-ender(p) = 16;
q
No vídeo
A[1]
double *p, *q, *r, A[3];
p = A; q = p+1; r = p+2;
r
printf ("p = %d; &A[0] = %d;",
p, &A[0]);
A[2]
printf ("\nq = %d; &A[1] = %d;",
q, &A[1]);
printf ("\nr = %d; &A[2] = %d;",
r, &A[2]);
printf ("\n\nq-p = %d;\nr-p = %d;", q-p, r-p);
printf("\n\nender(q)-ender(p) = %d;\nender(r)-ender(p) = %d;",
(int)q-(int)p, (int)r-(int)p);
Relações entre ponteiros e variáveis indexadas e entre
subscritos e o operador unário ‘*’ possibilitam várias
alternativas para realizar certas operações
Por exemplo, considerando-se a seguinte declaração:
int i, A[10] = {1,2,3,4,5,6,7,8,9,10}, *p, soma;
A soma dos elementos de A pode ser feita por:
1)
2)
3)
4)
5)
for
for
for
for
for
(soma
(soma
(soma
(soma
(soma
=
=
=
=
=
0, i = 0;
0, i = 0;
0, p = A;
0, p = A,
0,p = A,i
i
i
p
i
=
< 10; i++) soma += A[i];
< 10; i++) soma += *(A+i);
< &A[10]; p++) soma += *p;
= 0; i < 10; i++) soma += p[i];
0; i < 10; i++) soma += *(p+i);