Ir para conteúdo

Ponteiros C/C++


Furabio

Posts Recomendados

Estou estudando C/C++, e esse tutorial sobre ponteiros me tirou várias dúvidas e pensei em compartilhar ele com vocês.
Todo os créditos ficam a C0d3r__.

Audiência

Você tem dificuldades entendendo ponteiros? Tem problemas no uso de ponteiros? Até mesmo não tem menor idéia do que é um ponteiro? Se a resposta for SIM para uma das perguntas anteriores, então esse tutorial é para você, também se vc tiver uma idéia legal sobre ponteiros, quem sabe esse tutorial não pode aumentar o seu nàvel de conhecimento, então continue a lêr.

Requerimentos

Interesse em aprender, e um pouco de conhecimento sobre C ou C++ desenvolvido ou não. É necessario lêr as sessões do comeco ao fim sem pular nenhuma sessão pq o conhecimento no assunto de ponteiros sera construido sequencialmente, quero dizer uma sessão irí lhe dar o alicerce para a proxima sessão.

O que são ponteiros

Ponteiros são variaveis, isso mesmo um ponteiro é uma variavel, mas vc pode se perguntar: "oq é uma variavel?", a resposta seria, uma variavel é um tipo de dados que é armazenado na memoria do computador em um determinado endereço. Onde tipos de dados são como os inteiros, caracteres, números fracionarios, e o endereço é um número que representa a posiçao na memoria onde se encontra a variavel.

exemplo:

int MeuInt;

Como vc pode observar acima na declaracao de uma variavel em C, o int é o tipo da variavel que é inteiros, e o MeuInt é chamado de label, ou nome da variavel, onde internamente é associado ao nome da variavel o seu determinado endereço na memoria, "mas oq eu faco com o endereço e como obtenho?" com o endereco vc consegue referenciar a uma variavel, e pra se obter o endereco na programacao em C existe o operador &, que retorna o endereço na memoria de uma variavel. "Operador, que isso?" "Quem vai operar quem?" um operador da linguaguem C ou C++ é uma palavra-chave, que faz uma operacao então a operacao que o operador & faz é simplesmente retornar o endereço de uma variavel, vejamos:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
int MeuInteiro = 0; /* Declara um inteiro */ 

/* 
Usa o %p ou specificador de formato p para mostrar o endereço em hexadecimal 
da variavel que estara na lista de argumento de printf() onde temos &MeuInteiro 
*/ 

printf("%#p", &MeuInteiro); 

/* Pausa */ 
system("pause"); 
return 0; 
} 


Compilando o codigo acima em um compilador de C ou C++ vc vera que o programa irí mostrar na tela o endereço da variavel MeuInteiro, onde o operador & foi usado para se obter o endereço.

também se vc não sabe, números hexadecimais são números de base 16, a base 16 são 16 possiveis números e letras para um digito, os números são: de 0 a 9, e de A a F as letras. Se vc contar da 16, então o valor 0xABCD e um número hexadecimal, veja que cada digito, 4 digitos "ABCD" tem uma letra. também no exemplo acima foi usado o modificador de formato # antes de p que e para dizer a printf() que coloque o prefixo 0x defrente do número hexadecimal, pq 0x representa números hexadecimais.

Então logo depois que revisamos oq é uma variavel, e aprendemos que todas variaveis tem um endereco que e geralmente representado em hexadecimal, posso lhe dizer que um ponteiro é uma variavel que contém o endereço de uma outra variavel na memoria.

Da mesma forma que variaveis normais contem um valor, digamos:

int Inteiro = 10;

E dado a variavel Inteiro o valor 10.

Ponteiros também tem um valor, que e um endereço na memoria de outra variavel.

Também como toda variavel tem um endereço, o ponteiro também tem um endereço de localizacao na memoria.

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
int MeuInteiro = 0; /* Declara um inteiro */ 
int *MeuPonteiro = &MeuInteiro; /* Declara um ponteiro para um inteiro que esta recebendo o endereço de um inteiro */ 
/* Acessando o conteudo de um ponteiro, ou seja 5 e copiado para MeuInteiro */ 
*MeuPonteiro = 5; 

/* Mostra o valor de MeuInteiro */ 
printf("MeuInteiro = %d\n", MeuInteiro); 

/* pausa */ 
system("pause"); 
return 0; 
} 

Como vc pode observar acima e usado o operador * para se acessar oq o ponteiro esta apontando, ou seja: na variavel que MeuPonteiro aponta copie o valor 5, então e mostrado o valor da variavel MeuInteiro que foi acessada apartir de MeuPonteiro.

Pra vc ver que ponteiros também possuem seu endereço na memoria compile o codigo fonte abaixo:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
int MeuInteiro = 0; /* Declara um inteiro */ 
int *MeuPonteiro = &MeuInteiro; /* Declara um ponteiro para um inteiro que esta recebendo o endereço de um inteiro */ 

/* Acessando o conteudo de um ponteiro, ou seja 5 e copiado para MeuInteiro */ 
*MeuPonteiro = 5; 

/* Mostra o endereço do ponteiro */ 
printf("endereço do ponteiro = %#p\n", &MeuPonteiro); 

/* O que o ponteiro tem (Um endereço pra um inteiro) */ 
printf("MeuPonteiro = %#p\n", MeuPonteiro); 

/* Mostre MeuInteiro atravez do ponteiro */ 
printf("MeuInteiro = %d\n", *MeuPonteiro); 

/* pausa */ 
system("pause"); 
return 0; 
} 

Como vc pode observar com o operador * acessamos oq o ponteiro esta apontando, no caso acima acessamos a variavel MeuInteiro, onde usando o operador de copia = e copiado um valor atravez do ponteiro para a variavel, e também usando o *MeuPonteiro como na linha:

printf("MeuInteiro = %d\n", *MeuPonteiro);

e mostrado oq MeuInteiro possui atravez de MeuPonteiro.

