Apontadores; Programação com apontadores.

Arquitetura de memória

Arquitetura de memória

endereço conteúdo
0 10101010
1 11111010
\(\vdots\) \(\vdots\)
65535 11010010

Arquitetura de memória

endereço conteúdo
0x0000 10101010
0x0001 11111010
\(\vdots\) \(\vdots\)
0xffff 11010010

Variáveis

Exemplo

Operador &

  int main(void) {
     int i, j;
     printf("endereço i: %p\n", &i);
     printf("endereço j: %p\n", &j);
  }

Exemplo:

endereço i: 0x7ffe26476b20
endereço j: 0x7ffe26476b24

(Neste computador cada int ocupa 4 bytes.)

Apontadores

Declarar apontadores

   int *p;   /* `p` é um apontador para
                 valores de tipo `int` */
   char  *p;  // apontador para carateres
   double *p; // apontador para doubles
   long  *p;  // apontador para longs

Inicializar apontadores

   int *p;  // não inicializado

Inicializar apontadores (cont.)

   int i;
   int *p;
   ...
   p = &i;

Operador *

   int i, *p;
   i = 42;
   p = &i;
   printf("%d\n", *p); // imprime 42

Operador *

   int i, *p;
   p = &i;
   i = 42; 
   *p = 43; 
   printf("%d\n", i); // imprime 43

Exemplo passo-a-passo

p = &i;

i = 42;

*p = 43;

Operador *

   int *p;
   printf("%d\n", *p);   /* ERRO */
   int *p;
   *p = 1;               /* ERRO */

Atribuição de apontadores

   int i;
   int *p, *q;
   p = &i;
   q = p;

Atribuição de apontadores

   *p = 1;

   *q = 2;

Atribuição de apontadores

Atribuição de apontadores

p = &i; q = &j; i = 1;

*q = *p;

Apontadores como argumentos

Exemplo: trocar duas variáveis

/* Trocar duas variáveis; versão errada */
void trocar(int a, int b) {
    int temp;
    temp = a;
    a = b;
    b = temp;
}

Versão usando apontadores

void trocar(int *pa, int *pb) {
   int temp;
   temp = *pa;
   *pa = *pb;
   *pb = temp;
}
   int a, b;
   ...
   trocar(&a, &b);  // trocar `a' com `b'

Programação com apontadores

Introdução

Apontadores para elementos

   int a[10], *p;
   p = &a[0];

Modificar um elemento

   *p = 21;

Aritmética de apontadores

Aritmética de apontadores

Somar um inteiro

p = &a[2];

q = p + 3;

Subtrair um inteiro

p = &a[2];

q = p - 2;

Processamento de vetores

Processamento de vetores

int soma_vec(int vec[], int n) {
  int *p, soma;
  soma = 0;
  for(p = &vec[0]; n>0; n--) {
    soma += *p;
    p++;
  }
  return soma;
}

Apontadores e variáveis indexadas

   int a[10];
   *a = 7;      // coloca 7 em a[0] 
   *(a+1) = 12; // coloca 12 em a[1]


Em geral:

a+i é equivalente a &a[i]
a[i] é equivalente a *(a+i)

Apontadores e variáveis indexadas (cont.)

Podemos re-escrever a inicialização do ciclo

  for(p = &vec[0]; n>0; n--) {
   ...
  }

como:

  for(p = vec; n>0; n--) {
    ...
  }

Argumentos de funções

  int soma_vec(int vec[], int n) {
     ...
  }
  ...
  int a[N];
  ...
  s = soma_vec(a, N);  

Consequências

Exemplo 1

   int soma_vec(int *vec, int n) {
     ...
   }
   int soma_vec(int vec[], int n) {
     ...
   }

Exemplo 2

   int a[100];
   ...
   s = soma_vec(&a[30], 10);
      // somar a[30], a[31], ... a[39]
   s = soma_vec(a+30, 10);

Retornar apontadores

   int *max(int *pa, int *pb) {
      if(*pa > *pb)
         return pa;
      else
         return pb;  
   } 

Retornar apontadores (cont.)

   int *p, i, j;
   ...
   p = max(&i, &j);

Cuidados ao retornar apontadores

char *f( ... ) {
   char tmp[MAX];
   ...
   return &tmp[...]; // ERRO
}

Apontador nulo

Exemplo

Definir uma função

   char *find_alpha(char *str);

Exemplo (cont.)

#include <stdlib.h>
#include <ctype.h>

char *find_alpha(char *str) {
   while(*str != '\0' && !isalpha(*str)) {
      str ++;
   }
   if (*str != '\0')
      return str;  // encontrou
   else
      return NULL; // não encontrou
}

Apontador nulo

   char texto[100], *ptr;
   ...
   ptr = find_alpha(texto);
   if(ptr != NULL) 
     printf("Primeira letra: %c\n", *ptr);
   else 
     printf("Não existem letras!\n"); 

Apontador nulo (cont.)

int *p = NULL;

printf("%d", *p); // ERRO

*p = 42;          // ERRO

Processamento de cadeias

Processamento de cadeias

Vamos re-implementar algumas funções sobre cadeias usando apontadores:

Comprimento (1)

unsigned comprimento(char *str) {
  unsigned len = 0;
  char *ptr = str;
  while(*ptr != '\0') {
     len ++;
     ptr ++;
  }
  return len;
}

Comprimento (2)

Versão mais “idiomática”: pós-incremento na condição do ciclo.

unsigned comprimento(char *str) {
  unsigned len = 0;
  char *ptr = str;
  while(*ptr++ != '\0')
      len++;
  return len;
}

Comprimento (3)

Não é necessário a variável auxiliar ptr: podemos usar o argumento da função directamente.

unsigned comprimento(char *str) {
  unsigned len = 0;
  while(*str++ != '\0')
      len++;
  return len;
}

Comprimento (4)

unsigned comprimento(char *str) {
  char *ptr = str;
  while(*ptr++ != '\0');
  return ptr-str;
}

Copiar (1)

void copiar(char *dest, char *origem) {
   while(*origem != '\0') {
      *dest = *origem;
      dest ++;
      origem ++;
   }
   *dest = '\0';  // colocar terminador
}

Copiar (2)

Combinando a atribuição com o pós-incremento de apontadores.

void copiar(char *dest, char *origem) {
   while(*origem != '\0') 
      *dest++ = *origem++;
   *dest = '\0';  // colocar terminador
}

Concatenar

void concat(char *dest, char *origem) {
    // advançar até final do destino
   dest += comprimento(dest);
   // copia a origem
   while(*origem != '\0') 
      *dest++ = *origem++;
   *dest = '\0';
}

Comparar cadeias

Resultado 1 se as cadeias são iguais e 0 se são diferentes.

int comparar(char *str1, char *str2) {
  while(*str1 != '\0' && *str1 == *str2) {
    str1++;
    str2++;
  }
  return (*str1 == *str2);
}