quarta-feira, 2 de dezembro de 2009

Tá apontando o quê? (ponteiros)

Além dos tipos básicos e dos tipos estruturados (que vimos aqui e comentamos alguma coisa aqui), o C possui um tipo de dados especial: o tipo ponteiro.

A função principal do ponteiro é ... APONTAR (não é brincadeira!).
Vamos ver como isso funciona.

Eu sei onde você está!
Podemos imaginar um ponteiro como uma posição na memória onde está armazenado algum valor que pode ser alterado. Então um ponteiro é uma variável?

SIM e NÃO (rsrs).

A diferença entre um ponteiro e uma variável está no tipo de valores que eles podem armazenar. Enquanto uma variável pode armazenar caracteres, números inteiros e reais; um ponteiro armazena endereços de memória.
No post em que falamos sobre vetores (aqui), vimos a relação entre as variáveis e a sua posição na memória. Observe a figura abaixo (que é a última daquele post).



Se você clicou no link e viu o post sobre vetores, irá reparar que a numeração abaixo dos 'quadrinhos' está diferente. Por quê?

É diferente... mas é a mesma coisa!
Enquanto no referido post os números abaixo dos 'quadrinhos' indicavam os índices do vetor, neste se referem aos endereços de memória.
Um ponteiro que apontasse para o 3o elemento deste vetor estaria armazenando o valor 52.
Repare que os valores de endereço aumentam (ou diminuem) de 4 em 4. Isto acontece porque, neste exemplo, são necessários 4 bytes para armazenar um valor inteiro. Portanto, o ponteiro de que falamos acima é um ponteiro para inteiros. Se fosse um vetor de caracteres, onde cada caracter é armazenado em 1 byte, precisaríamos de um ponteiro para caracteres.

E qual é a vantagem de usar um ponteiro?
Antes de ver um ponteiro em ação (rsrs), vamos ver a sua sintaxe.

 int *pint;

O comando acima declara uma variável do tipo ponteiro para inteiros com o nome pint. Se quisermos apontar para um float o comando seria:

 float* pfl

Não é necessário que o nome do ponteiro comece com p (embora seja uma boa prática), nem que tenha o tipo da variável. Só deve ter o tipo e o *, que pode estar tanto junto ao nome como ao tipo.

Uma das vantagens no uso de um ponteiro está no fato de que além de apontar para o endereço de uma variável, também é possível ter acesso ao conteúdo desta variável usando o ponteiro. A esta operação se dá o nome de dereferenciar um ponteiro.

Como sempre, vamos ver isso na prática.


//EXEMPLO DO USO DE UM PONTEIRO
#include <stdio.h>
#include <stdlib.h>
 
int main(){
 int* pnum;
 int num = 10;
 
 pnum = &num;
 printf("O valor inicial de num eh %d.\n",*pnum);
 *pnum += 2;
 printf("O valor atual de num eh %d.\n",num);
 num--;
 printf("O valor final de num eh %d.\n",*pnum);
 system("pause");
}


O código acima é muito simples, mas mostra alguns fundamentos muito importantes. Vamos entender o que ele faz.
Na linha 6 definimos uma variável ponteiro para inteiros chamada de pnum. Na linha 7 definimos uma variável inteira num e a inicializamos com o valor 10.
Um ponteiro armazena endereços de memória. E é isso que fazemos na linha 9. Atribuimos o endereço da variável inteira num ao ponteiro para inteiros pnum.
Observe que a variável é do mesmo tipo do ponteiro. Se não fosse esta atribuição seria NÃO poderia ser feita.
Na linha 10 exibimos o valor de num dereferenciando o ponteiro pnum.
Além de acesso ao conteúdo da variável, também podemos alterá-lo usando o ponteiro, o que é mostrado na linha 11 e comprovado na linha 12. Acredito que agora você já saiba o que fazem as outras linhas (rsrs).
Você deve estar se perguntando o por quê de usar um ponteiro ao invés da própria variável. Isto vai se tornar claro quando falarmos sobre modularidade, que é a possibilidade de dividir um programa em várias partes, sendo que as variáveis de cada parte estão 'escondidas' das outras partes.

Outro uso muito comum de ponteiros é como uma forma alternativa de acessar elementos de um vetor. Esse acesso é feito através da aritmética de ponteiros.

Vetor x Ponteiro
Os dados armazenados numa variável do tipo ponteiro são endereços de memória (quantas vezes eu já repeti isso???). Esses endereços são sempre valores numéricos. E com esses valores podemos fazer algumas operações aritméticas (SOMENTE SOMA E SUBTRAÇÃO).
Observe este dois comandos aplicados a um ponteiro para inteiro pnum:

 (*pnum)++;
 pnum++;

