Ponteiros Avançados
Ponteiros são como bilhetes de endereço — um ponteiro para ponteiro é um bilhete escrito em cima de outro bilhete. Parece confuso, mas na vida real "o número do armário onde você guarda um pacote" funciona exatamente assim, camada sobre camada.
Ponteiro para Ponteiro
Uma variável ponteiro é, ela própria, uma variável — ocupa espaço na memória e tem seu próprio endereço. Quando você usa um ponteiro para apontar para outro ponteiro, obtém um "ponteiro para ponteiro".
int x = 42;
int *p = &x;
int **pp = &p;
Através de **pp você pode modificar indiretamente o valor de x:
**pp = 100;
printf("%d\n", x);
100
O uso mais comum de um ponteiro duplo é "modificar o próprio ponteiro dentro de uma função", como fazer uma função alocar memória para um ponteiro:
void alloc_buf(char **ptr, int size) {
*ptr = (char *)malloc(size);
}
int main(void) {
char *buf = NULL;
alloc_buf(&buf, 128);
if (buf) {
strcpy(buf, "hello");
printf("%s\n", buf);
free(buf);
}
return 0;
}
hello
char *ptr, a função modifica uma cópia — o ponteiro original de fora não muda. Para modificar o próprio ponteiro, você deve passar o endereço dele.
Array de Ponteiros vs. Ponteiro de Array
Esses dois conceitos são facilmente confundidos. A chave está na precedência de * e [].
Array de Ponteiros
int *arr[4] — liga-se com [] primeiro, então é um array cujos elementos são int *.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
printf("%d ", *arr[i]);
}
10 20 30
Uma aplicação típica: um array de strings.
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
Alice
Bob
Carol
Ponteiro de Array
int (*p)[4] — liga-se com * primeiro, então é um ponteiro que aponta para um array de 4 ints. Comumente usado para passar arrays 2D para funções.
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*p)[4] = matrix;
printf("%d\n", p[1][2]);
7
Exemplo
#include <stdio.h>
void print_matrix(int (*m)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
}
int main(void) {
int matrix[2][4] = {
{10, 20, 30, 40},
{50, 60, 70, 80}
};
print_matrix(matrix, 2);
const char *fruits[3] = {"apple", "banana", "cherry"};
for (int i = 0; i < 3; i++) {
printf("%s ", fruits[i]);
}
printf("\n");
return 0;
}
10 20 30 40
50 60 70 80
apple banana cherry
int *a[4] é um array de ponteiros, int (*a)[4] é um ponteiro de array.
Ponteiro de Função e Callback
O nome de uma função é o endereço de entrada dessa função. Um ponteiro de função pode armazenar esse endereço, permitindo "chamadas adiadas" ou "callbacks".
Declaração de Ponteiro de Função
int (*pf)(int, int);
Isso declara pf como um ponteiro para uma função que recebe dois ints e retorna um int.
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main(void) {
int (*pf)(int, int) = add;
printf("%d\n", pf(3, 5));
pf = sub;
printf("%d\n", pf(10, 4));
return 0;
}
8
6
Mecanismo de Callback
Passe um ponteiro de função como argumento para outra função, para que esta possa "chamar de volta" no momento certo.
void process(int *arr, int len, int (*transform)(int)) {
for (int i = 0; i < len; i++) {
arr[i] = transform(arr[i]);
}
}
int double_it(int n) { return n * 2; }
int negate(int n) { return -n; }
int main(void) {
int data[4] = {1, 2, 3, 4};
process(data, 4, double_it);
for (int i = 0; i < 4; i++) printf("%d ", data[i]);
printf("\n");
process(data, 4, negate);
for (int i = 0; i < 4; i++) printf("%d ", data[i]);
return 0;
}
2 4 6 8
-2 -4 -6 -8
Exemplo
#include <stdio.h>
#include <stdlib.h>
int cmp_asc(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
int cmp_desc(const void *a, const void *b) {
return *(int *)b - *(int *)a;
}
void sort_and_print(int *arr, int n, int (*cmp)(const void *, const void *)) {
qsort(arr, n, sizeof(int), cmp);
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void) {
int nums[5] = {42, 7, 19, 3, 55};
sort_and_print(nums, 5, cmp_asc);
int nums2[5] = {42, 7, 19, 3, 55};
sort_and_print(nums2, 5, cmp_desc);
return 0;
}
3 7 19 42 55
55 42 19 7 3
qsort da biblioteca padrão é uma aplicação clássica de callbacks com ponteiros de função — você só precisa fornecer a regra de comparação, e o algoritmo de ordenação cuida do resto.
Três Combinações de const com Ponteiros
const combinado com ponteiros tem três posições, cada uma com semântica diferente.
Ponteiro para const
const int *p;
int const *p;
Você não pode modificar o valor apontado através de p, mas p pode apontar para outro lugar.
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
10
20
Ponteiro const
int * const p = &a;
p não pode mudar para onde aponta, mas você pode modificar o valor através de p.
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
99
Ponteiro const para const
const int * const p = &a;
Nem o alvo do ponteiro nem o valor apontado podem ser alterados — o modo mais estrito de somente leitura.
const está à esquerda ou à direita de *. À esquerda, modifica os dados; à direita, modifica o próprio ponteiro.
Ponteiro void
void * é um "ponteiro genérico" que pode apontar para qualquer tipo, mas você deve fazer um cast antes de usar.
int a = 42;
double b = 3.14;
void *p;
p = &a;
printf("%d\n", *(int *)p);
p = &b;
printf("%.2f\n", *(double *)p);
42
3.14
Usos típicos de void *:
mallocretornavoid *, que pode ser atribuído a qualquer tipo de ponteiro- A função de comparação de
qsortusa parâmetrosconst void * - Implementação de estruturas de dados genéricas (ex: lista ligada genérica)
void * — o compilador não sabe o tamanho dos dados apontados. Você deve primeiro fazer o cast para um tipo de ponteiro concreto.
❓ Perguntas Frequentes
P: Como distinguir rapidamente entre um array de ponteiros e um ponteiro de array? R: Veja com que o nome da variável se liga primeiro — se liga com
[]primeiro, é um array (array de ponteiros); se liga com*primeiro, é um ponteiro (ponteiro de array). Use parênteses para mudar a precedência.
P: Qual a diferença entre um ponteiro de função e um nome de função? R: O nome da função é o endereço da função — é uma constante. Um ponteiro de função é uma variável que pode armazenar diferentes endereços de função, permitindo trocar o alvo da chamada em tempo de execução.
P:
const int *peint const *psão a mesma coisa? R: Sim, completamente idênticos. Ambos significam um ponteiro para const int — você não pode modificar os dados através de p, mas p pode apontar para outro lugar.
P: Pode-se fazer aritmética de ponteiros com um ponteiro void? R: Não. O compilador não sabe o tamanho dos dados que um ponteiro void referencia, então o avanço de
p+1é indefinido. Você deve primeiro fazer o cast para um tipo concreto antes de fazer aritmética.
📖 Resumo
- Um ponteiro duplo
int **ppé usado para modificar o próprio ponteiro dentro de uma função - Um array de ponteiros
int *a[N]é um array de ponteiros; um ponteiro de arrayint (*p)[N]é um ponteiro para um array - Um ponteiro de função
int (*pf)(int,int)armazena um endereço de função, e combinado com callbacks permite design flexível constà esquerda de*modifica os dados; à direita de*modifica o próprio ponteirovoid *é um ponteiro genérico — você deve fazer cast antes de usar
📝 Exercícios
- Escreva uma função
void swap_ptr(int a, int b)que troque o que dois ponteiros apontam. Verifique em main que cada ponteiro aponta para o valor original do outro após a troca - Declare um array de ponteiros de função
int (*ops[4])(int,int)contendo funções de soma, subtração, multiplicação e divisão. Chame a operação correspondente com base em um índice digitado pelo usuário - Escreva uma função de impressão genérica
void print_any(void *data, char type)onde type é 'i' para int, 'd' para double e 's' para string



