paulo1205
(usa Ubuntu)
Enviado em 29/05/2014 - 15:34h
Sempre que o assunto é ponteiros, eu fico com vontade de escrever uma aula. Só que falta tempo (além de organização mental para me expressar com a devida clareza e se modo sucinto).
Eu acho que sempre ajuda se você pensar no operador unário
* como "o conteúdo da posição de memória dada por". Você pode ler por extenso dessa maneira tanto na hora da declaração quanto na hora da utilização. Veja:
- a declaração
int **ppi; significa, literalmente, que “o conteúdo da posição de memória dada pelo conteúdo da posição de memória dada por
ppi é do tipo
int ” (desde que as respectivas "posições de memória dadas por" sejam endereços válidos no momento em que o programa for executado);
- a expressão
*(*ppi+n) significa “o conteúdo da posição de memória dada por (o conteúdo da posição de memória dada por
ppi mais
n )”.
Você muitas vezes pode escolher se coloca o foco no ponteiro ou no objeto apontado. Numa declaração como
int *pi; ,
*pi (ou “o conteúdo da posição de memória dada por
pi ”) é do tipo
int , e isso é equivalente a dizer que
pi (sem o asterisco) é do tipo “ponteiro para
int ”. Num caso como o da declaração
int **ppi; ,
**ppi é do tipo
int , o tipo de
*ppi é “ponteiro para
int ” e o de
ppi é “ponteiro para ponteiro para
int ”.
Parando mais detidamente sobre a expressão “
*(*ppi+n) ", acho que ajuda a técnica de "dividir para conquistar". Veja:
- pelas regras de precedência de operadores, “
*(*ppi+n) ” é equivalente a “
*((*ppi)+n) ” (lê-se “o conteúdo de posição de memória dada por ( (o conteúdo da posição de memória dada por
ppi ) mais
n )”;
- “
*ppi ”, que é a parte de maior precedência da expressão, devolve um endereço (ponteiro) para um valor do tipo
int ;
- ao se somar o valor
n ao ponteiro para
int obtido acima, obtém-se um novo endereço, que é igual ao endereço anterior mais
n vezes o tamanho ocupado por um valor do tipo
int (em outras palavras, obtém-se o endereço do
(n+1) -ésimo valor inteiro a partir da posição de memória dada por
*ppi );
- ao se aplicar o operador
* sobre o endereço obtido, recebe-se o valor do tipo
int armazenado nesse endereço.
---
O seu programa usa um ponteiro para ponteiro de modo artificioso, que, na prática, raramente é necessário. Você faz
p_Pnum=&p_num; , e depois só usa
p_Pnum na forma
*p_Pnum ; como
p_Pnum=&p_num ,
*p_Pnum=p_num (perceba a identidade comparando o segundo e o terceiro
loops de impressão).
---
Uma das razões (e talvez a mais importante) pelas quais ponteiros são importantes em C é para que o valor de uma variável possa ser alterado dentro de uma função e que a alteração seja visível e permanente para quem chamou a função.
O motivo para isso é que C aplica o que se chama de "chamada por valor" ("
call by value "), o que significa que, quando uma função é chamada, ela recebe uma
cópia dos valores dos argumentos. Caso tenha a pretensão de fazer com que a função manipule diretamente um valor que lhe é externo, a função deve receber não o valor ser manipulado, mas o endereço (na verdade uma cópia do endereço) desse objeto, para que, ao aplicar o operador
* sobre o endereço recebido, consiga chegar ao objeto original.
O exemplo clássico é a função
swap (), que troca os valores de dois argumentos.
void swap(int *a, int *b){
int c=*a;
*a=*b;
*b=c;
}
int main(void){
int i=0, j=1;
swap(&i, &j); /* Passa (cópias de) os endereços de i e de j. */
return i; /* Programa termina com status 1 (i==1). */
}
Seguindo por essa linha, ponteiros de ponteiros (e ponteiros de ponteiros de ponteiros) costumam aparecer com mais frequência quando uma função tem de manipular o valor de um objeto que já, do lado de fora, um ponteiro. Um exemplo é a função da biblioteca padrão
strtol (). O protótipo dela é
long strtol(const char *str, char **endptr, int base)
onde
str indica a string a ser convertida,
base indica a base de representação do número, e o "exótico"
endptr é um ponteiro para ponteiro, porque prevê que uma variável do tipo “ponteiro para
char ” será passada por referência (explícita, por meio do operador
& ), a fim de ser modificada dentro da função, para que aponte para o primeiro caráter dentro da string que não faz parte da representação numérica.
O programa abaixo mostra dois casos de uso de ponteiro para ponteiro: no segundo argumento da chamada a
strtol (), aplicando-se o operador
& a uma variável que já ponteiro, e a usar um ponteiro para percorrer um array de ponteiros.
#include <stdlib.h>
#include <stdio.h>
const char *strings[]={
"12345",
"987xxx321",
"",
"0",
" ",
"\t -54321 \t",
NULL
};
int main(void){
const char **p_number;
char *eon; /* end of number -- ponteiro para um caráter. */
long n;
for(p_number=strings; *p_number!=NULL; p_number++){
n=strtol(*p_number, &eon, 10);
if(**p_number){ /* primeiro caráter de *p_number não é nulo. */
if(!*eon) /* caráter contido no endereço eon é nulo. */
printf(
"A conversão de \"%s\" para inteiro consumiu todos os "
"caracteres e obteve o valor %ld.\n", *p_number, n
);
else
printf(
"A conversão de \"%s\" para inteiro parou após encontrar "
"o caráter inesperado '%c'. O valor da conversão parcial "
"é %ld.\n", *p_number, *eon, n
);
}
else
printf(
"A conversão de \"%s\" para inteiro não foi realizada porque "
"trata-se de uma string vazia. Se usado, o valor de n seria "
"%ld.\n", *p_number, n
);
}
return 0;
}