Vale apena salientar que se o operador * não e usado em um ponteiro o valor do ponteiro e acessado que e obvio um endereço, vejamos:

printf("MeuPonteiro = %#p\n", MeuPonteiro);

E mostrado o endereço de MeuInteiro que esta dentro da variavel MeuPonteiro.

também aplicando o operador & ao ponteiro pegamos o seu endereço de localizacao na memoria, vejamos:

printf("endereço do ponteiro = %#p\n", &MeuPonteiro);

você Sabia: desde que ponteiros tem enderecos, e possivel se criar um ponteiro para um ponteiro, ou uma variavel que possui o endereço de outro ponteiro.

Para que servem os ponteiros

Os ponteiros servem para adicionar flexibilidade na programacao, onde apartir de um ponteiro e possivel se acessar variaveis pra lêr oq as mesmas contem ou pra se modificar oq as mesmas contem.

? Ponteiros são usados com memoria dinamica, com arrays, com variaveis, ou com estruturas. Onde um ponteiro pode conter um determinado endereço de um valor dentro de um grupo de memoria, apartir dai usando aritimetica de ponteiros, e possivel se andar por absolutamente todos os bytes de um grupo de memoria, indepedente do tipo de dados. Funcoes de manipulacao de string abusam do uso de parametros que são ponteiros, simplesmente pq e impossivel fazer uma manipulacao de uma variavel de dentro de uma funcao e modificar a variavel se ela foi passada como parametro sem que um ponteiro pra variavel seja usado; Veremos mais detalhes sobre isso na sessão de funcoes.

? Ponteiros são usados com funcoes de varias formas: funcoes que tem parametros que são ponteiros, funcoes que retornam um ponteiro, ponteiro para funcoes.


Todas essas formas de ponteiros seram analizadas nas sessões a seguir.


Ponteiros e memoria dinamica

Memoria dinamica e toda memoria alocada dinamicamente ou seja na hora de execucao do programa de acordo com algum evento que satisfaca a necessidade de variacao, exemplo:

Em um programa de redes, cliente/servidor, um cliente pode requisitar(pedir) informacoes ao servidor usando bytes, o mesmo que dados, que são chamados de cabecalhos, então imagine a seguinte situacao: um cliente envia um cabecalho que define o tamanho da requisicao de dados que irí seguir ou seja o tamanho dos dados, o tamanho que e enviado inicialmente pelo cliente seria uma variavel inteiro, que ira dizer ao servidor a quantidade de bytes o servidor deve alocar para se guardar o requisito de informacao, onde essa quantidade de bytes varia. O servidor quando recebe esse tamanho que pode ser variado, aloca memoria no lado do servidor de acordo com esse tamanho de uma forma dinamica ao invez de constante como seria no caso de uma declaracao de uma variavel.

Onde o ponteiro entra em acao? O ponteiro e utilizado pra apontar para o inicio da memoria recem alocada; Funcoes como malloc(), calloc(), realloc(), são funcoes da livraria padrao da linguaguem C que retornam ponteiros.
também os operadores new e delete em C++ são exemplos que alocam memoria e retornam um ponteiro.

Vejamos um exemplo em C:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 

/* Declara um ponteiro pra um inteiro */ 
int *p = NULL; 

/* Usa malloc pra alocar memoria */ 
p = malloc(sizeof(int)); 

/* Abaixo se p == NULL memoria não foi alocada */ 
/* Pq 0 se torna 1 com o ! entao o bloco do if e executado */ 
if (!p) 
{ 
fputs("Erro: sem memoria disponivel!", stderr); 
return 1; 
} 

/* Acesse memoria dinamica */ 
*p = 1234; 

/* Mostre memoria dinamica */ 
printf("%i\n", *p); 

system("pause"); 
return 0; 
} 

Compile o codigo acima, e vera como e facil alocar memoria dinamica e usar o ponteiro pra essa memoria.
Observe que no codigo acima p e inicializado para NULL, onde NULL e um define dentro de stdlib.h que esta dessa forma:

#define NULL (void *)0

O NULL e o mesmo que 0 internamente, o casting (void *) siguinifica que 0 pode ser copiado para qualquer tipo de ponteiro usando o operador de atribuicao =.

E importante SEMPRE inicializar os nossos ponteiros para NULL e depois checar depois da rotina de alocacao de memoria se o ponteiro aponta pra algo diferente de NULL, senao problemas graves em projetos podem ocorrer onde o programa apenas e encerrado pelo sistema operacional pq o mesmo tentou manipular memoria protegida, lembra dos Read access violation, Write access violation, isso, esses 2 erros podem ser evitados inicializando os ponteiros para NULL, e sempre que vc for manipular os ponteiros verifique antes, se o mesmo realmente aponta pra memoria valida, memoria diferente de NULL e liberada para leitura/escrita pelo sistema.


Ponteiros e arrays

O que e uma array? Uma array e um grupo de variaveis em sequencia, exemplo:

char MinhaArray[50];

A declaracao acima aloca uma array chamada MinhaArray de tamanho 50, em outras palavras um grupo de 50 chars e alocado.

Existe um relacionamento entre ponteiros e arrays, pelo simples fato, sabemos que um ponteiro e uma variavel que aponta pra outra variavel, ja uma array e um grupo de variaveis no qual o NOME da array "MinhaArray" aponta pro primeiro item ou elemento da array. vejamos:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
/* Grupo de 50 chars */ 
char MinhaArray[50]; 
/* 
Desde que MinhaArray aponta pro primeiro elemento 
podemos usar o operador * e acessar o primeiro elemento 
*/ 
*MinhaArray = 10; 

/* Agora conferimos */ 
printf("Primeiro elemento = %i\n", *MinhaArray); 

/* Comprovamos */ 
printf("MinhaArray aponta pra = %#X\n", MinhaArray); 

system("pause"); 
return 0; 
} 

