You are on page 1of 11

Ponteiros

Ponteiros so as mais poderosas e tambm as mais controversas ferramentas da linguagem C e C++. Ponteiros foram abolidos de linguagens novas baseadas em C, como por exemplo Java. Mas, um programa que utiliza ponteiros sabiamente tem um desempenho muito maior, pois ponteiros trabalham diretamente com a memria. Dessa forma, praticamente, podemos manipular toda a quantia de memria do computador, mesmo que seja pouca ou muita. Ponteiros como o prprio nome diz, quem aponta. Sua nica funo, mas de extrema importncia, guardar o endereo de memria de uma varivel. Quando declaramos uma varivel e executamos o programa, o sistema operacional se encarrega de alocar (reservar) o espao necessrio para o tipo de dado e "marca" aquele espao de memria com um nome, que o nome da varivel. O papel do ponteiro guardar exatamente este endereo. Como ponteiros guardam endereos de memria, podemos guardar o endereo de qualquer parte da memria, mesmo que este espao no tenha sido reservado no incio da execuo do programa. Podemos declarar ponteiros ou usar referenciamento e de refernciamento. Ponteiros so declarados como se fossem variveis, mas contendo um indicador (que um asterisco). Por exemplo, se formos criar dois ponteiros, sendo um para um endereo de memria contendo um dado float e outro contendo um int, faramos:

float *ponteiroFloat int *ponteiroInt


Pronto, agora j temos nossos ponteiros criados. Agora, vamos ver realmente como eles funcionam.

Referncia
Referncia quando nos referimos diretamente ao identificador do endereo da memria. A memria na verdade uma grande tabela com clulas sequenciais, cada clula tem seu prprio endereo que segue um padro contnuo. Ou seja, a primeira clula ser 0x00000000, a segunda 0x00000001, a terceira 0x00000002, e assim por diante. Quando fazemos referncia, estamos obtendo exatamente este valor, que o endereo da clula na memria. A referncia dada pelo operador &. Ento, vamos imaginar que temos uma varivel inteira chamada var1 que vale 100. Iremos criar um ponteiro chamado ptr1 que ir obter o endereo de memria onde est var1.

#include <iostream> #include <cstdlib> using namespace std; int main (){ int var1 = 100; int *ptr1; ptr1 = &var1; cout << ptr1 << endl; system ("pause"); }
Perceba que aqui ns atribumos a ptr1 o endereo de memria de var1 (&var1) e no o valor contido em var1. Portanto, o valor de ptr1 no ser 100, mas um endereo de memria 0x "algum hexadecimal". Pois impossvel saber qual ser o endereo de uma varivel antes da execuo do programa.

De referncia
De referncia quando nos referimos ao valor contido no endereo armazenado, ou seja, o contrrio da operao de referncia. A de referncia busca o valor que est no endereo gravado no ponteiro, isso quer dizer que o valor obtido no est no ponteiro, mas no endereo que ele aponta. importante entender isso

porque quando alteramos o valor referenciado pelo ponteiro, na verdade estamos alterando o valor da varivel original e no do ponteiro. A de referncia dada pelo operador *. Ainda usando o exemplo da varivel var1.

#include <iostream> #include <cstdlib> using namespace std; int main (){ int var1 = 100; int *ptr1; ptr1 = &var1; cout << "O valor contido no endereco de memoria "; cout << ptr1 <<" e "<< *ptr1 << endl; system ("pause"); }
Portanto, resumidamente, ponteiros se utilizam de referncia e de referncia para acessar a memria, sendo a referncia (&) o endereo de memria e a de referncia (*) o valor contido no endereo de memria.

Erros Comuns ao Utilizar Ponteiros


