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".

C
int x = 42;
int *p = &x;
int **pp = &p;

Através de **pp você pode modificar indiretamente o valor de x:

C
**pp = 100;
printf("%d\n", x);
TEXT
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:

C
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;
}
TEXT
hello
💡 Dica: Se você passar apenas um ponteiro simples 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 *.

C
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
    printf("%d ", *arr[i]);
}
TEXT
10 20 30

Uma aplicação típica: um array de strings.

C
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
    printf("%s\n", names[i]);
}
TEXT
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.

C
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]);
TEXT
7

Exemplo

C
#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;
}
▶ Experimente
TEXT
 10 20 30 40
 50 60 70 80
apple banana cherry
⚠️ Atenção: Macete: 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

C
int (*pf)(int, int);

Isso declara pf como um ponteiro para uma função que recebe dois ints e retorna um int.

C
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;
}
TEXT
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.

C
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;
}
TEXT
2 4 6 8
-2 -4 -6 -8

Exemplo

C
#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;
}
▶ Experimente
TEXT
3 7 19 42 55
55 42 19 7 3
🔥 Erro Comum: A função 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

C
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.

C
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
TEXT
10
20

Ponteiro const

C
int * const p = &a;

p não pode mudar para onde aponta, mas você pode modificar o valor através de p.

C
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
TEXT
99

Ponteiro const para const

C
const int * const p = &a;

Nem o alvo do ponteiro nem o valor apontado podem ser alterados — o modo mais estrito de somente leitura.

💡 Dica: Macete rápido: veja se 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.

C
int a = 42;
double b = 3.14;
void *p;

p = &a;
printf("%d\n", *(int *)p);

p = &b;
printf("%.2f\n", *(double *)p);
TEXT
42
3.14

Usos típicos de void *:

⚠️ Atenção: Você não pode desreferenciar um 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 *p e int const *p sã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

📝 Exercícios

  1. 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
  2. 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
  3. 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
100%