Professional Documents
Culture Documents
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:
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.
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.
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.}
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.
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.
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.
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.}
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.