|
O que os ponteiros
permitem fazer? (início
da página)
|
É difícil enumerar tudo. Abaixo os usos mais comuns.
» Acessar endereços de memória que o programa aloca em tempo de
execução.
» Acessar variáveis que não são visíveis a uma função.
» Manipulação de arrays usando ponteiros para seus elementos,
evitando a forma direta.
» Manipulação de strings.
» Passar o endereço de uma função para outra.
» Retornar mais de um valor de uma função. |
|
|
O que é ponteiro?
(início da
página)
|
A definição é simples: Ponteiro é uma variável
que contém o endereço de um objeto de dados, normalmente uma
outra variável. Daí o nome, pois ele aponta para outra variável.
|
|
|
Declarando ponteiros
(início da
página)
|
Conforme qualquer variável em C, o ponteiro deve ser
declarado antes de ser usado. Basta colocar o operador indireto (*) após
o tipo conforme se segue:
int *ptr;
E teremos a variável ptr
definida como um ponteiro para uma variável do tipo
int.
Lembrar que a declaração de ponteiro não tem o mesmo significado da de
uma variável. Ela apenas indicará o tipo de objeto de dados que será
apontado e, desde que contém endereço de memória, o tamanho em bytes que
ocupa não tem relação com o do objeto apontado mas é fixo e depende
apenas do modelo de memória do sistema (2 bytes ou 4 bytes,
normalmente).
Se for declarar mais de uma variável por linha, usar o (*) antes de
cada. Exemplo:
char *ch1, *ch2; e
ch1 e ch2
serão ponteiros para o tipo char.
Se for omitido conforme char *ch1, ch2;
a variável ch2 não será ponteiro mas sim,
uma variável comum tipo char e certamente
provocará erro se usada como ponteiro.
O ponteiro pode ser declarado para qualquer tipo legal de variável em C
(char, int, float, double, etc), além de void,
que seria um genérico, podendo apontar para qualquer tipo de dado. |
|
|
Inicializando um ponteiro (início
da página)
|
Uma vez declarado, o ponteiro
deve ser associado ao endereço da variável desejada para que possa ser
usado. Veja exemplo abaixo (é suposto que as linhas sejam executadas
dentro de uma função). |
 |
int var;
int *ptr;
var = 10;
ptr = &var;
A última linha associa o ponteiro ptr ao
endereço da variável var. Para isto é usado
o operador de endereçamento (&).
A Fig 1 dá uma noção gráfica desta relação.
Está considerado que o endereçamento do sistema é dado por 2 bytes. |
Assim
ptr ocupará 2 bytes e var também 2
por ser uma variável int.
O número 4052 da posição de memória do primeiro byte de
var é apenas ilustrativo e, na
prática, dependerá do local da memória onde o programa foi carregado.
Mas supomos que seja este. Portanto, após a execução de
ptr = &var; o conteúdo de
ptr será 4052, ou seja, o primeiro byte da
variável apontada.
Com ptr apontando para
var, podemos ler ou modificar o valor desta
última de forma indireta através de ptr.
Exemplo: se continuarmos as linhas anteriores com esta
int newVar = *ptr; o valor de
newVar será 10, isto é, o valor de
var. E, se continuarmos
*ptr = 20; o valor de
var passará a ser 20, ou seja, modificado
através de ptr.
É importante lembrar que um ponteiro declarado mas não inicializado
poderá ter conteúdo nulo ou aleatório, a depender de onde foi alocado
pelo sistema. Nesta condição, se o conteúdo apontado for modificado
conforme linha anterior, poderá afetar variáveis de sistema ou de outros
programas com conseqüências imprevisíveis. |
|
|
Ponteiros para arrays (início
da página)
|
Ponteiros oferecem um
eficiente e prático meio de acesso e manipulação dos elementos de uma
array. Seja uma globalmente declarada conforme abaixo. |
 |
