Os conceitos de endereço e ponteiro são fundamentais em qualquer linguagem de programação (embora sejam mais visíveis em C que em outras linguagens). O conceito de ponteiro é muito útil mas difícil; seu domínio exige um certo esforço.
Veja o verbete Pointer na Wikipedia.
Cada objeto na memória do computador ocupa um certo número de bytes consecutivos. No meu computador, um char ocupa 1 byte, um int ocupa 4 bytes e um double ocupa 8 bytes
Cada objeto na memória do computador tem um endereço. Na maioria dos computadores, o endereço de um objeto é o endereço do seu primeiro byte. Por exemplo, depois das declarações
char c;
int i;
struct {
int x, y;
} ponto;
int v[4];
os endereços das variáveis poderiam ser
| c | 89421 | |
| i | 89422 | |
| ponto | 89426 | |
| v[0] | 89434 | |
| v[1] | 89438 | |
| v[2] | 89442 |
(o exemplo é fictício). O endereço de uma variável é dado pelo operador &. (Não confunda esse uso de "&" com o operador lógico and, que em C se escreve "&&".) Se i é uma variável então &i é o seu endereço. No exemplo acima, &i vale 89422 e &v[3] vale 89446.
Exemplo: 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 i; scanf( "%d", &i);
Um ponteiro (= apontador = pointer) é um tipo especial de variável que armazena endereços. Um ponteiro pode ter o valor especial
NULL
que não é o endereço de lugar algum. A constante NULL está definida no arquivo-interface stdlib e seu valor é 0 na maioria dos computadores.
Se um ponteiro p armazena o endereço de uma variável i, podemos dizer "p aponta para i" ou "p é o endereço de i" (querendo dizer "o ponteiro p aponta para i" no primeiro caso e "o valor de p é o endereço de i" no segundo). Se um ponteiro p tem valor diferente de NULL então
*p
é o valor do objeto apontado por p. (Não confunda esse uso de "*" com o operador de multiplicação!) Por exemplo, se i é uma variável e p é igual a &i então dizer "*p" é o mesmo que dizer "i".
|
|
Figura esquerda: um ponteiro p,
armazenado no endereço 90001,
contém o endereço de um inteiro.
Figura direita: representação esquemática da situação. |
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. Para declarar um ponteiro p para um inteiro, diga
int *p;
Para declarar um ponteiro p para um registro cel, diga
struct cel *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 i e j. É claro que a função
void troca( int i, int j) /* errado! */
{
int temp;
temp = i; i = j; j = temp;
}
não produz o efeito desejado, pois recebe apenas os valores das variáveis e não as variáveis propriamente ditas. A função recebe "cópias" das variáveis e troca os valores dessas cópias, 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 *p, int *q)
{
int temp;
temp = *p; *p = *q; *q = temp;
}
Para aplicar a função às variáveis i e j basta dizer
troca( &i, &j);
ou ainda
int *p, *q; p = &i; q = &j; troca( p, q);
void troca( int *i, int *j) {
int *temp;
*temp = *i; *i = *j; *j = *temp;
}
Os elementos de qualquer vetor (= array) têm endereços consecutivos na memória do computador. [Na verdade, os endereços não são consecutivos, pois cada elemento do vetor pode ocupar vários bytes. Mas o compilador C acerta os detalhes internos de modo a criar a ilusão de que a diferença entre os endereços de elementos consecutivos vale 1.] Por exemplo, depois da declaração
int *v; v = malloc( 100 * sizeof (int));
o ponteiro v aponta o primeiro elemento de um vetor de 100 elementos. O endereço do segundo elemento do vetor é v+1 e o endereço do terceiro elemento é v+2. Se i é uma variável do tipo int então
v + i
é o endereço do (i+1)-ésimo elemento do vetor. A propósito, as expressões v + i e &v[i] têm exatamente o mesmo valor e portanto as atribuições
*(v+i) = 87; v[i] = 87;
têm o mesmo efeito. Analogamente, qualquer dos fragmentos de código abaixo pode ser usado para "carregar" o vetor v:
for (i = 0; i < 100; ++i) scanf( "%d", &v[i]); for (i = 0; i < 100; ++i) scanf( "%d", v + i);
Todas essas considerações também valem se o vetor for alocado pela declaração
int v[100];
mas nesse caso v é uma espécie de "ponteiro constante", cujo valor não pode ser alterado.
int v[100];
Descreva, em português, a sequência de operações que deve ser executada para calcular o valor da expressão
&v[k + 9];
char *a, *b; a = "abacate"; b = "uva"; if (a < b) printf( "%s vem antes de %s no dicionário", a, b); else printf( "%s vem depois de %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]];