<aside>

Conhecimentos necessários:

Variáveis, entrada e saída de dados, estruturas condicionais e estruturas repetitivas

Dados, bases numéricas e memória

Tratamento de caracteres

Sub-rotinas (funções)

Estruturas de dados homogêneas: array (vetor/matriz)

</aside>

<aside>

Navegação

</aside>

O que são?

Um ponteiro nada mais é do que uma variável que armazena um endereço. Esse endereço pode ser de outras variáveis!

Com os ponteiros, é possível acessar os locais das variáveis e modificar seu valor.

Declaração

Para declarar um ponteiro, você precisa apenas colocar um asterisco antes do nome de uma variável (ou também pode ser logo depois de declarar o tipo de uma variável). Um exemplo:

Para criar um ponteiro do tipo int, posso escrever assim:

int *nome_do_ponteiro;

Se eu quiser criar um ponteiro do tipo char (em outras palavras, consegue guardar o endereço de uma variável do tipo char), posso escrever assim:

char *nome_do_ponteiro;

Agora, pense comigo: o ponteiro é um tipo de variável, então ele também tem um endereço de variável como qualquer outra, então eu também consigo guardar o endereço de um ponteiro em um ponteiro!

Para demonstrar como isso é possível, vamos criar um ponteiro que guarda o endereço de um int:

int *pont1;

Se eu quiser guardar o endereço desse ponteiro em um ponteiro, basta eu criar um ponteiro que guarda o endereço de um ponteiro de int! Ficando assim:

int **pont2;

Na prática, ponteiro de ponteiros só possuem um asterisco a mais!

Uso e operadores de ponteiro

Mas agora, como usar um ponteiro? Para isso Precisamos saber os operadores de ponteiro:

Vamos a um exemplo:

Criarei uma variável para guardar uma idade:

int idade = 18;

Quero guardar o endereço dessa variável, então posso criar um ponteiro e fazer isso:

int *pont = &idade;

Tá vendo? Guardei o local onde essa variável fica, não o valor dela em si! Eu também poderia fazer assim para guardar o endereço dessa variável:

int *pont; pont = &idade;

Tá vendo que na atribuição eu não usei um asterisco? Isso ocorre por causa que o asterisco no início só estava servindo para falar ao código que eu queria criar um ponteiro, depois eu utilizo ele normalmente!

Só que, se mais para frente eu quero ir no local dessa variável e mudar o valor de 18 para 24, por exemplo, aí eu uso o asterisco para falar ao código que quero ir no local desse endereço e mudar o valor dele, ficando assim:

*pont = 24;

Ou seja, mudei o valor da variável idade pelo ponteiro!

E caso queira inicializar um ponteiro para não deixá-lo com lixo de memória, pode fazer:

int *pont = NULL;

Sei que tudo isso pode parecer confuso, então essa tabela vai tentar resumir tudo isso:

Criação de ponteiros (tabela)

Criação O que aconteceu?
int pont; Apenas criei uma variável do tipo int.
int *pont; Criei um ponteiro que guarda endereços de ints.
int pont = idade; Criei uma variável do tipo int e guardei o mesmo valor da variável idade.
int *pont = idade; Tá errado, pois um ponteiro tem que guardar um endereço, não um valor int comum.
int pont = &idade; Tá errado, pois estou criando um int e tentando guardar um endereço nele.
int *pont = &idade; Criei um ponteiro e guardei o endereço da variável idade.

Uso de ponteiros (tabela)

Uso O que aconteceu?
pont = idade; Tá errado, pois o ponteiro só pode guardar endereços, não números ints comuns.
pont = &idade; Estou guardando o endereço da variável idade.
*pont = idade; Estou mudando o valor da variável que o ponteiro guardou o endereço para o mesmo valor de idade.
*pont = &idade Tá errado, pois estou tentando mudar o valor da variável que o ponteiro guardou o endereço e colocar um endereço, isso não pode.

Ponteiros vs arrays

Os ponteiros e arrays têm mais em comum do que aparentam… Eles são quase iguais, para falar a verdade.

Um array, tecnicamente é um ponteiro… mas como ele poderia ser um ponteiro?

Um array nada mais faz do que guardar o endereço do primeiro elemento!

Quando queremos acessar o segundo elemento, ele nada mais faz do que ir no local do primeiro elemento e andar uma unidade (não necessariamente 1 byte), o que faz chegar no segundo elemento!

Se eu fizer isso:

int notas[4] = {7, 8, 10, 6};

Quando eu chamar apenas o nome notas, é a mesma coisa de eu escrever &notas[0], pois o nome do array nada mais é do que o endereço para o primeiro elemento.

Sem falar que eu também posso fazer isso aqui:

int *pont = notas;

E usar pont[0], pont[1], pont[2], … pois como um array é quase um ponteiro, posso usar um ponteiro como um array.

E é por esse motivo que os arrays sempre são passados por referência nas funções!

Ponteiros genéricos

Caso precise, também consegue criar ponteiros que aceitam qualquer tipo de endereço, os chamados ponteiros genéricos.

Para criar, no lugar do tipo do ponteiro, use void, ficando assim:

void *pont;

Agora esse ponteiros pode guardar qualquer tipo de conteúdo, mas se precisar alterar o valor de algumas variável com ele, precisa escrever o tipo do ponteiro entre o nome e o asterisco. Por exemplo:

int main(){
	int idade = 18;
	void *pont = &idade;
	*(int*)pont = 24; //falei que ele estava atuando como um ponteiro do tipo int* e mudei o valor da variável idade para 24
	
	return 0;
}

Isso pode ser bem útil se quer fazer funções que funcione para todos os tipos, por exemplo:

#include <stdio.h>

void mostrarValor(void *ptr, char tipo) {
    if (tipo == 'i') {
        printf("Inteiro: %d\\n", *(int*)ptr);
    } else if (tipo == 'f') {
        printf("Float: %.2f\\n", *(float*)ptr);
    } else if (tipo == 'c') {
        printf("Caractere: %c\\n", *(char*)ptr);
    } else {
        printf("Tipo desconhecido.\\n");
    }
}

int main() {
    int i = 42;
    float f = 3.14;
    char c = 'A';

    mostrarValor(&i, 'i');
    mostrarValor(&f, 'f');
    mostrarValor(&c, 'c');

    return 0;
}

Ponteiros e condicionais

Também é possível usarmos ponteiros dentro de condicionais. Eles funcionam de maneira bem simples:

Caso sejam NULL (não armazenam endereço algum) eles servirão como false nas condicionais. Caso contrário, eles serão true.

Um exemplo:

#include <stdio.h>

int main() {
    int resposta_universo = 42;
    int *ptr = NULL; //inicialização
    
    if(ptr)
	    printf("Esse comando nao sera executado");
	  else
		  printf("Nao ha nada no ponteiro");
		
		ptr = &resposta_universo;
		
		if(ptr)
	    printf("Agora ha um endereco no ponteiro");
	  else
		  printf("Esse comando nao sera executado");

    return 0;
}

Claro que esse esse código está meio feio… vamos arrumar usando dois operadores ternários?

#include <stdio.h>

int main() {
    int resposta_universo = 42;
    int *ptr = NULL; //inicialização
    
    ptr ? printf("Esse comando nao sera executado") : printf("Nao ha nada no ponteiro");
		
		ptr = &resposta_universo;
		
		ptr ? printf("Agora ha um endereco no ponteiro") : printf("Esse comando nao sera executado");

    return 0;
}

Bem melhor, concorda?