Em C, funções normalmente recebem argumentos por valor, ou seja, uma cópia do dado é passada. Isso impede que a função modifique a variável original. Ponteiros resolvem essa limitação: ao passar o endereço de uma variável, a função pode acessar e alterar diretamente o conteúdo daquela posição de memória. Este mecanismo é chamado de passagem por referência (embora C passe o ponteiro por valor, o efeito é o mesmo).

Dominar ponteiros com funções é fundamental para escrever código modular, eficiente e que retorne múltiplos resultados. Vamos ver os conceitos na prática.

Passagem por referência

Na passagem por referência, a função recebe um ponteiro para a variável original. Dentro da função, usamos o operador de desreferência (*) para acessar e modificar o valor apontado. Isso contrasta com a passagem por valor, onde a função opera sobre uma cópia.

Considere este exemplo clássico: uma função que troca os valores de duas variáveis (swap). Sem ponteiros, a troca não teria efeito fora da função.

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    printf("Antes: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("Depois: x = %d, y = %d\n", x, y);
    return 0;
}

No código, swap recebe os endereços de x e y. Dentro dela, *a e *b acessam os valores originais. A troca é refletida em main porque os ponteiros apontam para as mesmas posições de memória.

Alterando valores

Além de trocar, podemos usar ponteiros para modificar variáveis de outros tipos. Por exemplo, uma função que incrementa um inteiro:

void incrementa(int *p) {
    (*p)++;  // parênteses necessários devido à precedência
}

int main() {
    int cont = 0;
    incrementa(&cont);
    printf("%d\n", cont);  // 1
    return 0;
}

Note que (*p)++ incrementa o valor apontado, enquanto *p++ incrementaria o ponteiro (o que não desejamos). Sempre use parênteses para desreferenciar antes de aplicar operadores.

Outro exemplo comum: funções que retornam status e preenchem um valor. Por exemplo, uma função que lê um número do usuário e retorna 1 se bem-sucedido, 0 caso contrário:

int ler_numero(int *resultado) {
    printf("Digite um numero: ");
    if (scanf("%d", resultado) == 1)  // resultado já é um ponteiro
        return 1;
    return 0;
}

int main() {
    int num;
    if (ler_numero(&num))
        printf("Voce digitou %d\n", num);
    else
        printf("Entrada invalida\n");
    return 0;
}

Retornando via ponteiro

Funções em C só podem retornar um único valor. Para retornar múltiplos valores, usamos parâmetros ponteiro: a função recebe endereços de variáveis e escreve neles. Isso é comum em funções que calculam vários resultados, como dividir um número em quociente e resto.

void divide(int dividendo, int divisor, int *quociente, int *resto) {
    *quociente = dividendo / divisor;
    *resto = dividendo % divisor;
}

int main() {
    int q, r;
    divide(17, 5, &q, &r);
    printf("17 / 5 = %d, resto %d\n", q, r);
    return 0;
}

Outra técnica é retornar um ponteiro para uma variável estática ou alocada dinamicamente. Cuidado: nunca retorne ponteiro para variável local (ela deixa de existir após a função). Exemplo seguro:

#include <stdlib.h>

int* cria_vetor(int tamanho) {
    int *v = malloc(tamanho * sizeof(int));
    return v;
}

int main() {
    int *vetor = cria_vetor(10);
    if (vetor) {
        // usar vetor
        free(vetor);
    }
    return 0;
}

Exemplos

Exemplo 1: Função que calcula estatísticas

Uma função que recebe um vetor e retorna a soma e a média via ponteiros:

void estatisticas(int v[], int n, int *soma, float *media) {
    *soma = 0;
    for (int i = 0; i < n; i++)
        *soma += v[i];
    *media = (float)*soma / n;
}

int main() {
    int dados[] = {10, 20, 30, 40, 50};
    int s;
    float m;
    estatisticas(dados, 5, &s, &m);
    printf("Soma: %d, Media: %.2f\n", s, m);
    return 0;
}

Exemplo 2: Busca em vetor com retorno de posição

