Ponteiros
Ponteiros são um recurso poderoso na programação em C que permite trabalhar diretamente com endereços de memória.
Em C, um ponteiro é uma variável especial que armazena o endereço de memória de outra variável ou mesmo de uma função. Em outras palavras, um ponteiro “aponta” para o endereço de memória de outra variável. Isso permite aceder e manipular diretamente na memória o valor dessa variável.

Na imagem acima, a variável ptr guarda o endereço de memória da variável nr. A variável ptr é um ponteiro para a variável nr. Através do ponteiro ptr podemos aceder e alterar o conteúdo da variável nr.
O uso de ponteiros permite o seguinte:
- Acesso direto à memória: Ponteiros fornecem acesso direto a locais de memória, permitindo uma manipulação de dados mais rápida em comparação ao acesso a dados por meio de nomes de variáveis.
- Manipulação eficiente de dados: Estruturas de dados complexas podem ser manipuladas de forma mais eficiente usando ponteiros, levando a um desempenho aprimorado em operações como percorrer listas, por exemplo.
As duas características acima mencionadas levam a um tempo de execução de um programa em C mais rápido.
O operador endereço de (address of)
O operador endereço de (&) é fundamental na manipulação de ponteiros uma vez que este operador nos o endereço de memória de uma variável:
int nr = 7;
int endereco = &nr;
Declaração e inicialização de ponteiros
Os ponteiros são declarados como outra variável qualquer mas usando o operador de indireção(*).
A sintaxe para a declaração de um ponteiro é a seguinte:
tipo_dado *nome_ponteiro;
Após a declaração de um ponteiro inicializamo-lo com o endereço de uma outra variável:
//declaração de uma variável do tipo int
int nr=7;
//declaração de um ponteiro do tipo int
int *ptr;
//inicialização do ponteiro com o endereço da variável nr
ptr = &nr;
NOTA: o ponteiro ptr guarda o endereço da variável nr, logo podemos dizer que o ponteiro *ptr aponta para a variável nr.
NOTA: o tipo de dados de um ponteiro deve ser sempre o mesmo da variável para que ele aponta.
Quando declaramos um ponteiro podemos inicializa-lo de imediato :
int nr = 7;
int *ptr = &nr;
NOTAS:
- o nome do ponteiro sem asterisco refere-se ao endereço de memória nele guardado.
- o nome do ponteiro com asterisco refere-se ao valor que está guardado nesse endereço de memória