não confunda! apesar de existir um relacionamento com ponteiros o nome da array não e um ponteiro, porem por ser associado o nome ao endereço do primeiro elemento, onde o nome e simplesmente um label linhas como:

*MinhaArray = 10;

Se tornam possivel, por que * funciona da mesma forma com nomes de array como com nomes de ponteiros.
No entanto linhas como:

*MinhaArray++;

ou

*++MinhaArray;

não são permitidos, por que MinhaArray não possui um espaco unico na memoria, então:

MinhaArray += sizeof(TIPO_DE_OBJETO);

ou

MinhaArray = MinhaArray + sizeof(TIPO_DE_OBJETO);

Onde tipo de objeto pode ser qualquer tipo suportado, não são permitidos; Observe que desde que MinhaArray não possui o seu espaco na memoria não e possivel assinar a mesma valores.


O operador ++ faz a operacao de incrementacao por 1, então:

x++;

e o mesmo que:

x = x + 1;

e

++x;

também e:

x = x +1;

Logo desde que não podemos salvar a posiçao de um determinado elemento dentro do nome da array, usamos ponteiros, logo observe:

char MinhaArray[50];
char *MeuPonteiro;

MeuPonteiro = MinhaArray;

E dado ao MeuPonteiro o endereço do primeiro elemento da array MinhaArray. Declarando um ponteiro temos um espaco na memoria, onde podemos manter o endereço de um determinado byte, no caso de um char, dentro da array, dessa forma podemos andar ou pecorrer os items da array; Vejamos:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
/* O grupo de bytes */ 
char MinhaArray[50]; 
/* Coloca o endereço do primeiro elemento da array dentro de MeuPonteiro */ 
char *MeuPonteiro = MinhaArray; 
/* 
Mantem a posiçao atual e evita que ultrapassemos 
o tamanho da array que e 50. 
*/ 
int Count = 0; 
/* 
Esse loop irí pecorrer a array de chars de 0 a 49 que são 50 elementos e 
irí copiar o valor atual de Count + 1 dentro de um determinado elemento usando 
ponteiro. 
*/ 
while (Count < 50) 
{ 
*(MeuPonteiro + Count) = Count + 1; 
Count++; 
} 

/* Reseta Count */ 
Count = 0; 
/* 
O loop abaixo irí pecorrer a array usando o ponteiro e irí mostrar na tela 
oq a array contem. 
*/ 
while (Count < 50) 
{ 
printf("%d ", *(MeuPonteiro + Count)); 
Count++; 
} 


system("pause"); 
return 0; 
} 

A linha abaixo faz o seguinte:

*(MeuPonteiro + Count) = Count + 1;

O valor de MeuPonteiro que e um endereço e obtido depois e somado com Count, e depois o operador * irí permitir que acessemos esse endereço, onde e copiado usando o operador =, Count + 1.

A linha acima poderia ser substituida por:

*MeuPonteiro++ = Count + 1;

E o ponteiro seria resetado assim:

MeuPonteiro = MeuPonteiro - 50;

Onde MeuPonteiro++ e o mesmo que: MeuPonteiro = MeuPonteiro + 1; dessa forma pecorremos os bytes da array incremetando o endereço do primeiro byte que faz com que o ponteiro aponte pro segundo byte mas note que MeuPonteiro depois da expressao irí apontar para o endereço + 1, por que MeuPonteiro++ e o mesmo que MeuPonteiro = MeuPonteiro + 1, observe o operador =, por isso e necessario resetar o ponteiro, se vc deseja passar pelos bytes iniciais, porque em outras palavras o operador ++ modifica o seu operando; Um exemplo mais simples segue:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
/* Grupo de bytes inicializado, e terminado por 0 */ 
char MinhaString[] = "Ola mundo!"; 

/* Coloca o endereço do primeiro elemento da array em MeuPonteiro */ 
char *MeuPonteiro = MinhaString; 

/* Enquanto o byte apontado por MeuPonteiro não e 0 */ 
while (*MeuPonteiro != '\0') 
{ 
/* Mostre o determinado byte */ 
putchar(*MeuPonteiro); 
/* Depois que mostrou o byte aponte pro proximo */ 
MeuPonteiro++; 
} 
/* Nova linha */ 
putchar('\n'); 
system("pause"); 
return 0; 
} 

Compile o exemplo acima e vera que uma string e mostrada na tela usando ponteiros.
Se vc não esta lembrado uma string e uma array de characteres terminada por um 0. Quando vc cria uma string:
char MinhaString[] = "Ola mundo!"; o 0 e colocado depois do sinal de !. Observe também que na linha:

MeuPonteiro++

Acontece:

MuPonteiro = MeuPonteiro + sizeof(TIPO_DE_OBJETO);

Dessa forma apontamos pro proximo byte da string.

Vale apena salientar que MeuPonteiro++ pode variar de acordo com o tipo de ponteiro, se for um ponteiro para character então e somado sizeof(char) ao endereço, se for um ponteiro pra um inteiro e somado o tamanho do inteiro, sizeof(int), se for um ponteiro para um float e somado sizeof(float), e ser for um ponteiro para um double e somado sizeof(double).

Utilizando operadores aritimeticos como +, -, com ponteiros e chamado de aritimetica com ponteiros que de acordo com o tipo de ponteiro tem um efeito diferente, observe o exemplo abaixo:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
/* Declaracao de uma array de inteiros Smile */ 
int MinhaArrayDeInteiros[50]; 

/* Nosso ponteiro apontando pro inicio da array */ 
int *MeuPonteiroPraInteiros = MinhaArrayDeInteiros; 

/* Mostra oq o ponteiro contem */ 
printf("endereço que ponteiro contem = %#X\n", MeuPonteiroPraInteiros); 

/* 
Aqui incremente o endereço de MeuPonteiroPraInteiros por 
sizeof(int) ou seja o endereço do ponteiro inicial e somando 
por sizeof(int), e não por 1, pq não estamos lidando com 1 byte; 
Assim apontamos pro proximo inteiro, e acessamos o mesmo colocando 
10 nele. 
*/ 
*++MeuPonteiroPraInteiros = 10; 

