Call by Value, Call by Reference, ponteiros como parâmetros e retorno múltiplo
Na Aula 13 você aprendeu a declarar, definir e chamar funções. Agora vamos entender como os dados entram e saem delas — que é onde C se diferencia radicalmente do Java.
| Tipo | Retorno | Parâmetros | Quando usar |
|---|---|---|---|
void f(void) |
Nenhum | Nenhum | Ação pura (ex: imprimir cabeçalho) |
void f(int x) |
Nenhum | Por valor | Ação que usa dados mas não precisa devolver resultado |
int f(int x) |
Um valor | Por valor | Cálculo simples com um resultado |
void f(int *x) |
Nenhum (via ponteiro) | Por referência | Modificar variáveis externas ou retornar múltiplos resultados |
return: Uma função em C só pode retornar um único valor com return. Para "retornar" múltiplos resultados, a solução é passar ponteiros como parâmetros — a função escreve os resultados diretamente na memória dos argumentos.
Antes de mergulhar nos exemplos, vale fixar os dois nomes que você verá em qualquer livro de C, livro de algoritmos ou entrevista técnica:
int, float, char…).
&variavel) e usando o operador de desreferência (*) dentro da função.
| Mecanismo | O que é passado | Pode modificar o original? | Sintaxe na chamada | Sintaxe no parâmetro |
|---|---|---|---|---|
| Call by Value | Cópia do valor | ❌ Não | f(x) |
void f(int x) |
| Call by Reference | Endereço de memória | ✅ Sim | f(&x) |
void f(int *x) |
int, double…) — igual ao C. Para objetos, Java passa a referência por valor (cópia do endereço), permitindo modificar o conteúdo do objeto mas não a referência em si. C é mais explícito: você decide manualmente quando usar ponteiros.
#include <stdio.h> /* Call by Value: recebe cópia, retorna novo valor */ int somarDez_valor(int numero) { return numero + 10; // não altera o original } /* Call by Reference: recebe endereço, modifica diretamente */ void somarDez_referencia(int *numero) { *numero = *numero + 10; // modifica no endereço apontado } int main() { int a = 5; printf("a = %d\n", a); // 5 a = somarDez_valor(a); // precisa reatribuir o retorno printf("após valor: a = %d\n", a); // 15 somarDez_referencia(&a); // &a = endereço de a printf("após referência: a = %d\n", a); // 25 return 0; }
Quando você escreve f(x), C cria uma cópia de x na stack da função. A variável original fica completamente isolada.
#include <stdio.h>
void tentaDobrar(int n) {
n = n * 2; // modifica apenas a cópia local
printf("dentro: n = %d\n", n); // imprime 10
}
int main() {
int n = 5;
tentaDobrar(n);
printf("fora: n = %d\n", n); // ainda 5 — original intocado
return 0;
}
| Momento | n em main (end. 0x100) |
n em tentaDobrar (end. 0x200) |
|---|---|---|
| Antes da chamada | 5 | — |
| Ao entrar na função | 5 | 5 (cópia criada) |
Após n = n * 2 | 5 | 10 |
| Após retorno | 5 | — (destruída) |
Para que uma função modifique a variável original, passamos seu endereço. A função recebe um ponteiro e usa * para acessar o valor naquele endereço.
| Operador | Nome | Onde aparece | Significado |
|---|---|---|---|
& |
Endereço-de | Na chamada: f(&x) |
Obtém o endereço de memória de x |
*tipo |
Declaração de ponteiro | No parâmetro: int *p |
Declara que p é um ponteiro para int |
*ponteiro |
Desreferência | No corpo: *p = 10 |
Acessa/modifica o valor no endereço apontado por p |
#include <stdio.h>
void dobrar(int *p) {
*p = *p * 2; // lê o valor em 0x100, multiplica por 2, salva de volta
}
int main() {
int x = 5;
dobrar(&x); // passa o endereço 0x100
printf("x = %d\n", x); // 10
return 0;
}
O swap é impossível com passagem por valor. Com ponteiros, funciona perfeitamente:
#include <stdio.h> /* ❌ Versão errada — por valor */ void trocar_errado(int a, int b) { int temp = a; a = b; b = temp; // troca as cópias, não os originais } /* ✅ Versão correta — por ponteiro */ 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_errado(x, y); printf("errado: x=%d y=%d\n", x, y); // ainda 10 e 20 trocar(&x, &y); printf("certo: x=%d y=%d\n", x, y); // 20 e 10 return 0; }
#include <stdio.h>
void minMax(int v[], int tam, int *minimo, int *maximo) {
*minimo = *maximo = v[0];
for (int i = 1; i < tam; i++) {
if (v[i] < *minimo) *minimo = v[i];
if (v[i] > *maximo) *maximo = v[i];
}
}
int main() {
int notas[] = {7, 3, 9, 5, 8};
int min, max;
minMax(notas, 5, &min, &max);
printf("Mínimo: %d Máximo: %d\n", min, max);
return 0;
}
Arrays em C têm um comportamento especial: ao serem passados para uma função, decaem automaticamente para um ponteiro para o primeiro elemento. Você nunca precisa (nem consegue) passar um array por valor.
#include <stdio.h>
void dobrarTodos(int *v, int tam) {
for (int i = 0; i < tam; i++)
v[i] *= 2; // modifica o array original — sem & necessário na chamada
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
dobrarTodos(nums, 5); // nums já é o endereço do [0]
for (int i = 0; i < 5; i++)
printf("%d ", nums[i]);
printf("\n");
return 0;
}
dobrarTodos(nums, 5) é equivalente a dobrarTodos(&nums[0], 5). O nome do array decai para um ponteiro para o primeiro elemento. Por isso a função já recebe o endereço original e pode modificar todos os elementos.
const int *v quando a função só precisa ler o array — o compilador avisa se você tentar modificá-lo por acidente.
Ao passar um array para uma função, o ponteiro permite tanto lê-lo quanto modificá-lo diretamente. Use const quando quiser garantir que a função não alterará os dados:
#include <stdio.h> /* Apenas lê o array — const protege contra modificação acidental */ void printArray(const int *v, int tam) { int i; for (i = 0; i < tam; i++) printf("%d ", v[i]); printf("\n"); } /* Modifica o array original — incrementa cada elemento */ void incrementarTodos(int *v, int tam) { int i; for (i = 0; i < tam; i++) v[i] = v[i] + 1; // equivalente a *(v+i) = *(v+i) + 1 } int main() { int notas[5] = {6, 7, 8, 5, 9}; printf("Antes: "); printArray(notas, 5); incrementarTodos(notas, 5); // sem & — arrays já são ponteiros printf("Depois: "); printArray(notas, 5); return 0; }
&? Arrays em C decaem automaticamente para o ponteiro do primeiro elemento quando passados para funções. Escrever printArray(notas, 5) já é equivalente a printArray(¬as[0], 5). Use const int *v no parâmetro quando a função não precisar modificar o array — isso documenta a intenção e o compilador avisa se você tentar modificar por acidente.
| Tipo de retorno | Exemplo | Uso típico |
|---|---|---|
void |
void imprimirSeparador(void) |
Ação sem resultado |
| Tipo primitivo | int ehPrimo(int n) |
Cálculo simples |
Código de erro (int) |
int salvarArquivo(...) |
0 = sucesso, -1 = erro |
| Múltiplos (via ponteiro) | void minMax(int[], int, int*, int*) |
Quando precisar de mais de um resultado |
#include <stdio.h>
int ehPrimo(int n) {
if (n < 2) return 0; // saída antecipada
for (int i = 2; i * i <= n; i++)
if (n % i == 0) return 0;
return 1;
}
/* Padrão código-de-erro: 0 = OK, -1 = falha */
int dividir(int a, int b, float *resultado) {
if (b == 0) return -1; // indica erro ao chamador
*resultado = (float)a / b;
return 0; // indica sucesso
}
int main() {
printf("7 é primo? %s\n", ehPrimo(7) ? "Sim" : "Não");
printf("4 é primo? %s\n", ehPrimo(4) ? "Sim" : "Não");
float res;
if (dividir(10, 3, &res) == 0)
printf("10 / 3 = %.2f\n", res);
else
printf("Erro: divisão por zero!\n");
dividir(5, 0, &res); // retorna -1, res não é modificado
return 0;
}
/* ❌ ERRADO — retorna endereço de variável local destruída */ int* funcaoErrada() { int x = 42; return &x; // x deixa de existir após o return! }
Reunindo os quatro tipos de função num único programa:
#include <stdio.h> #include <math.h> /* void sem parâmetros */ void imprimirSeparador(void) { printf("──────────────────────\n"); } /* retorno simples */ double calcularHipotenusa(double a, double b) { return sqrt(a * a + b * b); } /* retorno como código: 1=acutangulo 2=retangulo 3=obtusangulo 0=invalido */ int classificarTriangulo(double a, double b, double c) { if (a <= 0 || b <= 0 || c <= 0) return 0; if (a + b <= c || a + c <= b || b + c <= a) return 0; double maior2 = (a>b && a>c) ? a*a : (b>c ? b*b : c*c); double somaQuad = a*a + b*b + c*c - maior2; if (maior2 < somaQuad) return 1; if (maior2 == somaQuad) return 2; return 3; } /* múltiplos resultados via ponteiro */ void estatisticasVetor(const float v[], int tam, float *media, float *maximo, float *minimo) { *media = *maximo = *minimo = v[0]; for (int i = 1; i < tam; i++) { *media += v[i]; if (v[i] > *maximo) *maximo = v[i]; if (v[i] < *minimo) *minimo = v[i]; } *media /= tam; } int main() { imprimirSeparador(); printf("Hipotenusa (3,4): %.2f\n", calcularHipotenusa(3, 4)); imprimirSeparador(); const char *tipos[] = {"Inválido","Acutângulo","Retângulo","Obtusângulo"}; printf("Triângulo 3-4-5: %s\n", tipos[classificarTriangulo(3,4,5)]); printf("Triângulo 5-5-5: %s\n", tipos[classificarTriangulo(5,5,5)]); imprimirSeparador(); float notas[] = {7.5f, 8.0f, 6.0f, 9.5f, 5.5f}; float med, mx, mn; estatisticasVetor(notas, 5, &med, &mx, &mn); printf("Média: %.1f | Máx: %.1f | Mín: %.1f\n", med, mx, mn); return 0; }
| Aspecto | C | Java |
|---|---|---|
| Passagem de primitivos | Sempre por valor (cópia) | Sempre por valor (cópia) |
| Modificar variável externa | Ponteiro explícito (&x, *p) |
Impossível para primitivos; objetos podem ser modificados via referência |
| Retornar múltiplos valores | Ponteiros como parâmetros de saída | Retornar objeto/array contendo os valores |
| Swap de dois inteiros | ✅ Possível com ponteiros | ❌ Impossível para primitivos — Java não tem ponteiros explícitos |
| Arrays passados para funções | Sempre por ponteiro (decay automático) | Por referência automática |
| Proteger array de modificação | const int *v |
Não há equivalente direto para arrays primitivos |
void trocar(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}
// uso:
trocar(&x, &y);
void trocar(int a, int b) {
int t = a;
a = b;
b = t;
// x e y fora do método
// continuam inalterados
}