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