Em C, funções normalmente recebem argumentos por valor, ou seja, uma cópia do dado é passada. No entanto, com ponteiros é possível simular a passagem por referência, permitindo que a função modifique diretamente as variáveis originais. Isso é essencial para escrever código eficiente e para retornar múltiplos valores.

Nesta aula, veremos como declarar e usar ponteiros como parâmetros de funções, como alterar valores através deles e como retornar resultados indiretamente. Ao final, você será capaz de implementar funções que manipulam dados externos de forma segura e eficaz.

Passagem por referência

Em C, toda passagem de argumento para função é feita por valor. Isso significa que a função recebe uma cópia do valor da variável passada. Qualquer alteração feita no parâmetro dentro da função não afeta a variável original. Para contornar essa limitação, usamos ponteiros: passamos o endereço da variável (um ponteiro) e, dentro da função, acessamos e modificamos o conteúdo desse endereço.

Para declarar um parâmetro como ponteiro, usamos o operador * na declaração. Por exemplo: void func(int *p). Dentro da função, para acessar o valor apontado, usamos o operador de desreferência *p. Ao chamar a função, passamos o endereço da variável com o operador &: func(&var).

#include <stdio.h>

void dobrar(int *num) {
    *num = *num * 2;
}

int main() {
    int valor = 5;
    printf("Antes: %d\n", valor);
    dobrar(&valor);
    printf("Depois: %d\n", valor);
    return 0;
}

No exemplo, a função dobrar recebe um ponteiro para int. Dentro dela, *num acessa o valor original e o modifica. Como passamos o endereço de valor com &valor, a alteração persiste após a chamada.

Alterando valores

Alterar valores via ponteiro é útil para modificar múltiplas variáveis em uma única função, economizar memória (evitando cópias de grandes estruturas) e implementar algoritmos como troca de valores (swap).

O padrão é: a função recebe ponteiros para as variáveis a serem alteradas. Dentro, usamos * para ler e escrever nos endereços. É importante lembrar que o ponteiro em si é passado por valor (cópia do endereço), mas o conteúdo apontado é compartilhado.

#include <stdio.h>

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

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

A função trocar recebe dois ponteiros. A variável temp armazena o valor de *a, depois *a recebe *b e *b recebe temp. Assim, os valores originais são trocados.

Retornando via ponteiro

Funções em C podem retornar apenas um valor usando return. Para retornar múltiplos valores, uma técnica comum é usar parâmetros ponteiro: a função recebe endereços de variáveis onde os resultados serão armazenados. Dessa forma, a função pode “retornar” vários valores indiretamente.

Por exemplo, uma função que calcula o quociente e o resto de uma divisão pode preencher duas variáveis passadas por ponteiro, enquanto retorna um código de erro (ou void).

#include <stdio.h>

int divisao(int dividendo, int divisor, int *quociente, int *resto) {
    if (divisor == 0) return -1; // erro
    *quociente = dividendo / divisor;
    *resto = dividendo % divisor;
    return 0; // sucesso
}

int main() {
    int q, r;
    if (divisao(10, 3, &q, &r) == 0) {
        printf("Quociente: %d, Resto: %d\n", q, r);
    } else {
        printf("Erro: divisão por zero\n");
    }
    return 0;
}

A função divisao recebe dois ponteiros para int (quociente e resto). Ela preenche esses endereços com os resultados e retorna um valor indicando sucesso ou erro. Assim, conseguimos obter dois resultados de uma só vez.

Exemplos

Vamos ver mais exemplos práticos que combinam os conceitos.

Exemplo 1: Função que incrementa um valor

#include <stdio.h>

void incrementar(int *p) {
    (*p)++;
}

int main() {
    int contador = 0;
    incrementar(&contador);
    printf("Contador: %d\n", contador);
    return 0;
}

Exemplo 2: Função que preenche um array via ponteiro

#include <stdio.h>