/* Mostra oq o ponteiro no momento contem */ 
printf("Agora endereço que ponteiro contem = %#X\n", MeuPonteiroPraInteiros); 

/* Mostra a diferenca do endereço final em relacao ao endereço inicial */ 
printf("Diferenca = %d\n", (int)MeuPonteiroPraInteiros - (int)MinhaArrayDeInteiros); 

/* Usa indexacao [] para se acessa minha array que foi modificada apartir do ponteiro*/ 
printf("%d\n", MinhaArrayDeInteiros[1]); 

system("pause"); 
return 0; 
} 

sizeof() se vc estiver esquecido retorna o tamanho de um tipo de dado.

E possivel tambem se ter arrays de ponteiros ou seja uma array onde todos os seus elementos sao ponteiros, vejamos:

char *MinhaArraydePonteiros[10];

Na declaracao acima uma array de 10 ponteiros pra characteres e criada, ou seja uma array de 10 elementos que sao enderecos de memoria.

O uso de uma array de ponteiro e bem eficaz e dinamico, muitas vezes precisamos de mais de 1 ponteiro em nossos programas para apontar para variaveis que muitas vezes nao sabemos oq seram ou quantas seram, observemos o caso de um suposto programa editor de texto; Sabemos que a cada caractere que o usuario tecla esse caractere deve ser gravado em um buffer ou em uma alocacao de memoria, entao digamos que implementamos uma funcao que se obtenha uma sequencia de caracteres, quando o usuario pressiona enter sabemos que temos uma linha, em um programa de texto haveram varias linhas, entao entra-se em questao, onde guardar essas linhas, e como se ter controle de todas essas linhas, se a cada chamada de uma suposta funcao EntreLinha() 1 buffer seria preenchido, onde estaria esse buffer? voce poderia fazer uma array multidimensional porem memoria seria altamente disperdicada se o usuario precisa-se somente de umas 10 linhas e voce aloca-se espaco para 1000, por isso e usado uma array de ponteiros para charactere, lembra da sessao de memoria dinamica e ponteiros, o dinamismo do ponteiro resolve o problema, EntreLinha() retornaria um ponteiro para uma linha que foi alocada na memoria dinamicamente e esse ponteiro seria copiado para a array de ponteiros e um inteiro de posicao da array seria incrementado, nesse caso por simplicidade e tambem pq como tudo e limitado a array de ponteiros teria de ter um tamanho predeterminado e limitado digamos 1000 linhas, entao imagine que em um determinado momento o usuario do programa precisa procurar por uma string dentro das linhas, simplesmente seria pecorrida a array de ponteiros acessando-se a memoria em que os ponteiros aponta; Se nao fosse com uma array de ponteiros como ordenaria-mos as linhas sequencialmente, assim?

#define TAMANHO_DA_LINHA 200

buf1[TAMANHO_DA_LINHA]
buf2[TAMANHO_DA_LINHA]
buf3[TAMANHO_DA_LINHA]
...
...
...

Com certeza nao!

Talvez assim?

#define MAXIMO_DE_LINHAS 1000

buf[MAXIMO_DE_LINHAS][TAMANHO_DA_LINHA]

Com certeza nao!

Voce ver no procedimento acima array multidimensional, seria alocado MAXIMO_DE_LINHAS(1000) * TAMANHO_DA_LINHA(200) que no caso seria 200,000 bytes que e disperdicio desde que vc nao sabe se o usuario somente precisa de 10 linhas ou seja 10 * 200 que e igual a 2,000 bytes.

Entao a solucao e:

char *MinhaArraydePonteiros[MAXIMO_DE_LINHAS];

Pq ai memoria e alocada de acordo com a necessidade de linhas, onde custariamos espaco inicialmente somente para os ponteiros ou sizeof(char *) * 1,000 que e igual a 4,000 bytes, e quando 10 linhas fossem tecladas, 10 * 200 teriamos 2,000, ao todo estariamos tomando 4,000 + 2,000 que e 6,000 bytes que comparado com a array multidimensional 200,000 bytes e absurdamente mais flexivel.

Observe um exemplo:

Leia o exemplo abaixo atenciosamente, e compile.

#include <stdio> /* Para IO*/ 
#include <stdlib> /* Para rotinas em geral */ 
#include <time> /* Calendario */ 


/* Definicao do limite de linhas */ 
#define NUMERO_DE_LINHAS 1000 
/* Definicao do tamanho de uma linha */ 
#define TAMANHO_DA_LINHA 200 

/* Definicao de uma string de error */ 
#define ERR_SEM_MEMORIA "Erro: sem memoria suficiente." 

/* A definicao de uma Array de Ponteiros para characteres observe a sintaxe */ 
char *Linhas[NUMERO_DE_LINHAS] = {NULL}; /* Inicializada pra NULL */ 

/* Index de linha, representa a linha atual dentro de Linhas.*/ 
int NumLinhas = 0; 

/* Mostra sugestoes sobre um texto que voce possa escrever */ 
void MostreUmaSugestao(void); 
/* Entrada de uma linha pelo teclado */ 
char *EntreLinha(void); 
/* Mostra todas as linhas em LinhasParam na quantidade exata */ 
void MostreLinhas(char *LinhasParam[], int QuantasLinhas); 
/* Limpa todas as linhas em LinhasParam na quantidade exata */ 
void LimpeLinhas(char *LinhasParam[], int QuantasLinhas); 