int ar[] = {10,
50, 20, 30};
E o ponteiro
int *ptr;
E se, dentro de uma função fizermos
ptr = &ar[0];
Quando esta for executada, ptr estará
associado ao primeiro byte da array conforme Fig 2 ao lado.
Similar ao item anterior, o valor de 4052 é meramente ilustrativo.
|
Aritmética do ponteiro
Desde que que ptr foi apontado para o
início da array (elemento 0), o endereçamento indireto
*ptr poderá obter ou modificar o valor
deste elemento, similar item anterior.
E para os demais elementos? Considerando que ptr
está apontado para o elemento 0, basta incrementá-lo de uma unidade para
movê-lo para o elemento 1, ou seja, ptr++;
ou ptr += 1; Esta operação fará o
conteúdo de ptr 4054, que é o endereço do
elemento 1.
Assim, na aritmética dos ponteiros, os compiladores ajustam os endereços
de acordo com o tamanho dos elementos da array e você não
precisará se preocupar com isto (Exemplo: se em vez de
int os dados fossem do tipo
float, o endereço seria incrementado de 4
bytes).
Estando ptr apontado para o elemento 1, se você desejar retornar ao 0,
basta dar um decremento, ptr--; ou
ptr -= 1;
Em resumo podemos dizer que a simples soma ou subtração ao ponteiro
permite o acesso a qualquer elemento. Entretanto, cabe a quem programa
cuidar para que o ponteiro não ultrapasse os limites da array.
Caso contrário, o programa poderá ficar corrompido, com resultados
imprevisíveis. Os compiladores, normalmente, não verificam isso.
Uma outra sintaxe para iniciar
No início deste item foi usada a seguinte linha para inicializar o
ponteiro:
ptr = &ar[0]; Mas poderia ser
perfeitamente desta forma:
ptr = ar;
Isto significa que o nome de uma array é, na realidade, um
ponteiro e pode ser usado da mesma forma. |
|
|
Ponteiros e strings (início
da página)
|
Desde que uma string é
uma array de elementos tipo char, as
operações ocorrem conforme item anterior mas aqui estão algumas notações
mais elegantes para simplificar a sintaxe. Seja, por exemplo o seguinte
programa: |
#include <stdio.h>
main(){
int i;
char rua[] = "nova";
char *ptr = rua;
for (i=0; i<4; i++)
printf( "%d> %c\n", i, *ptr++ );
} |
A saída seria:
0> n
1> o
2> v
3> a
|
Strings têm um
caractere nulo após o último elemento. Assim uma versão mais elegante do
programa seria: |
#include <stdio.h>
main(){
int i = 0;
char rua[] = "nova";
char *ptr = rua;
while( *ptr )
printf( "%d> %c\n", i++, *ptr++ );
} |
A saída seria a mesma do anterior.
Entretanto o loop while permite o
seguinte artifício:
*ptr irá parar o loop quando chegar
no caractere nulo no final da string. |
Agora uma outra versão do
programa: |
#include <stdio.h>
main(){
int i;
char rua[] = "nova";
char *ptr = rua;
for (i=0; i<4; i++)
printf( "%d> %c\n", i, *(rua+i) );
} |
É quase idêntico ao primeiro mas, no último
argumento de printf foi usado
*(rua+i) em lugar de
*ptr++.
Isso porque, conforme item anterior, o nome de uma array é também
um ponteiro para a mesma e pode ser usado como tal. |
E também poderia usar o
clássico rua[i].
As notações *ptr++ (isto é incrementado
para cada i), *(rua+i) e
rua[i] se equivalem e podem ser usadas sem
distinção. Entretanto, muitos programadores preferem a notação como
ponteiro por ser mais rápida em certas condições. Mas não é só isso.
Depois de você se habituar com os ponteiros e de fazer uso intensivo
deles, você irá certamente preferir a notação como ponteiro para dar uma
aparência mais homogênea aos seus códigos. |
|
|
Ponteiros de estruturas (início
da página)
|
Ponteiros para estruturas são
escritos de forma similar aos já mencionados. A principal diferença é o
uso do operador -> para acesso aos
membros das mesmas. Veja exemplo a seguir. |
#include <stdio.h>
struct telelista{
char nome[15];
char ddd[2];
char num[10];
};
struct telelista *ptr;
main(){
struct telelista pedro = {
"Pedro da Silva", "99", "123-456"};
ptr = &pedro;
printf( "%s", ptr->nome );
} |
No programa ao lado, após a declaração da
estrutura, é declarado o ponteiro ptr para
a mesma, de forma similar ao de outras variáveis.
Dentro da função main(), depois de
definidos membros para a variável pedro, o
ponteiro ptr é associado à mesma.
Veja então o segundo argumento da função printf,
ptr->nome que aponta para o membro
nome da estrutura (tem o mesmo efeito da
notação usual pedro.nome)
Os demais membros seriam acessados de forma similar (ptr->ddd
e ptr->mum). |
|
|
Ponteiros como argumentos de funções
(início da
página)
|
Quando uma função recebe uma
variável como argumento, ela tem apenas uma cópia do valor desta
variável. Assim, se esta variável é externa à função, esta não pode
modificar seu valor. Mas se o endereço (isto é, um ponteiro) da
variável for passado à função, não haverá dificuldade na modificação.
A idéia também permite que uma função possa retornar mais de um valor.
Veja exemplo no programa abaixo. |
#include <stdio.h>
void dobrar (int*, int*);
main(){
int a = 2, b = 4;
dobrar(&a, &b);
printf("a=%d b=%d", a, b);
}
void dobrar(int *ptr0, int *ptr1){
*ptr0 *= 2;
*ptr1 *= 2;
} |
A saída deste programa seria:
a=4 b=8
Ou seja, as variáveis a e
b, externas à função
dobrar, foram modificadas pela mesma e a função retornou mais de
um valor.
Um ganho que se obtém ao se passar ponteiros para uma função é de
desempenho quando os argumentos são objetos grandes como arrays e
estruturas. Lembrar que a função recebe uma cópia do argumento.
Assim, ao se passar um endereço, é eliminada a operação de copiar todo o
objeto. |
|
|
Ponteiros para funções (início
da página)
|
Pelo que foi até então
exposto, é lícito supor que ponteiros podem se referir a qualquer objeto
localizado na memória. Tais objetos incluem também as funções e,
portanto, é possível criar um ponteiro para uma função.
A sintaxe é semelhante mas tem algumas regras próprias conforme programa
exemplo abaixo. |
#include <stdio.h>
main(){
int (*ptrf) ();
ptrf = printf;
(*ptrf) ("Teste de ponteiro");
} |
A saída do programa seria:
Teste de ponteiro
Ou seja, a função printf foi chamada
através do ponteiro ptrf.
Deve se observada a sintaxe indicada. Inclusive o tipo de dado na
declaração do ponteiro int (*ptrf) (); deve
ser o mesmo retornado pela função (int
porque printf retorna
int). |
Ponteiros de funções permitem
procedimentos avançados como, por exemplo, a criação de uma tabela de
funções e a execução de cada uma de acordo com um determinado
critério |
|