Um dos problemas que levaram a extino do uso de ponteiros em linguagens mais recentes baseadas em C so os erros de lgica cometidos por programadores (inclusive experientes) que muitas vezes levava o programa ao crash ou erro fatal. A seguir, esto alguns problemas comuns que lidamos ao trabalharmos com ponteiros. Ponteiros devem sempre apontar para algum endereo de memria. Portanto, todo ponteiro deve ser inicializado antes de ser utilizado. Ex.: int *ptr1; cout << ptr1;. Ponteiros no guardam valores, apenas endereos de memria, ou seja, se atribumos um valor ao ponteiro, esse valor ser um novo endereo de memria. Ex.: ptr1 = &var1; ptr1 = 100; Ns mudamos o endereo de memria que ptr1 est apontando, agora ele no aponta mais para o endereo de var1, e sim, para o endereo 100. Mas, o que tem no endereo 0x100 ? Esse um erro muito comum. Inicializar um ponteiro com o valor de uma de referncia. Ex.: int *ptr1 = *var1;. Esses so alguns dos erros mais comuns, embora praticamente todos os compiladores so capazes de impedir que eles ocorram.

Ponteiro de Struct
Vimos a pouco como criar uma estrutura de dados agrupado (struct), como definir um nome essa estrutura com typedef e como criar um ponteiro para indicar um endereo de memria. Agora, vamos nos aprofundar um pouco mais nesse assunto vendo como procedemos com um ponteiro de struct. Como vimos anteriormente, um struct consiste em vrios dados agrupados em apenas um. Para acessarmos cada um desses dados, usamos um ponto (.) para indicar que o nome seguinte o nome do membro. Um ponteiro guarda o endereo de memria que pode ser acessado diretamente. O problema aqui est no seguinte, como acessaremos um membro de uma estrutura de dados usando um ponteiro? Pois simples. Para isso, basta usarmos o que chamamos de "seta". A "seta" consiste de um sinal de menos e um maior (->). Portanto, podemos criar nosso struct do mesmo jeito de sempre e nosso ponteiro tambm. Mas, quando formos acessar um membro dessa estrutura usando um ponteiro ns no usaremos um ponto, mas uma seta.

Vejamos: Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.#include <cstdlib> 03.using namespace std; 04. 05.typedef struct data Data; 06. 07.struct data { 08. short dia; 09. short mes; 10. int ano; 11.}; 12. 13.int main (void){ 14. Data data; //varivel data do tipo struct data 15. Data *hoje; //ponteiro hoje para um tipo struct data 16. hoje = &data; //hoje aponta para o endereo de data 17. 18. //dados sendo inseridos na varivel data 19. hoje->dia = 20; 20. hoje->mes = 1; 21. hoje->ano = 2009; 22. 23. //mostrando o que est gravado no endereo contido em hoje 24. cout << "Data registrada:"<<endl; 25. cout << hoje->dia <<"/"<< hoje->mes <<"/"<< hoje->ano << endl; 26. system ("pause"); 27.}
Agora, h mais uma maneira de acessarmos um membro da estrutura usando um ponteiro. Esta outra forma consiste em indicar de qual ponteiro nos referimos colocando o de refernciador entre parenteses, assim (*hoje). Dessa forma podemos acessar diretamente usando um ponto (.). No exemplo abaixo usamos apenas ponteiros com de refernciador para escrever no struct data. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.#include <cstdlib> 03.using namespace std; 04. 05.typedef struct data Data; 06. 07.struct data { 08. short dia; 09. short mes; 10. int ano; 11.}; 12. 13.int main (void){

14. Data data; //varivel data do tipo struct data 15. Data *hoje; //ponteiro hoje para um tipo struct data 16. hoje = &data; //hoje aponta para o endereo de data 17. 18. //dados sendo inseridos na varivel data 19. (*hoje).dia = 20; 20. (*hoje).mes = 1; 21. (*hoje).ano = 2009; 22. 23. //mostrando o que est gravado no endereo contido em hoje 24. cout << "Data registrada:"<<endl; 25. cout << (*hoje).dia <<"/"<< (*hoje).mes <<"/"<< (*hoje).ano << endl; 26. system ("pause"); 27.}
Concluindo, podemos acessar um membro de um tipo de dado estrutura de dado usando ponteiro de duas formas: Usando um de refernciador entre parnteses e um ponto (.) para indicar o membro. Usando o prprio ponteiro e uma seta (->) para indicar o membro.