/* Funcao inicial */ 
int main(void) 
{ 
/* Mostra um titulo de programa amigavel */ 
puts("Usuario(a), bem vindo ao editor de texto," 
" para terminar digite '.' ponto, no comeco de uma" 
" linha e pressione [ENTER]."); 
puts(""); 

/* Mostra uma sugestao, veja declaracao da funcao logo abaixo */ 
MostreUmaSugestao(); 

/* Enquanto nao passamos do limite de linhas */ 
while (NumLinhas < NUMERO_DE_LINHAS) 
{ 
/* EntreLinha() retorna um ponteiro para uma linha para dentro de Linhas */ 
Linhas[NumLinhas] = EntreLinha(); 
/* Se o primeiro charactere de Linhas em uma determinada linha for . saia */ 
if (Linhas[NumLinhas][0] == '.') 
break; 
/* Proxima linha */ 
NumLinhas++; 
} 

puts(""); 
printf("Voce escreveu %d linha(as).\n", NumLinhas); 
puts("Mostrando toda(as) a(as) linha(as)..."); 
puts(""); 

/* Declaracao abaixo */ 
MostreLinhas(Linhas, NumLinhas); 
/* Declaracao abaixo */ 
LimpeLinhas(Linhas, NumLinhas); 

puts("O fim."); 
puts(""); 

system("PAUSE"); 
return 0; 
} 
/* Mostra uma sugestao declaracao */ 
void MostreUmaSugestao() 
{ 

/* Todas as sugestoes */ 
/* 
Observe que Sugestoes, abaixo e uma array multidimensional 
de 2 dimensoes, inicializada com todas as possiveis sugestoes, 
saiba tambem que e alocado em bytes 10 * 60 = 600 bytes. 
*/ 
char Sugestoes[10][60] = 
{ 
"Historia da sua vida", 
"Melhor dia da sua vida", 
"Sua primeira vez", 
"Pior dia da sua vida", 
"Um fato traumatico", 
"Um objetivo a ser alcancado para o futuro", 
"Relacionamento familiar", 
"Melhores amigos(as)", 
"Ponteiros", 
"Arrays de ponteiros" 
}; 

/* Numero que representa a data e hora do sistema */ 
time_t t; 

puts("Escreva sobre: "); 

/* Inicializa a funcao rand() para pegar uma sugestao aleatoriamente */ 
srand(time(&t)); 

/* Pega uma sugestao aleatoriamente */ 
printf("%s.\n\n", Sugestoes[rand() % 10]); 

} 

/* Entra uma linha pelo teclado */ 
char *EntreLinha(void) 
{ 
/* Esse buf e temporario ira segura a linha por um momento */ 
char Buf[TAMANHO_DA_LINHA]; 
/* Esse e um ponteiro para a linha que sera alocada */ 
char *Linha = NULL; 

/* 
Abaixo Pega uma linha para dentro de buf, note fgets() 
coloca '\n' que representa uma nova linha, 
dentro de buf. 
*/ 
fgets(Buf, TAMANHO_DA_LINHA, stdin); 

/* 
Abaixo e alocado memoria para Linha de acordo com o 
tamanho de "caracteres" em Buf. 

strlen(Buf) + 1 

+ 1 e necessario para se reservar espaco para o NULL. 
*/ 
Linha = (char *)malloc((strlen(Buf) + 1) * sizeof(char)); 

/* Alocou memoria ok? senao mostre erro, e saia. */ 
if (!Linha) 
{ 
puts(ERR_SEM_MEMORIA); 
exit(EXIT_FAILURE); 
} 

/* Copie Buf para dentro da Memoria alocada apontada por Linha */ 
strcpy(Linha, Buf); 

/* 
Retorne um ponteiro para a memoria alocada para 
a linha que e copiado para dentro da array de 
ponteiro de caracteres depois. 
*/ 
return Linha; 
} 

/* Mostre linhas no console */ 
void MostreLinhas(char *LinhasParam[], int QuantasLinhas) 
{ 

int i; 
/* Observe que LinhasParam[x] retorna um endereco para uma linha */ 
for (i = 0; i < QuantasLinhas; i++) 
if (LinhasParam[i]) /* Nao pode ser NULL */ 
printf(LinhasParam[i]); /* E diferente de NULL acesse endereco */ 

} 