O 1o comando incrementa o conteúdo da posição de memória apontada por pnum, ou seja, se na posição apontada por pnum estiver um inteiro com o valor 11, após este comando o valor deste inteiro passa a ser 12.
Já o 2o comando incrementa o valor armazenado em pnum. Como pnum é um ponteiro para inteiros, que estamos supondo necessitarem de 4 bytes para serem armazenados na memória, ao valor de pnum será somado 4.
Por exemplo, digamos que no código exemplo que usamos acima o endereço da variável inteira num fosse 38. Este valor foi atribuido ao ponteiro para inteiros pnum. Então o conteúdo de pnum é 38. Ao executar este 2o comando este conteúdo passa a ser 42. Ou seja, pnum não estará mais apontando para num, e passará a apontar para uma posição de memória na qual não sabemos o que está armazenado. Isto é extremamente perigoso, pois torna possível alterar algo que não deveria ser alterado.
Se para uma única variável isto é perigoso, para um vetor é um ótimo recurso, afinal um vetor é um grupo de variáveis do mesmo tipo armazenadas em sequência na memória.
Observe como ficaria o 2o exemplo do post sobre vetores utilizando aritmética de ponteiros.


//PROGRAMA QUE RECEBE 100 NÚMEROS INTEIROS
#include <stdio.h>
#include <stdlib.h>
 
int main(){
 int vetor[100], i;
 int* pvet;
 
 pvet = vetor;
 printf("Digite 100 numeros inteiros:\n");
 for(i=0; i < 100; i++){
    printf ("Valor %d: ",i+1);
    scanf("%d", pvet++);
 }
 system("pause");
}


Na linha 6 declaramos um vetor de 100 inteiros e uma variável inteira i que será usada para dar ao usuário uma noção de quantos valores ele já digitou. Na linha 7 definimos uma variável ponteiro para inteiros chamada pvet. Na linha 9 atribuimos o endereço do vetor ao ponteiro pvet (Numa atribuição, o nome de um vetor corresponde ao seu endereço). Na linha 13, dentro do for, é feita a leitura do valor digitado pelo usuário para a posição de memória apontada por pvet, que da 1a vez é a 1a posição do vetor, e depois o conteúdo de pvet é incrementado, passando a apontar para a próxima posição do vetor.

Viu como não é tão difícil quanto parece?

3 comentários:

  1. Edson jr, lhe enviei um email sobre o Link Malloc, perguntando sobre os prototipos de função e enviando tbm um codigo em Pascal.

    Gostaria se houver disponibilidade sua que vc nao me desse o edificio pronto mas informações de como converter codigo em Pascal para o C.

    Para lhe poupar trabalho vou lhe mostrar os pontos em C nos quais tenho duvida.

    1. Os registros em Pascal é equivalente aos Struct's em C ?

    2. Como atrelar Registros(struct's) a matrizes em C ?

    3. Se não me engano o comando de limpar tela de console em C possui o mesmo nome que em Pascal é 'clrscr', é isso mesmo ?
    Senão, qual biblioteca devo declarar na include do C pra usar tal limpador de tela ?

    4. Em Pascal vc acessa as posições e os valores de matrizes de maneira simples demais; em C me parece nao ser tao simples assim, pois já tentei acessar e me retornava nao os valores das matrizes mas seus endereços.
    Ao se usar funcões em C indubitavelmente eu tenho que usar Ponteiros, ou só nos casos cujos parametros passados forem matrizes ?
    Pelo que q eu entendi em seu site, usa-se ponteiros justamente para encontrar sua respectiva variavel e assim encontrar seu valor e seu indice(posição), isso mesmo ?

    - Se fiz perguntas desnecessárias cujas respostas já estao no site, nao é necessario resposta, só coloque o LInk para lhe poupar trabalho.

    daniel m.waite - programador jr.

    ResponderExcluir
  2. Daniel,
    Mais uma vez agradeço o seu interesse pelo blog.
    Apesar de ter pedido na resposta que dei ao seu email, para que vc fazer suas perguntas nos comentários, essas eu vou responder via email.
    Estou tentando seguir uma ordem cronologica no blog. Ainda faltam alguns tópicos até chegar em structs.

    ResponderExcluir
  3. Oi Edson!
    Parabens pelo BLOG, tds os topicos estão mt bem explicados. Fui passeando pelo BLOG e acabei com um monte de duvidas simples que ngm conseguia me explicar, e finalmente começar a por na cabeça a logica dos ponteiros pq agora soh falta eu conseguir resolver sozinha os exercicios!
    Obrigada
    :*

    ResponderExcluir