Ponteiros de vetor
Como j visto at agora, ponteiros so variveis que guardam o endereo de memria de outra varivel. Veremos agora, como se aplica este recurso em vetores. Recapitulando o que so vetores: variveis de endereo continuo na memria. Ou seja, enquanto uma varivel do tipo int ocupa, por exemplo, um bloco de memria, um vetor de 5 posies do tipo int ocupa 5 blocos de memria. Para acessarmos cada item de um vetor usamos os ndices que ficam entre colchetes ([]). H duas formas de trabalhar com ponteiros para vetor. 1. Usar ndices no ponteiro como se ele fosse um vetor: ponteiro = vetor; ponteiro[indice1] = qualquer valor para ser gravado nesse ndice; ponteiro[indiceN] = qualquer valor para ser gravado nesse ndice; Preste ateno no seguinte detalhe. Apesar de ser ponteiro, ao atribuirmos um valor ao ndice no usamos o sinal de de referncia (*). Visualizar Codigo Fonte Imprimir?

01.#include <stdio.h> 02.#include <stdlib.h> 03. 04.int main (void){ 05. int vetor [2]; 06. int *v; // ponteiro 07. v = vetor; 08. v[0] = 123; 09. v[1] = 456; 10. printf ("vetor[0] = %d\n", vetor[0]); 11. printf ("vetor[1] = %d\n\n", vetor[1]); 12. system ("pause");

13.}
2. Usar o que chamamos de aritmtica de ponteiros. Aritmtica de ponteiros consiste em modificar o valor do ponteiro para ele indicar o prximo endereo de memria do vetor. Exemplificando, seria algo como: ponteiro = endereo do ndice 0 do vetor; *(ponteiro+indice1) = qualquer valor para ser gravado nesse ndice; *(ponteiro+indiceN) = qualquer valor para ser gravado nesse ndice; Perceba que aqui atribuimos um valor ao vetor usando um de refernciador do ponteiro (*). Quando somamos um nmero ao ponteiro, o que estamos fazendo , na verdade, somar o nmero necessrio de bytes para o prximo endereo. Ex.: Se tivermos um ponteiro para um vetor de inteiro, quando formos calcular o terceiro espao faremos *(ponteiro+3). Internamente ser calculado o seguinte -> ponteiro + 3 o tamanho de int (4 bytes). Ento, 3 4 = 12 bytes. ponteiro = 0x00001100; ponteiro + 12 bytes; novo ponteiro = 0x00001100C; Apesar de ser uma conta simples, no precisamos nos preocupar com isso. Porque o prprio sistema cuida de executar este clculo. Visualizar Codigo Fonte Imprimir?

01.#include <stdio.h> 02.#include <stdlib.h> 03. 04.#define MAX 10 05. 06.int main (void){ 07. int vetor [MAX], i, valor, *v; 08. v = &vetor[0]; 09. printf ("Digite um valor para ser gravado no\n"); 10. printf ("indice\tEndereco de Memoria\n"); 11. for (i=0; i<MAX; i++) { 12. printf ("[%d]\t%p\t\t-> ", i, (v+i)); 13. scanf ("%d", &valor); 14. getchar(); 15. *(v+i) = valor; //valor gravado no endereo apontado pelo ponteiro 16. } 17. system ("cls"); 18. printf ("Os valores gravados no vetor foram:\n"); 19. for (i=0; i<MAX; i++) { 20. printf("vetor[%d], ponteiro (%p) = %d\n", i, (v+i), vetor[i]); 21. } 22. system ("pause"); 23.}

Ponteiros como Argumento de Funes


