CURSO DE C

Ponteiros e Matrizes


Veremos nestas seções que ponteiros e matrizes têm uma ligação muito forte.

 

Matrizes como ponteiros

Vamos dar agora uma idéia de como o C trata matrizes.

 Quando você declara uma matriz da seguinte forma:

 tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN];

 o compilador C calcula o tamanho, em bytes, necessário para armazenar esta matriz. Este tamanho é:

 tam1 x tam2 x tam3 x ... x tamN x tamanho_do_tipo

 O compilador então aloca este número de bytes em um espaço livre de memória. O nome da variável que você declarou é na verdade um ponteiro para o tipo da variável da matriz. Este conceito é fundamental. Eis porque: Tendo alocado na memória o espaço para a matriz, ele toma o nome da variável (que é um ponteiro) e aponta para o primeiro elemento da matriz.

 Mas aí surge a pergunta: então como é que podemos usar a seguinte notação?

 nome_da_variável[índice]

 Isto pode ser facilmente explicado desde que você entenda que a notação acima é absolutamente equivalente a se fazer:

 *(nome_da_variável+índice)

 Agora podemos entender como é que funciona uma matriz! Vamos ver o que podemos tirar de informação deste fato. Fica claro, por exemplo, porque é que, no C, a indexação começa com zero. É porque, ao pegarmos o valor do primeiro elemento de uma matriz, queremos, de fato, *nome_da_variável e então devemos ter um índice igual a zero. Então sabemos que:

 *nome_da_variável é equivalente a nome_da_variável[0]

 Outra coisa: apesar de, na maioria dos casos, não fazer muito sentido, poderíamos ter índices negativos. Estaríamos pegando posições de memória antes da matriz. Isto explica também porque o C não verifica a validade dos índices. Ele não sabe o tamanho da matriz. Ele apenas aloca a memória, ajusta o ponteiro do nome da matriz para o início da mesma e, quando você usa os índices, encontra os elementos requisitados.

 Vamos ver agora um dos usos mais importantes dos ponteiros: a varredura sequencial de uma matriz. Quando temos que varrer todos os elementos de uma matriz de uma forma sequencial, podemos usar um ponteiro, o qual vamos incrementando. Qual a vantagem? Considere o seguinte programa para zerar uma matriz:

 

main ()
{
float matrx [50][50];
int i,j;
for (i=0;i<50;i++)
        for (j=0;j<50;j++)
                matrx[i][j]=0.0;
}
Podemos reescrevê-lo de uma maneira muito mais eficiente como sendo:

 

main ()
{
float matrx [50][50];
float *p;
int count;
p=matrx[0];
for (count=0;count<2500;count++)
        {
        *p=0.0;
        p++;
        }
}
Você consegue ver porque é que o segundo programa é mais eficiente? Simplesmente porque cada vez que se faz matrx[i][j] o programa tem que calcular o deslocamento para dar ao ponteiro. Ou seja, o programa tem que calcular 2500 deslocamentos. No segundo programa o único cálculo que deve ser feito é o de um incremento de ponteiro. Fazer 2500 incrementos em um ponteiro é muito mais rápido que calcular 2500 deslocamentos completos.

 Há uma diferença entre o nome de um vetor e um ponteiro que deve ser frisada: um ponteiro é uma variável, mas o nome de um vetor não é uma variável. Isto significa, que nã se consegue alterar o endereço que é apontado pelo "nome do vetor". Seja:

     int vetor[10];
     int *ponteiro, i;
     ponteiro = &i;

/* as operacoes a seguir sao invalidas */

     vetor = vetor + 2;     /* ERRADO: vetor nao e' variavel */
     vetor++;               /* ERRADO: vetor nao e' variavel */
     vetor = ponteiro;      /* ERRADO: vetor nao e' variavel */

Teste as operações acima no seu compilador. Ele dará uma mensagem de erro. Alguns compiladores dirão que vetor não é um Lvalue. Lvalue, significa "Left value", um símbolo que pode ser colocado do lado esquerdo de uma expressão de atribuição, isto é, uma variável. Outros dirão que tem-se "incompatible types in assignment", tipos incompatíveis em uma atribuição.

/* as operacoes abaixo sao validas */

     ponteiro = vetor;      /* CERTO: ponteiro e' variavel */
     ponteiro = vetor+2;    /* CERTO: ponteiro e' variavel */


 O que você aprendeu nesta seção é de suma importância. Não siga adiante antes de entendê- la bem.

 

Ponteiros como matrizes

Sabemos agora que, na verdade, o nome de uma matriz é um ponteiro constante. Sabemos também que podemos indexar o nome de uma matriz. Como consequência podemos também indexar um ponteiro qualquer. O programa mostrado a seguir funciona perfeitamente:

 

#include <stdio.h>
main ()
{
int matrx [10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *p;
p=matrx;
printf ("O terceiro elemento da matriz e: %d",p[2]);
}
Podemos ver que p[2] equivale a *(p+2).

 

Strings

Seguindo o raciocínio acima, nomes de strings, são do tipo char*. Isto nos permite escever a nossa função StrCpy(), que funcionará como a função strcpy() da biblioteca:

 

#include <stdio.h>
#include <string.h>
StrCpy (char *destino,char *origem)
{
while (*origem)
        {
        *destino=*origem;
        origem++;
        destino++;
        }
*destino='\0';
}
main ()
{
char str1[100],str2[100],str3[100];
printf ("Entre com uma string: ");
gets (str1);
StrCpy (str2,str1);
StrCpy (str3,"Voce digitou a string ");
printf ("\n\n%s%s",str3,str2);
}
Há vários pontos a destacar no programa acima. Observe que podemos passar ponteiros como argumentos de funções. Na verdade é assim que funções como gets() e strcpy() funcionam. Passando o ponteiro você possibilita à função alterar o conteúdo das strings. Você já estava passando os ponteiros e não sabia. No comando while (*origem) estamos usando o fato de que a string termina com '\0' como critério de parada. Quando fazemos origem++ e destino++ o leitor poderia argumentar que estamos alterando o valor do ponteiro-base da string, contradizendo o que recomendei que se deveria fazer, no final de uma seção anterior. O que o leitor talvez não saiba ainda (e que será estudado em detalhe mais adiante) é que, no C, são passados para as funções cópias dos argumentos. Desta maneira, quando alteramos o ponteiro origem na função StrCpy() o ponteiro str2 permanece inalterado na função main().

 

Endereços de elementos de matrizes

Nesta seção vamos apenas ressaltar que a notação

 &nome_da_variável[índice]

 é válida e retorna o endereço do ponto da matriz indexado por índice. É interessante notar que, como consequência, o ponteiro nome_da_variável tem o endereço &nome_da_variável[0], que indica onde na memória está guardado o valor do primeiro elemento da matriz.

 

Matrizes de ponteiros

Podemos fazer matrizes de ponteiros como fazemos matrizes de qualquer outro tipo. Uma declaração de uma matriz de ponteiros inteiros poderia ser:

 

                int *pmatrx [10];
No caso acima, pmatrx é um vetor que armazena 10 ponteiros para inteiros. Como existe uma certa equivalência entre vetores e ponteiros, podemos dizer que pmatrx é um ponteiro para ponteiro.

 


AUTO AVALIAÇÃO

Veja como você está.

Fizemos a função StrCpy(). Faça uma função StrLen() e StrCat() que funcionem como as funções strlen() e strcat() de string.h respectivamente


Página Anterior  Índice da Aula  Próxima Página

Dúvidas? De uma olhadinha em nosso FAQ

Perguntas para a lista do Curso de C