/* Libera memoria alocada para as linhas */ 
void LimpeLinhas(char *LinhasParam[], int QuantasLinhas) 
{ 
int i; 
/* Observe que LinhasParam[x] retorna um endereco para uma linha */ 
for (i = 0; i <QuantasLinhas> ou operador seta, vejamos: 

PonteiroPraMinhaStruct = &InstanciaMinhaStruct; 

PonteiroPraMinhaStruct -> Campo1 = 10; 
printf("%i\n", PonteiroPraMinhaStruct -> Campo1); 

Observe como o operador -> e usado para acessar os campos de uma estrutura, também e possivel utilizar a seguinte sintaxe:

(*PonteiroPraMinhaStruct).Campo1;

Onde os parenteses são necessarios para com oq a estrutura seja acessada apartir do ponteiro primeiro, depois o operador . e aplicado e Campo1 pode ser acessado dentro da estrutura apontada por PonteiroPraMinhaStruct.

Vejamos um exemplo:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
/* Declara uma struct dentro de main */ 
struct MinhaStruct 
{ 
int Campo1; 
int Campo2; 
}; 

/* Inicializa a struct na memoria */ 
struct MinhaStruct InstanciaMinhaStruct; 

/* Declara um ponteiro para uma estrutura */ 
struct MinhaStruct *PonteiroPraMinhaStruct; 

/* Ponteiro pra estrutura aponta pra instancia na memoria */ 
PonteiroPraMinhaStruct = &InstanciaMinhaStruct; 

/* Acesse o campo1 usando -> */ 
PonteiroPraMinhaStruct -> Campo1 = 10; 

/* Acesse o campo1 pra mostrar na tela */ 
printf("%i\n", PonteiroPraMinhaStruct -> Campo1); 

/* Acesse o campo2 usando sintaxe normal de ponteiros */ 
/* 
Note a necessidade dos parenteses por causa que ponto 
tem precedencia maior 
*/ 
(*PonteiroPraMinhaStruct).Campo2 = 20; 

/* Acesse o campo2 pra mostrar na tela */ 
printf("%i\n", (*PonteiroPraMinhaStruct).Campo2); 

system("pause"); 
return 0; 
} 

não e facil usar ponteiros com estruturas? Smile não tem segredo.


Ponteiros e parametros de funcoes

Muitas vezes em nossos programas precisamos passar parametros para uma funcao, se vc não sabe oq e uma funcao ou não esta tao confiante aqui vai uma revisao:

Tipo-de-retorno Nome-da-funcao(Tipo-de-parametro Nome-do-parametro, ...);

Acima o formato de uma funcao em C/C++ o Tipo-de-retorno e oq a funcao retorna podendo ser qualquer dos tipos suportados na linguaguem C/C++, o Nome-da-funcao vc escolhe, e o Tipo-de-parametro Nome-do-parametro, e a declaracao de um parametro para uso dentro da funcao, exemplo:

void Tabuada(int De, int Ate);

Seria uma funcao que retorna nada(void) chamada Tabuada e que pega 2 parametros o inteiro De, e o inteiro Ate, onde qualquer linha de codigo chamando a funcao passara os parametros por valor, ou seja valores seram passados nos dois parametros ou variaveis que contenham um valor que seja do mesmo tipo excluindo ponteiros.

então oq acontece se eu fizer:

Tabuada(1, 10);

Estaria passando os valores que programaticamente e chamado: Parametros passados por valor.

ou

int x = 1;
int y = 10;
Tabuada(x, y);

Seria a mesma coisa: Parametros passados por valor.

Quando um parametro e passado por valor, e feita uma copia do mesmo na stack, a stack e um lugar onde as variaveis do seu programa em C/C++ que não são globais, ficam armazenadas. então essa copia do valor fica na stack e não tem nenhum relacionamento com as variaveis x, e y fora da funcao, exemplo:

#include <stdio> 
#include <stdlib> 

/* 
A funcao exemplo pega 2 parametros inteiros. E depois pras respectivas copias de a e b que foram passadas pra funcao que estao na stack e escrito 10 e 90, então se 10 e 90 e escrito para as copias as variaveis que foram passadas por valor não são afetadas, somente as copias. 
*/ 
void Exemplo(int a, int 
{ 
a = 10; 
b = 90; 
} 

int main(void) 
{ 
int x = 1, y = 2; 
/* 
Chama a funcao Exemplo() no qual e feita uma copia de x e y na stack. 
*/ 
Exemplo(x, y); 
/* x continua 1 e y continua 2 */ 
printf("X = %d, Y = %d\n", x, y); 
system("pause"); 
return 0; 
}

Então aqui e onde ponteiros entram em acao, e utilizado ponteiros para se modificar o valor de variaveis de dentro de uma funcao!!!

Ou seja, desde que, quando chamamos uma funcao passando parametros por valor, copias dos valores são feitas exclusivas para a funcao na stack, então se vc modifica o parametro da funcao vc não afeta as variaveis que foram usadas para se passar pra funcao, mas sim as copias que foram feitas automaticamente que estao na stack, então as variaveis que foram usadas pra se passar pra funcao ficam intocaveis, a solucao para esse problema: variaveis intocaveis, e o uso de ponteiros.

Vejamos:

#include <stdio> 
#include <stdlib> 

/* 
A funcao exemplo pega 2 ponteiros pra inteiros. E depois atravez dos ponteiros e acessado as respectivas variaveis que são passadas por referencia, ou seja os enderecos das variaveis são passados, então os valores 10 e 90 são escritos diretamente. 
*/ 
void Exemplo(int *a, int * 
{ 
*a = 10; 
*b = 90; 
} 

int main(void) 
{ 
int x = 1, y = 2; 
/* 
Chama a funcao Exemplo() no qual e feita uma copia dos enderecos x e y na stack. 
*/ 
Exemplo(&x, &y); 
/* X foi modificado e Y também */ 
printf("X = %d, Y = %d\n", x, y); 
system("pause"); 
return 0; 
} 

Se vc compilar o exemplo acima vera que x e y, variaveis dentro de main() são modificadas pela funcao Exemplo() que recebe o endereço das variaveis, que e chamado de: Parametros passados por referencia.

Lembra de:

scanf("%c", &MeuChar);

Isso mesmo e necessario uma chamada por referencia para com que a variavel MeuChar possa ser acessada por scanf; Onde o endereço da variavel e passado pra funcao e a mesma modifica a variavel, se voce nao passar o endereco, scanf() pegara o valor de MeuChar pensando que o mesmo e um endereco, no qual nao e, e scanf() tentara copiar um caractere para um espaco de memoria que possa estar protegido, e o sistema abortara o programa com erro de write access violation.

Isso vale para qualquer variavel que vc deseja modificar de dentro de uma funcao, arrays, estruturas, etc.

Observe o exemplo abaixo:

#include <stdio> 
#include <stdlib> 

/* Essa funcao troca o valor em x pelo de y */ 
void Troca(int x, int y) 
{ 
int temp; 
temp = x; /* Guarda x*/ 
x = y; /* Troca por y */ 
y = temp; /* Troca por x */ 
printf("copia de x = %d e copia de y = %d\n", x, y); 
} 

int main(void) 
{ 
int x, y; 
x = 10; 
y = 20; 
puts("x e y inicial: "); 
printf("x = %d e y = %d\n", x, y); 
/* Parametros passados por valor */ 
Troca(x, y); 
/* Continua o mesmo */ 
puts("Dentro de main:"); 
printf("x = %d e y = %d\n", x, y); 
system("pause"); 
return 0; 
} 

Analizando a chamada da funcao Troca(x, y), parametros sao passados por valor, oq siguinifica que de dentro da funcao nao ha como se referenciar as variaveis que foram declaradas dentro de main, entao siguinifica que apesar de Troca() trocar os valores, esses valores seram trocados usando as copias de x e y que foram feitas que estao na stack, por tanto quando a funcao retorna, x e y dentro de main continuam a mesma coisa.

Observe o exemplo abaixo:

#include <stdio> 
#include <stdlib> 

/* Essa funcao troca o valor em x pelo de y */ 
void Troca(int *x, int *y) 
{ 
int temp; 
/* Observe o uso do operador * para acessar as variaveis */ 
temp = *x; /* Guarda x */ 
*x = *y; /* Troca por y */ 
*y = temp; /* Troca por x */ 
} 

int main(void) 
{ 
int x, y; 
x = 10; 
y = 20; 
puts("x e y inicial: "); 
printf("x = %d e y = %d\n", x, y); 
/* Parametros passados por referencia */ 
Troca(&x, &y); 
/* Troca() modifica */ 
puts("Dentro de main:"); 
printf("x = %d e y = %d\n", x, y); 
system("pause"); 
return 0; 
} 

Chamando Troca() com parametros passados por referencia, e dado a funcao os enderecos das variaveis aonde se encontram os dados dessa forma de dentro da funcao e possivel se acessar esses dados, entao as variaveis de dentro de main() sao modificadas.

Funcoes que retornam um ponteiro

Funcoes que retornam um ponteiro são funcoes que retornam o endereço em memoria de uma variavel dessa forma e possivel manipular a variavel apartir do ponteiro, vejamos o exemplo:

#include <stdio> 
#include <stdlib> 
#include <string> /* Para usar strupr() */ 

/* 
A funcao abaixo pega 1 ponteiro pra um char então a funcao 
strupr() e chamada passando o parametro Str de StrMaiuscula(), e o retorno e um 
ponteiro pra um caractere em letra maiuscula que e retornado da strupr(). 
*/ 
char *StrMaiuscula(char *Str) 
{ 
return strupr(Str); 
} 

int main(void) 
{ 
char String[] = "minha string Smile"; 
/* 
Chama a funcao StrMaiuscula() passando a string String como 
parametro depois printf() e chamado, desde que StrMaiuscula retorna o endereço de String o uso 
direto como abaixo e possivel. 
*/ 
printf("%s\n", StrMaiuscula(String)); 
system("pause"); 
return 0; 
} 

Como vc pode observar strupr() retorna um ponteiro pra um char por isso:

return strupr(Str);

E aceitavel. Pq estamos retornando oq strupr() retornou um ponteiro para um caractere.

também e necessario lembrar que String, o nome da array e um label que associa o nome String a um endereço, e esse endereço e o endereço do primeiro caractere na memoria, por isso vc não ve o operador & antes de String nas chamadas de funcao, simplesmente pq String ja representa o endereço do primeiro elemento da array, no entanto &String[0] também e possivel.

Ponteiro pra funcoes

Se vc não sabe e possivel se declarar um ponteiro pra uma funcao, da seguinte forma:

Tipo-de-retorno-da-funcao (*Nome-do-ponteiro-pra-funcao)(Tipo-de-parametro Nome-de-parametro, ...);

Logo um ponteiro pra uma funcao que retorne um inteiro e pega nada seria:

int (*PonteiroPraFuncao)(void);

um Ponteiro pra uma funcao que retorna nada e pega um ponteiro pra um inteiro seria:

void (*PonteiroPraFuncao)(int *);

Quando obtemos um ponteiro apenas fazemos o mesmo apontar para o endereço de algo, nesse caso o endereço de uma funcao.

Para se obter o endereço de uma funcao e usado o nome da funcao ou label, o operador & não e necessario, e ilegal, vejamos:

void (*PonteiroPraFuncao)(int *);

PonteiroPraFuncao = NomeDaMinhaFuncao;

Dessa forma podemos chamar uma funcao apartir de seu ponteiro, observe:

int Inteiro;
(*PonteiroPraFuncao)(&Inteiro);

Acima e chamada a funcao void NomeDaMinhaFuncao(int *x) apartir do ponteiro pra funcao, onde parametros são passados.

Exemplo:

#include <stdio> 
#include <stdlib> 

/* Funcao que retorna nada e pega um ponteiro para um inteiro */ 
void Funcao(int *PonteiroInt) 
{ 
/* O mesmo que *PonteiroInt = (*PonteiroInt) * 2 */ 
*PonteiroInt *= 2; 
} 

int main(int argc, char *argv[]) 
{ 
/* Declara uma variavel x */ 
int x = 10; 
/* Declara um ponteiro para uma funcao que retorne nada e pega int * */ 
void (*PonteiroPraFuncao)(int *); 
/* Faz com oq PonteiroPraFuncao aponte pra Funcao */ 
PonteiroPraFuncao = Funcao; 
/* Chama funca(&x) apartir de PonteiroPraFuncao */ 
(*PonteiroPraFuncao)(&x); 
/* x = 20 */ 
printf("x = %d\n", x); 
system("PAUSE"); 
return 0; 
} 

Ponteiros para ponteiros

Lembra quando escrevi que ponteiros sao variaveis? Toda variavel possui um endereco de localizacao na memoria, entao ponteiros por serem variaveis possuem seu endereco na memoria, no qual ironicamente um ponteiro guarda um endereco de outra variavel, entao desde que ponteiros tem enderecos, pq nao ter como guardar o endereco de um ponteiro, logo temos ponteiros para ponteiros.

Um ponteiro para um ponteiro e uma variavel que guarda o endereco de outro ponteiro, dessa forma apartir do endereco de um ponteiro, ou seja o endereco onde um ponteiro se encontra, podemos acessar o mesmo para escrita e leitura, e ate acessar ao oq o mesmo aponta.

Para se declarar um ponteiro para um ponteiro e usada a seguinte sintaxe:

Tipo **NomeDoPonteiro;

Onde Tipo e qualquer tipo de dados da linguagem C/C++, os asteristicos, 2 no caso, representam que a variavel a ser declarada e um ponteiro para um ponteiro.

Como em,

char **pp;

Entao digamos se temos:

char MeuChar = '@';

char *PonteiroParaChar = &MeuChar;

Fazemos com oq PonteiroParaChar aponte para MeuChar. Logo depois:

char **PonteiroParaPonteiroParaChar = &PonteiroParaChar;

Observe que PonteiroParaPonteiroParaChar aponta para o PonteiroParaChar, oq siguinifica que PonteiroParaPonteiroParaChar contem o endereco onde se encontra o PonteiroParaChar.

Para se accessar oq o ponteiro dentro de PonteiroParaPonteiroParaChar aponta e utilizada a seguinte sintaxe, observe:

printf("%c\n", **PonteiroParaPonteiroParaChar);

Que retorna oq o ponteiro que o ponteiro para ponteiro aponta contem, que e o char arroba ou @.

Digamos que queira modificar esse char apartir do ponteiro para ponteiro, entao:

**PonteiroParaPonteiroParaChar = '*';

Agora MeuChar contem asterisco ou *.

Para se entender oq esta acontecendo considere essa explicacao:

O operador * retorna um valor de um tipo de dados em um endereco de memoria, ou seja quando usamos
o operador * com PonteiroParaPonteiroParaChar dessa forma: *PonteiroParaPonteiroParaChar desde que PonteiroParaPonteiroParaChar contem o endereco de um ponteiro, acessamos o determinado ponteiro; E o que acontece no caso de **PonteiroParaPonteiroParaChar, observe os parenteses em:
*(*PonteiroParaPonteiroParaChar) 1o e acessado o ponteiro, que claro contem o endereco de uma variavel, e 2o e por ultimo temos o seguinte *(Endereco de uma variavel), onde o operador asteristico acessa essa determinada variavel, logo no caso acessamos Meuchar.

Observe o exemplo com atencao e compile:

#include <stdio> 
#include <stdlib> 

int main(void) 
{ 
char MeuChar = '@'; 
char *MeuPonteiro = &MeuChar; 
char **MeuPonteiroParaPonteiroParaChar = &MeuPonteiro; 

puts("Nome: MeuChar"); 
printf("Endereco: %#p\n", &MeuChar); 
printf("Valor: %c\n", MeuChar); 

puts(""); 
puts("Nome: MeuPonteiro"); 
printf("Endereco: %#p\n", &MeuPonteiro); 
printf("Valor: %#p\n", MeuPonteiro); 
printf("** Observe que o valor de MeuPonteiro e o endereco" 
" de MeuChar **\n"); 
printf("Acesse endereco: %c\n", *MeuPonteiro); 

puts(""); 

puts("Nome: MeuPonteiroParaPonteiroParaChar"); 
printf("Endereco: %#p\n", &MeuPonteiroParaPonteiroParaChar); 
printf("Valor: %#p\n", MeuPonteiroParaPonteiroParaChar); 
printf("** Observe que o valor de MeuPonteiroParaPonteiroParaChar e o endereco" 
" de MeuPonteiro **\n"); 
printf("Acesse endereco: %#p\n", *MeuPonteiroParaPonteiroParaChar); 
printf("** Observe que acessando o valor de MeuPonteiroParaPonteiroParaChar" 
" que e o endereco de MeuPonteiro, encontramos o endereco de MeuChar **\n"); 
printf("Acesse ponteiro para ponteiro: %c\n", **MeuPonteiroParaPonteiroParaChar); 
puts("** Encontramos o que o ponteiro apontado pelo ponteiro pra ponteiro aponta **"); 
puts(""); 

system("pause"); 
return 0; 
} 

Vejamos um dos usos de um ponteiro para um ponteiro; Digamos que voce queira fazer uma funcao que aloque memoria e que um ponteiro para essa memoria seja retornado, nao necessariamente voce precisa retornar esse ponteiro, por que nao passar um endereco de um ponteiro para a funcao, no qual quando voce chamar a funcao voce passa o endereco de um ponteiro do tipo de dados para a alocacao, onde apartir dai pelo endereco do ponteiro voce pode modificar o ponteiro que esta em algum lugar no seu programa, copiando para o mesmo o endereco dos bytes recem alocados, veja o exemplo:

#include <stdio> 
#include <stdlib> 

/* Recebe um endereco para um ponteiro */ 
void Exemplo(char **p) 
{ 
/* 
Como p e o endereco de um ponteiro use o operador 
* para acessar esse endereco e copiar o endereco 
retornado por malloc(). 
*/ 
*p = malloc(30); 
} 

int main(void) 
{ 
char *p; 
/* Chamada por referencia */ 
Exemplo(&p); 
/* Memoria foi alocada copie dados */ 
strncpy(p, "Test 123\n", 30); 
/* Mostre na tela */ 
printf(p); 
system("pause"); 
return 0; 
} 

Um outro uso de ponteiro para ponteiro e com array de ponteiros; Da mesma forma que e possivel utilizar um ponteiro para se caminhar por uma array de um tipo, e possivel se usar um ponteiro para um ponteiro para se caminhar por todos os items de uma array de ponteiros que por sinal sao ponteiros, e nao e exatamente isso ao que um ponteiro para um ponteiro aponta.

Vejamos um exemplo:

#include <stdio> 
#include <stdlib> 

int main(int argc, char *argv[]) 
{ 
char **pp = argv; 
while (argc-- > 0) 
printf("%s\n", *pp++); 
system("pause"); 
return 0; 
} 

E declarado um ponteiro para um ponteiro chamado pp, que e inicializado para o valor de argv, no qual e um endereco para um ponteiro, depois no while loop, pp e incrementado assim: pp += sizeof(char *); dessa forma o proximo endereco para um ponteiro e acessado, ja o operador * acessa os enderecos especificos onde e encontrado enderecos, que e o parametro para printf() mostrar uma string de argumento.

Ponteiros em C/C++
por: C0d3r__

Link para o comentário
Compartilhar em outros sites

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...