Antes de entender o que é um ponteiro (veja Pointer na Wikipedia), é preciso entender o conceito de endereço.
A memória de qualquer computador é uma seqüência de bytes. Cada byte pode armazenar um número inteiro entre 0 e 255 (ou seja, um caracter). Cada byte tem um endereço (= address) numérico.
Cada objeto que reside na memória do computador ocupa um certo número de bytes. No meu computador, cada inteiro ocupa 4 bytes consecutivos, um caracter ocupa 1 byte e um número ponto-flutuante ocupa 4 bytes consecutivos.
Cada objeto que reside na memória do computador tem um endereço; no meu computador, o endereço é do primeiro byte do objeto. Por exemplo, depois das declarações
int x; char a; int v[100];
o endereços das variáveis poderiam ser (o exemplo é fictício):
x | 89422 | |
a | 89426 | |
v | 89427 | |
v[0] | 89427 | |
v[1] | 89431 | |
v[2] | 89435 |
Em C, o endereço de uma variável é dado pelo operador & . Assim, o endereço de x é &x . (Não confunda esse uso de "&" com o operador lógico and, que em C se escreve "&&".) No exemplo acima, &x vale 89422 e &v[3] vale 89439.
O segundo argumento da função de biblioteca scanf é o endereço da posição na memória onde devem ser depositados os objetos lidos no dispositivo padrão de entrada:
int a; scanf ("%d", &a);
Um ponteiro (= apontador = pointer) é um tipo especial de variável cujo valor é um endereço. Um ponteiro pode ter o valor especial
NULL
que não é o endereço de lugar algum. NULL é uma constante definida no arquivo-interface stdlib. Na maioria dos computadores, NULL vale 0. Se o valor de um ponteiro p não é NULL então
*p
é o conteúdo de p, ou seja, o valor armazenado no objeto cujo endereço é p . (Não confunda esse uso de "*" com o operador de multiplicação!) Antes de dizer *p, o programador deve se certificar de que p é diferente de NULL.
|
Figura esquerda: um ponteiro p,
armazenado no endereço 90001,
contém o endereço de uma célula.
Figura direita: representação esquemática. |
Há vários tipos de ponteiros: ponteiros para caracteres, ponteiros para inteiros, ponteiros para ponteiros para inteiros, ponteiros para registros etc. O computador faz questão de saber de que tipo de ponteiro você está falando. Um ponteiro p para um inteiro é declarado por
int *p;
Um ponteiro r para um ponteiro que apontará um inteiro é declarado assim:
int **r;
Suponha que a, b e c são variáveis inteiras. Eis um jeito bobo de fazer "c = a+b":
int *p; /* p é um ponteiro para um inteiro */ int *q; p = &a; /* o valor de p é o endereço de a */ q = &b; /* q aponta para b */ c = *p + *q;
Outro exemplo bobo:
int *p; int **r; /* r é um ponteiro para um ponteiro para um inteiro */ p = &a; /* p aponta para a */ r = &p; /* r aponta para p e *r aponta para a */ c = **r + b;
Suponha que precisamos de uma função que troque os valores de duas variáveis inteiras, digamos x e y. É claro que a função
void troca (int x, int y) /* errado! */ { int temp; temp = x; x = y; y = temp; }
não produz o efeito desejado os argumentos são passados à função "em valor" (= call by value) e não "por referência" (= call by reference): a função recebe cópias das variáveis e troca os seus valores, enquanto as variáveis "originais" permanecem inalteradas. Para obter o efeito desejado, é preciso passar à função os endereços das variáveis:
void troca (int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; }
Para aplicar a função às variáveis x e y basta dizer
troca (&x, &y);
Analogamente, para trocar os valores de variáveis inteiras a e b podemos dizer
int *p, *q; p = &a; q = &b; troca (p, q);
void troca (int *x, int *y) { int *temp; *temp = *x; *x = *y; *y = *temp; }
Os elementos de qualquer vetor (= array) têm endereços consecutivos na memória do computador. Depois da declaração
int v[100];
a variável v é, essencialmente, um ponteiro para o primeiro elemento do vetor. Mais precisamente, v é uma espécie de "ponteiro constante": você não pode mudar o valor de v.
Como v contém o endereço do primeiro elemento do vetor, a expressão v+1 é o endereço do segundo elemento, v+2 é o endereço do terceiro elemento, etc. Se i é uma variável do tipo int então as expressões v + i e &v[i] têm exatamente o mesmo valor. Portanto,
*(v+i) = 87;
tem o mesmo efeito que
v[i] = 87;
Para "carregar" um vetor v[0..9] pode-se dizer
for (i = 0; i < 10; i++) scanf ("%d", &v[i]);
ou
for (i = 0; i < 10; i++) scanf ("%d", v + i);
As duas formas têm exatamente o mesmo efeito.
int v[100];
Descreva, em português, a seqüência de operações que deve ser executada para calcular o valor da expressão
&v[k + 9];
char *a; char *b; a = "cenoura"; b = "cereja"; if (a < b) printf ("%s precede %s no dicionário", a, b);
int a[99]; for (i = 0; i < 99; ++i) a[i] = 98 - i; for (i = 0; i < 99; ++i) a[i] = a[a[i]];