Vimos at agora, qual a importncia dos ponteiros, como utiliz-los e principalmente qual sua sintaxe.

Agora, comearemos a tratar de ponteiros em tpicos mais avanados, comeando com o uso de ponteiros como argumento de funes. Primeiramente, iremos entender qual a vantagem de usarmos ponteiros como argumentos. At agora, apenas havamos utilizado argumentos de valores. Os argumentos de valores so cpias dos valores dos dados passados a funo em sua chamada. Exemplo: Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int mostrarNumero (int nr) { 05. cout << nr << endl; 06.} 07. 08.int main (void) { 09. int n = 10; 10. mostrarNumero(n); 11. system("pause"); 12.}
No exemplo acima, quando passamos a varivel n para a funo mostrarNumero o valor de n copiado para nr. Ou seja, o valor que est sendo manipulado dentro da funo no o valor original, mas uma cpia. Isso nos impossibilita de acessar a varivel diretamente, fazendo com que muitas vezes tenhamos que forar nossa funo retornar algum valor. O problema est quando criamos uma funo que afeta diretamente vrias partes da memria. Neste caso, no possvel apenas retornarmos um valor para ser tratado novamente dentro do corpo do programa. Abaixo esta um exemplo muito simples, mas que simplesmente no atende a nossas expectativas. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int trocar (int a, int b) { 05. int aux; 06. aux = a; 07. a = b; 08. b = aux; 09.} 10. 11.int main (void) { 12. int var1 = 10, var2 = 50; 13. trocar (var1, var2); 14. cout << "O valor de var1 e " << var1 << endl; 15. cout << "O valor de var2 e " << var2 << endl; 16. system("pause"); 17.}

A idia da funo acima passar o valor de uma varivel para outra. Mas, ao executarmos este cdigo, percebemos que ao final os valores de var1 e var2 continuam os mesmos. Por qu? Simples. O que foi trocado dentro da funo trocar foi a cpia dos valores de var1 e var2 que so a e b dentro da funo. Ou seja, o que foi alterado na verdade foi o valor de a que passou para b e b que passou para a. A troca aconteceu, mas no com as variveis que queramos. Mas, se ao invs de utilizarmos o valor da varivel, utilizssemos sua referncia de memria, ou seja, o seu ponteiro. Dessa forma, a funo saberia que o valor a ser tratado j existe e est exatamente no local da memria que indicarmos. Portanto, o valor que seria tratado dentro da funo no seria uma cpia, mas a varivel original. Ento, vamos reformular a funo do exemplo anterior usando a referncia dos ponteiros (endereos contidos nos ponteiros) como argumentos dessa vez. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int trocar (int &a, int &b) { 05. int aux; 06. aux = a; 07. a = b; 08. b = aux; 09.} 10. 11.int main (void) { 12. int var1 = 10, var2 = 50; 13. trocar (var1, var2); 14. cout << "O valor de var1 e " << var1 << endl; 15. cout << "O valor de var2 e " << var2 << endl; 16. system("pause"); 17.}
Neste caso, usamos o endereo como forma de manipulao, e ento, conseguimos trocar os valores de var1 e var2, pois a funo no copiou os valores dessas variveis, ele manipulou diretamente seus endereos de memria. Aqui, podemos destacar mais uma vatangem que foi a economia de memria. Pois, no foi criado mais um espao de memria, j que ns utilizamos diretamente o endereo de memria j alocado anteriormente por var1 e var2.

Ponteiros para Ponteiros


