Projeto de Algoritmos

Ponteiros

Antes de entender o que é um ponteiro (veja Pointer na Wikipedia),  é preciso entender o conceito de endereço.

Endereços

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.

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 a;
   scanf ("%d", &a);

Ponteiros

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.


diagrama de um ponteiro              diagrama de um ponteiro
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;

Exemplos

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;

Mais um exemplo

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);

Exercícios

  1. Por que o código abaixo está errado?
    void troca (int *x, int *y) {
       int *temp;
       *temp = *x; *x = *y; *y = *temp;
    }
    

  2. Um ponteiro pode ser usado para dizer a uma função onde ela deve depositar o resultado de seus cálculos. Escreva uma função xxx que converta minutos em horas-e-minutos. A função recebe um inteiro mnts e os endereços de duas variáveis inteiras, digamos h e m, e atribui valores a essas variáveis de modo que m seja menor que 60 e que 60*h + m seja igual a mnts.   Escreva também uma função main que use a função xxx.

  3. Escreva uma função yyy que receba um vetor inteiro v[0..n-1] e os endereços de duas variáveis inteiras, digamos min e max, e deposite nessas variáveis o valor de um elemento mínimo e o valor de um elemento máximo do vetor.   Escreva também uma função main que use a função yyy.

Vetores e endereços

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.

Exercícios

  1. Suponha que os elementos do vetor v são do tipo int e cada int ocupa 8 bytes no seu computador. Se o endereço de v[0] é 55000, qual o valor da expressão  v + 3?

  2. Suponha que v é um vetor declarado assim:
       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];
    

  3. Suponha que v é um vetor. Descreva a diferença conceitual entre as expressões   v[3]   e   v + 3.

  4. O que há de errado com o seguinte trecho de código?
    char *a; char *b;
    a = "cenoura";
    b = "cereja";
    if (a < b)
       printf ("%s precede %s no dicionário", a, b);
    

  5. Diga (sem usar o computador) qual o conteúdo do vetor a depois dos seguintes comandos.
    int a[99];
    for (i = 0; i < 99; ++i) a[i] = 98 - i;
    for (i = 0; i < 99; ++i) a[i] = a[a[i]];
    

 


URL of this site: www.ime.usp.br/~pf/algoritmos/
1998 | Last modified: Wed Aug 30 12:01:18 BRT 2006
Paulo Feofiloff
IME-USP