Exemplo:
int nr = 7;
ptr = &nr;
printf("%d - %d", ptr, *ptr);
Aceder ao valor apontado por um ponteiro
Para aceder ao valor apontado por um ponteiro, usamos o operador de indireção *:
int numero = 37;
int *ponteiroNumero = №
//acessa o valor apontado pelo ponteiro:
int valor = *ponteiroNumero; // valor conterá 37
Modificar o valor apontado por um ponteiro
Podemos modificar o valor apontado por um ponteiro simplesmente atribuindo um novo valor a ele, usando o operador de indireção:
*ponteiroNumero = 94; // Agora, 'numero' contém 94
Aritmética de ponteiros
A aritmética de ponteiros permite que se manipulem os endereços de memória de forma eficaz para aceder e percorrer arrays, estruturas de dados e outros tipos de dados.
A aritmética de ponteiros consiste basicamente na possibilidade que o C oferece de podermos incrementar ou decrementar um endereço de memória guardado num ponteiro, de forma a o ponteiro apontar para endereço de memória depois ou antes do atual, ou, então, podermos adicionar ou diminuir um valor a esse endereço.
A mecânica por trás da aritmética de ponteiros tem a ver com o tipo de dados do ponteiro em causa.
Vamos considerar o ponteiro seguinte:
int nr = 7;
int *ptr = &nr;
Vamos supor que o endereço guardado no endereço *ptr é 6422036.
Incrementemos o ponteiro de uma unidade e imprimamos o valor no ecran:
printf(“%d”, ptr + 1);
O novo endereço mostrado no ecran é agora 6422040.
Ou seja, ao adicionarmos 1 ao endereço, de facto o C adicionou 4.
A razão de tal é porque sendo o ponteiro *ptr do tipo int (que ocupa 4 bytes de memória) ao adicionarmos 1 ao endereço no ponteiro são adicionados 4 pois só assim o ponteiro passa para o endereço seguinte.
Resumidamente, ao adicionarmos 1 a um ponteiro dos tipos de dados abaixo, na realidade são adicionados os valores seguintes:
Tipo | Tamanho | Incremento de 1 | Decrementa de 1 |
char | 1 byte | Incrementa 1 | Decrementa 1 |
int | 4 bytes | Incrementa 4 | Decrementa 4 |
float | 4 bytes | Incrementa 4 | Decrementa 4 |
double | 8 bytes | Incrementa 8 | Decrementa 8 |
A aritmética de ponteiros é muito usada para percorrermos arrays :
int nrs[] = {4, 8, 6, 5};
//inicialização do ponteiro com o endereço do array:
int *ptr = nrs;
//percorre todos os elementos do array incrementando o ponteiro em cada iteração:
int i;
for(i = 0; i < 4; i++, ptr++){
printf("%d\n", *ptr);
}
Ponteiros para estruturas
Podemos usar ponteiros em variáveis do tipo de uma estrutura qualquer.
Consideremos a estrutura seguinte:
struct Pessoa {
char nome[20];
int idade;
float altura;
};
Declaremos uma variável do tipo de estrutura Pessoa e um ponteiro para essa variável:
struct Pessoa pessoa;
struct Pessoa *ptr = &pessoa;
Usando o ponteiro *ptr podemos aceder aos membros da estrutura:
strcpy(ptr->nome, "Maria");
ptr->idade = 21;
ptr->altura = 1.76;
NOTA: quando usamos ponteiros para estruturas devemos usar o operador ‘->’ em lugar do operador ‘.’ para aceder aos membros da estrutura.
Ponteiros para funções
Ponteiros para funções em C são variáveis que armazenam o endereço de uma função, permitindo chamá-la indiretamente. São úteis para implementar callbacks, tabelas de funções e programação orientada a eventos.
Como declarar um ponteiro para função
A sintaxe básica é:
tipo_retorno (*nome_ponteiro)(tipo_param1, tipo_param2, ...);
Exemplo:
Para uma função que recebe dois inteiros e retorna um inteiro:
int (*operacao)(int, int);
Como atribuir uma função ao ponteiro
Basta usar o nome da função (sem parênteses):
int soma(int a, int b) {
return a + b;
}
operacao = soma;
Como chamar a função via ponteiro
Você pode usar tanto (*operacao)(x, y)
quanto operacao(x, y)
:
int resultado = operacao(3, 4); // resultado = 7
Exemplo completo
#include <stdio.h>
int soma(int a, int b) {
return a + b;
}
int subtrai(int a, int b) {
return a - b;
}
int main() {
int (*operacao)(int, int);
operacao = soma;
printf("Soma: %d\n", operacao(5, 3)); // Imprime 8
operacao = subtrai;
printf("Subtração: %d\n", operacao(5, 3)); // Imprime 2
return 0;
}
Aplicações comuns
- Callbacks: Passar funções como argumento para outras funções.
- Tabelas de funções: Implementar menus ou comandos dinâmicos.
- Abstração: Permitir que funções genéricas operem sobre diferentes operações.
Dicas
- O tipo do ponteiro deve ser compatível com o da função.
- Use ponteiros para funções para tornar seu código mais flexível e modular.
Passagem de ponteiros para funções
Podemos ter ponteiros como parâmeros de funções. Os argumentos passados são endereços de variáveis. Trata-se de uma passagem de argumentos por referência ao invés de ser por cópia. Na função podemos modificar diretamente os valores originais dessas variáveis:
void alteraNrs(int *p1, int *p2){
*p1 = 10;
*p2 = 20;
}
int main()
{
int nr1 = 6;
int nr2 = 14;
alteraNrs(&nr1, &nr2);
printf("%d - %d", nr1, nr2); //output : 10 - 20
}
Ao chamarmos a função alteraNrs() passamos os endereços das variáveis nr1 e nr2. A função altera os valores destas variáveis.