void preencher(int *arr, int tamanho, int valor) {
    for (int i = 0; i < tamanho; i++) {
        arr[i] = valor + i;
    }
}

int main() {
    int vetor[5];
    preencher(vetor, 5, 10);
    for (int i = 0; i < 5; i++) {
        printf("%d ", vetor[i]);
    }
    printf("\n");
    return 0;
}

Exemplo 3: Retornando o maior e menor de três números

#include <stdio.h>

void maiorMenor(int a, int b, int c, int *maior, int *menor) {
    *maior = a;
    *menor = a;
    if (b > *maior) *maior = b;
    if (b < *menor) *menor = b;
    if (c > *maior) *maior = c;
    if (c < *menor) *menor = c;
}

int main() {
    int x=5, y=9, z=3, max, min;
    maiorMenor(x, y, z, &max, &min);
    printf("Maior: %d, Menor: %d\n", max, min);
    return 0;
}

Boas práticas

Ao usar ponteiros em funções, verifique se o ponteiro não é nulo antes de desreferenciá-lo (especialmente se o ponteiro pode vir de uma fonte externa). Use const para parâmetros que não serão modificados (ex.: void func(const int *p)). Documente claramente se a função modifica os valores apontados. Prefira passar estruturas grandes por ponteiro para evitar cópias desnecessárias.

Referências

Exercícios

  1. Escreva uma função chamada atualizar que recebe um ponteiro para inteiro e dobra o valor apontado. Teste com uma variável na main.

    ✓ Resposta:
    #include <stdio.h>
    
    void atualizar(int *p) {
        *p = *p * 2;
    }
    
    int main() {
        int num = 7;
        atualizar(&num);
        printf("Valor atualizado: %d\n", num);
        return 0;
    }
  2. Crie uma função somaProduto que recebe dois inteiros e dois ponteiros para inteiro, e armazena neles a soma e o produto dos dois números. Teste.

    ✓ Resposta:
    #include <stdio.h>
    
    void somaProduto(int a, int b, int *soma, int *produto) {
        *soma = a + b;
        *produto = a * b;
    }
    
    int main() {
        int x=3, y=4, s, p;
        somaProduto(x, y, &s, &p);
        printf("Soma: %d, Produto: %d\n", s, p);
        return 0;
    }
  3. Implemente uma função trocarFloat que troca os valores de duas variáveis float usando ponteiros.

    ✓ Resposta:
    #include <stdio.h>
    
    void trocarFloat(float *a, float *b) {
        float temp = *a;
        *a = *b;
        *b = temp;
    }
    
    int main() {
        float x=1.5, y=2.7;
        trocarFloat(&x, &y);
        printf("x=%.1f, y=%.1f\n", x, y);
        return 0;
    }
  4. Escreva uma função que recebe um array de inteiros e seu tamanho, e retorna (via ponteiro) a soma e a média dos elementos. A função deve retornar void.

    ✓ Resposta:
    #include <stdio.h>
    
    void somaMedia(int arr[], int n, int *soma, float *media) {
        *soma = 0;
        for (int i = 0; i < n; i++) {
            *soma += arr[i];
        }
        *media = (float)*soma / n;
    }
    
    int main() {
        int vet[] = {1,2,3,4,5};
        int s;
        float m;
        somaMedia(vet, 5, &s, &m);
        printf("Soma: %d, Média: %.2f\n", s, m);
        return 0;
    }
  5. Implemente uma função que recebe um ponteiro para um caractere e converte o caractere para maiúsculo (se for minúsculo). Use a tabela ASCII. Teste com uma letra minúscula.

    ✓ Resposta:
    #include <stdio.h>
    
    void paraMaiusculo(char *c) {
        if (*c >= 'a' && *c <= 'z') {
            *c = *c - 32;
        }
    }
    
    int main() {
        char letra = 'b';
        paraMaiusculo(&letra);
        printf("Maiúsculo: %c\n", letra);
        return 0;
    }