C/C++ nos permite criar, tambm, ponteiros com nveis de apontamento. O que isso quer dizer? Simples. possvel criar um ponteiro que aponte para outro ponteiro, criando assim nveis, pois um ponteiro poder apontar para outro ponteiro, que, por sua vez, aponta para outro ponteiro, que aponta para um ponteiro diferente e assim por diante. A sintaxe para se criar ponteiros para ponteiros a mesma de ponteiros comuns, apenas a forma de acessar seus valores diferente, pois para cada nvel de apontamento usaremos um de refernciador. Por exemplo, se um ponteiro aponta para um ponteiro que aponta para um valor inteiro, para acessarmos esse valor inteiro a partir do primeiro ponteiro usaramos dois asterscos (**), ao invs de apenas um (*). Isso se d ao fato que um ponteiro pode mudar o valor de outro ponteiro de acordo com seu nvel. O exemplo abaixo demonstra uma sintaxe simples. Visualizar Codigo Fonte

Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int main (void) { 05. int a=10, b=50; 06. int *ptr; // ponteiro de inteiro 07. int **ptrPtr; // ponteiro de um ponteiro inteiro 08. ptr = &a; 09. ptrPtr = &ptr; 10. cout << "O valor final de ptrPtr e " << **ptrPtr << endl; 11. system("pause"); 12. return EXIT_SUCCESS; 13.}
O cdigo acima contem duas variveis inteiras (a e b) inicializados com os valores 10 e 50 respectivamente. Logo aps, h uma varivel de ponteiro (*ptr) e uma varivel de ponteiro para ponteiro (**ptrPtr). Ento, atribuimos o endereo da varivel inteira a em *ptr. Agora *prt aponta para a. Aps isso, atribuimos o endereo do ponteiro *ptr ao ponteiro **ptrPtr. Neste momento **ptrPtr passa apontar para *ptr. Nesse instante devemos prestar atenco no seguinte detalhe, no momento da impresso da mensagem "O valor final de ptrPtr e "... usamos o ponteiro do ponteiro para chegar at o valor de a.

Entendendo os Nveis de Apontamento


Quando usamos ponteiros de ponteiros podemos usar os nveis de apontamento que so determinados pelo nmero de asteriscos que precedem o ponteiro. Portanto, se um ponteiro aponta para um ponteiro que tambm aponta para um ponteiro que contem um endereo de uma varivel char, usaramos neste caso trs asterscos. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int main (void) { 05. char letra='a'; 06. char *ptrChar; 07. char **ptrPtrChar; 08. char ***ptrPtr; 09. ptrChar = &letra; 10. ptrPtrChar = &ptrChar; 11. ptrPtr = &ptrPtrChar; 12. cout << "O valor final de ptrPtr e " << ***ptrPtr << endl; 13. system("pause"); 14. return EXIT_SUCCESS; 15.}
Para entender melhor o acesso indireto que o ponteiro executa, vejamos o diagrama abaixo.

Este diagrama demonstra o que cada um dos asteriscos representa. Sabendo disso possvel alterar tanto o valor final da varivel quanto o endereo apontado pelos ponteiros intermedirios. Vejamos a exemplificao abaixo. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int main (void) { 05. float var1 = 25.5; 06. float var2 = 72.8; 07. float *ptr; 08. float **ptrPtr; 09. ptr = &var1; 10. ptrPtr = &ptr; 11. cout << "O valor final de ptrPtr e " << **ptrPtr << endl; 12. cout << "Pois o endereco de ptr e " << ptr << endl; 13. *ptrPtr = &var2; //novo endereo no ponteiro intermedirio 14. cout << "\nO valor final de ptrPtr e " << **ptrPtr << endl; 15. cout << "Pois o endereco de ptr e " << ptr << endl; 16. system("pause"); 17. return EXIT_SUCCESS; 18.}
No cdigo acima podemos perceber a troca de um ponteiro intermedirio. A princpio atribuimos o endereo da varivel var1 ao ponteiro ptr. Depois, atribuimos o endereo do ponteiro ptr ao ponteiro ptrPtr. Na primeira vez que mandamos imprimir **ptrPtr (Importante! Note os dois asteriscos) ser apresentado o valor contido em var1, porque ptrPtr acessa var1 indiretamente pelo ponteiro ptr. Quando fazemos a operao *ptrPtr = &var2;, na verdade, no estamos alterando o valor contido em var1, mas o endereo contido no ponteiro ptr, porque usamos apenas um asterisco em ptrPtr. Ou seja, usando apenas um asterisco acessaremos o ponteiro, no o valor apontado pelo ponteiro. Vejamos:

Esse astersco representa exatamente o ponteiro intermedirio. Portanto, agora o ponteiro ptr no aponta mais para var1, ele aponta para var2. Na segunda vez que imprimimos o valor de **ptrPtr no aparecer o valor de var1, mas o valor de var2, porque o endereo contido no ponteiro intermedirio ptr foi alterado para apontar para var2.

Ponteiros Nulos e Vazios


At o momento, temos estudado vrios tipos de ponteiros que apontam para outros lugares e assim por diante. Mas, C e C++ possuem outras formas de ponteiros, e, no obrigatoriamente, um ponteiro tem que apontar para algum lugar, pois um ponteiro simplesmente pode apontar para lugar nenhum.

Null Pointers (Ponteiros Nulos)


Nem sempre um ponteiro deve apontar para algum lugar, principalmente se este lugar ainda no existir. Ento, possvel um ponteiro ser nulo. Para um ponteiro apontar para lugar algum devemos lhe atribuir o valor inteiro 0, ou a constante NULL. No importa se o ponteiro for do tipo float, int, char, etc., pois automaticamente haver um typecasting para o tipo de dado correto. importante entender este conceito de ponteiro nulo porque facilita o trabalho com estruturas de memria dinmicas como listas encadeadas, filas, arvores binrias, etc. Pois, na maior parte do tempo existir um ponteiro, mas no um endereo fsico j criado na memria. Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int main (void) { 05. int *nullPointer; 06. nullPointer = NULL; 07. cout << nullPointer << endl; 08. system ("pause"); 09. return EXIT_SUCCESS; 10.}

Void Pointers (Ponteiros Vazios)


Chamamos de ponteiros vazios qualquer ponteiro que no possua um tipo de dado especfico, ou seja, se seu tipo de dado for void. Ponteiros vazios ou void pointers so ponteiros genricos que podem apontar para qualquer outro tipo de dado, mas trazem consigo problemas lgicos. Tais problemas residem no fato de que como so ponteiros vazios - no h um tipo de dado especfico - ento, no podemos determinar seu tamanho, assim como tambm no podemos dereferenci-lo diretamente. Esses problemas so facilmente resolvidos se induzirmos o endereo void para o endereo do tipo de dado correto, da mesma forma que executamos qualquer typecast. Entender o conceito de void pointers (ponteiros vazios) de suma importncia para a compreenso de estruturas dinmicas de memria, porque trabalharemos o tempo todo com funes que retornam endereos de memria genricos (que podem ser qualquer tipo de endereo de memria, portanto sem tipo de dado - void). Visualizar Codigo Fonte Imprimir?

01.#include <iostream> 02.using namespace std; 03. 04.int main (void) { 05. void *ptr; 06. int *ptrVar1, var1 = 10; 07. float *ptrVar2, var2 = 50.123; 08. ptr = &var1; // ponteiro aponta para um inteiro

09. ptrVar1 = (int*) ptr; // typecasting: ponteiro genrico p/ inteiro 10. cout << *ptrVar1 << endl; 11. ptr = &var2; // o mesmo ponteiro aponta agora para um float 12. ptrVar2 = (float*) ptr; // typecasting: ponteiro genrico p/ float 13. cout << *ptrVar2 << endl; 14. system ("pause"); 15. return EXIT_SUCCESS; 16.}
Como demonstrado no exemplo acima, ponteiros vazios podem apontar para qualquer tipo de dado (inteiro, float, char, etc), porm no podem ser de refernciados diretamente. Para contornar este problema, usamos typecast para que um ponteiro concreto possa ser de refernciado no lugar do ponteiro genrico.

You might also like