Função que busca um valor e retorna o índice via ponteiro, e retorna 1 se encontrado:

int busca(int v[], int n, int chave, int *indice) {
    for (int i = 0; i < n; i++) {
        if (v[i] == chave) {
            *indice = i;
            return 1;
        }
    }
    return 0;
}

int main() {
    int arr[] = {3, 7, 1, 9, 4};
    int pos;
    if (busca(arr, 5, 9, &pos))
        printf("Encontrado no indice %d\n", pos);
    else
        printf("Nao encontrado\n");
    return 0;
}

Boas práticas

Sempre verifique se o ponteiro não é NULL antes de desreferenciar, especialmente em funções que recebem ponteiros. Use nomes claros para parâmetros ponteiro (por exemplo, pResultado). Documente se o parâmetro é de entrada, saída ou ambos. Evite retornar ponteiros para variáveis locais; prefira alocação dinâmica ou parâmetros de saída.

Referências

Exercícios

  1. Escreva uma função dobra que recebe um ponteiro para inteiro e dobra o valor apontado. Teste no main.
  2. ✓ Resposta:
    #include <stdio.h>
    
    void dobra(int *p) {
        *p *= 2;
    }
    
    int main() {
        int x = 5;
        dobra(&x);
        printf("%d\n", x);  // 10
        return 0;
    }
  3. Implemente uma função min_max que recebe um vetor de inteiros, seu tamanho, e dois ponteiros para inteiro (min e max). A função deve calcular o menor e o maior valor do vetor e armazená-los nos locais apontados.
  4. ✓ Resposta:
    #include <stdio.h>
    
    void min_max(int v[], int n, int *min, int *max) {
        *min = *max = v[0];
        for (int i = 1; i < n; i++) {
            if (v[i] < *min) *min = v[i];
            if (v[i] > *max) *max = v[i];
        }
    }
    
    int main() {
        int arr[] = {4, 2, 9, 1, 7};
        int menor, maior;
        min_max(arr, 5, &menor, &maior);
        printf("Menor: %d, Maior: %d\n", menor, maior);
        return 0;
    }
  5. Crie uma função soma_vetor que recebe um vetor e seu tamanho, e retorna a soma via ponteiro (a função retorna void). Teste.
  6. ✓ Resposta:
    #include <stdio.h>
    
    void soma_vetor(int v[], int n, int *soma) {
        *soma = 0;
        for (int i = 0; i < n; i++)
            *soma += v[i];
    }
    
    int main() {
        int numeros[] = {1, 2, 3, 4, 5};
        int total;
        soma_vetor(numeros, 5, &total);
        printf("Soma: %d\n", total);
        return 0;
    }
  7. Escreva uma função que recebe dois inteiros e retorna a soma e a diferença através de ponteiros. A função deve retornar 0 se a soma for par, 1 se ímpar.
  8. ✓ Resposta:
    #include <stdio.h>
    
    int opera(int a, int b, int *soma, int *diferenca) {
        *soma = a + b;
        *diferenca = a - b;
        return (*soma) % 2;
    }
    
    int main() {
        int s, d;
        int par = opera(10, 3, &s, &d);
        printf("Soma: %d, Diferenca: %d, Soma impar? %d\n", s, d, par);
        return 0;
    }
  9. Implemente uma função que recebe um ponteiro para inteiro e um inteiro n. A função deve alocar dinamicamente um vetor de n inteiros e retornar o ponteiro para o vetor alocado (via retorno da função). O parâmetro ponteiro deve ser usado para retornar o tamanho do vetor (que é n). Teste.
  10. ✓ Resposta:
    #include <stdio.h>
    #include <stdlib.h>
    
    int* cria_vetor(int n, int *tamanho) {
        *tamanho = n;
        int *v = malloc(n * sizeof(int));
        return v;
    }
    
    int main() {
        int tam;
        int *vet = cria_vetor(5, &tam);
        if (vet) {
            printf("Vetor alocado com %d elementos\n", tam);
            free(vet);
        }
        return 0;
    }