Relação entre arrays e apontadores

Na linguagem C, os arrays e os apontadores estão intimamente ligados. Qualquer operação que é feita fazendo referência ao índice de um array, também pode ser feita através de apontadores. A declaração,

     int a[5];

define um array de 5 inteiros, um bloco consecutivo de memória que permite guardar a[0], a[1], a[2], a[3], a[4].

Como vimos na aula anterior, o nome do array é sinónimo do endereço do primeiro elemento do array. Assim sendo, se fizermos,

     int *pa;
     pa = a;     /* também podíamos ter feito pa = &a[0] */

pa vai ficar a apontar para o primeiro elemento do array a.

Se agora fizermos,

     x = *pa;   

vamos atribuir a x o valor que é apontado por pa, ou seja, a[0].

Se pa aponta para um determinado elemento de um array, pa+1 aponta para o elemento seguinte. No caso geral, pa+i aponta para o elemento que está i posições a seguir. Esta regra é válida qualquer que seja o tipo de elementos do array. Resumindo, se pa apontar para o inicio do array a,

     *pa     é sinónimo de a[0]
     *(pa+1) é sinónimo de a[1]
     *(pa+2) é sinónimo de a[2]
     ...
     *(pa+i) é sinónimo de a[i]

Programa de ordenação outra vez

Vamos voltar a escrever o programa de ordenação de uma lista de n números de um modo mais estruturado. Em vez de estar tudo feito no main, vamos fazer várias funções, cada qual com a sua tarefa bem determinada.

O segredo da boa programação está em conseguir dividir um problema complexo em sub-problemas menos complexos. Depois, se resolvermos os sub-problemas ficamos com o problema original resolvido. Reparem que cada sub-problema por sua vez pode ser decomposto em sub-sub-problemas e por aí fora. Vamos então ao código.

    #include <stdio.h>
    #include <stdlib.h>
    
    /* imprime uma mensagem de erro e termina o programa */
    void erro( char *mensagem )
    {
      printf("ERRO: %s\n", mensagem );
      exit(1);
    }
    
    /* troca o conteúdo das variáveis apontadas por x e y */
    void troca( int *x, int *y )
    {
      int temp;
      
      temp = *x;
      *x = *y;
      *y = temp;
    }
    
    /* Pede ao utilizador para introduzir 'n' elementos de um array */
    void introduzir_array( int *a, int n )
    {
      int i;
      
      for (i=0; i<n; i++)
       {
         printf("%d.º numero -> ",i+1);
         scanf("%d",&a[i]);
       }
    }
    
    /* escreve os 'n' elementos de um array no ecrã */
    void escrever_array( int *a, int n )
    {
      int i;
      
      for( i=0; i<n; i++ )
        printf("%d ", a[i]);
      printf("\n");
    }
    
    /* Ordena um array de 'n' elementos inteiros por ordem crescente. */
    void ordenar_array( int *a, int n )
    {
      int i, k, m, min;
    
      for( k=0; k<=n-1; k++ )
       {
         /* descobre o índice do mínimo em a[k], a[k+1], ..., a[n-1] */
         min = a[k];
         m = k;
         for( i=k; i<=n-1; i++ )
           if( a[i] < min )
            {
              min = a[i];
              m = i;
            }
         /* troca a[k] com a[m] */
         troca( &a[k], &a[m] );
       }
    }

    main()
    {
      int *a;
      int n;
          
      printf("Indique o tamanho do array\n");
      scanf("%d",&n);
    
      a = malloc( n * sizeof(int) );   /* alocar memória */
      if( a == NULL )
        erro("nao ha memoria.");
    
      introduzir_array( a, n );
      ordenar_array( a, n );
      escrever_array( a, n );
    
      free(a);  /* libertar memória */
    }

Reparem como o main ficou muito mais simples. Além disso, torna-se mais fácil reutilizar partes do programa. Devem sempre tentar escrever funções genéricas de modo a que possam ser reutilizadas noutros programas.

Repara no modo como os arrays são passados nas funções. Aquilo que é passado é o endereço do primeiro elemento do array. Quando chamamos a função ordenar_array,

      ordenar_array( a, n );
também poderíamos ter escrito,
      ordenar_array( &a[0], n );

No que diz respeito à definição da função, declaramos o array como sendo um apontador,

      void ordenar_array( int *a, int n )
      {
         ...
      }

esta declaração é equivalente a,

      void ordenar_array( int a[], int n )
      {
         ...
      }

Utilizem aquela que gostarem mais. Ambas são equivalentes. De facto, quando se declara,

      int a[];

estamos a dizer que a é um apontador para um inteiro.

Reparem também na função troca. Como queremos trocar o conteúdo de a[k] com a[m], temos de passar o endereço das variáveis. No código escrevemos,

      troca( &a[k], &a[m] );

mas também podíamos ter escrito,

      troca( a+k, a+m );