Funções que chamam a si mesmas — caso base, caso recursivo e a pilha de chamadas
Até agora todas as funções que escrevemos chamavam outras funções. Recursão é a técnica em que uma função chama a si mesma para resolver uma versão menor do mesmo problema, repetindo esse processo até chegar a um caso simples que pode ser resolvido diretamente.
tipo funcaoRecursiva(parametros) {
/* CASO BASE — condição de parada: resolve sem chamar a si mesma */
if (condicaoDeParada)
return valorSimples;
/* CASO RECURSIVO — chama a si mesma com problema menor */
return funcaoRecursiva(parametrosMenores);
}
Antes de exemplos matemáticos, vamos ver recursão da forma mais visual possível: uma contagem regressiva.
#include <stdio.h>
void contagem(int n) {
/* Caso base: quando n chega a 0, encerra */
if (n == 0) {
printf("Fim!\n");
return;
}
/* Caso recursivo: imprime n e chama com n-1 */
printf("%d...\n", n);
contagem(n - 1);
}
int main() {
contagem(5);
return 0;
}
| Chamada | n | Ação | Próxima chamada |
|---|---|---|---|
| 1ª | 5 | Imprime "5..." | contagem(4) |
| 2ª | 4 | Imprime "4..." | contagem(3) |
| 3ª | 3 | Imprime "3..." | contagem(2) |
| 4ª | 2 | Imprime "2..." | contagem(1) |
| 5ª | 1 | Imprime "1..." | contagem(0) |
| 6ª | 0 | Imprime "Fim!" e retorna | — caso base atingido |
printf aqui vem antes da chamada recursiva — por isso os números aparecem em ordem decrescente (5, 4, 3...). Se você movesse o printf para depois da chamada, a ordem seria crescente (1, 2, 3...) porque os printf só executariam ao retornar de cada nível. Experimente!
Cada chamada de função reserva um espaço na pilha de memória (stack) para armazenar seus parâmetros e variáveis locais. Com recursão, várias chamadas ficam empilhadas ao mesmo tempo, aguardando umas às outras:
Estado da pilha durante contagem(3) | |
|---|---|
contagem(0) ← topo — em execução (caso base) | |
contagem(1) ← aguardando retorno | |
contagem(2) ← aguardando retorno | |
contagem(3) ← aguardando retorno | |
main() ← base da pilha |
Após o caso base retornar, cada chamada desempilha em ordem inversa: contagem(0) retorna para contagem(1), que retorna para contagem(2), e assim por diante até voltar ao main.
O fatorial de um número inteiro positivo n é o produto de todos os inteiros de 1 até n. A definição matemática já é naturalmente recursiva:
#include <stdio.h>
long fatorial(int n) {
/* Caso base */
if (n == 0) return 1;
/* Caso recursivo: n! = n × (n-1)! */
return n * fatorial(n - 1);
}
int main() {
for (int i = 0; i <= 10; i++)
printf("%2d! = %ld\n", i, fatorial(i));
return 0;
}
fatorial(4) passo a passo| Chamada | Cálculo | Retorna |
|---|---|---|
fatorial(4) | 4 × fatorial(3) | aguarda... |
fatorial(3) | 3 × fatorial(2) | aguarda... |
fatorial(2) | 2 × fatorial(1) | aguarda... |
fatorial(1) | 1 × fatorial(0) | aguarda... |
fatorial(0) | caso base! | 1 |
↩ fatorial(1) | 1 × 1 | 1 |
↩ fatorial(2) | 2 × 1 | 2 |
↩ fatorial(3) | 3 × 2 | 6 |
↩ fatorial(4) | 4 × 6 | 24 |
A sequência de Fibonacci começa com 0 e 1, e cada número seguinte é a soma dos dois anteriores: 0, 1, 1, 2, 3, 5, 8, 13, 21... É um exemplo com dois casos base e duas chamadas recursivas:
#include <stdio.h>
long fib(int n) {
/* Dois casos base */
if (n == 0) return 0;
if (n == 1) return 1;
/* Caso recursivo */
return fib(n - 1) + fib(n - 2);
}
int main() {
for (int i = 0; i <= 12; i++)
printf("fib(%2d) = %ld\n", i, fib(i));
return 0;
}
fib(4) — o problema do retrabalho
fib(4)
/ \
fib(3) fib(2)
/ \ / \
fib(2) fib(1) fib(1) fib(0)
/ \
fib(1) fib(0)
fib(2) é calculada duas vezes, fib(1) três vezes. Para fib(40) o número de chamadas passa de 300 milhões. Em aplicações reais, use a versão iterativa ou a técnica de memoização (guardar resultados já calculados). Aqui o objetivo é apenas ilustrar a estrutura recursiva com dois casos base.
#include <stdio.h>
int somaVetor(int v[], int n) {
if (n == 0) return 0; // vetor vazio: soma é 0
return v[0] + somaVetor(v + 1, n - 1); // primeiro + soma do resto
}
int main() {
int notas[] = {7, 8, 6, 9, 5};
int total = somaVetor(notas, 5);
printf("Soma: %d | Media: %.1f\n", total, total / 5.0f);
return 0;
}
#include <stdio.h>
double potencia(double base, int exp) {
if (exp == 0) return 1.0; // base^0 = 1
return base * potencia(base, exp - 1); // base^n = base × base^(n-1)
}
int main() {
printf("2^10 = %.0f\n", potencia(2, 10));
printf("3^5 = %.0f\n", potencia(3, 5));
printf("1.5^4 = %.4f\n", potencia(1.5, 4));
return 0;
}
Um dos algoritmos mais antigos da matemática — e um dos exemplos mais elegantes de recursão. A ideia: mdc(a, b) = mdc(b, a % b), com caso base mdc(a, 0) = a.
#include <stdio.h>
int mdc(int a, int b) {
if (b == 0) return a; // caso base: MDC(a, 0) = a
return mdc(b, a % b); // caso recursivo
}
int main() {
printf("MDC(48, 18) = %d\n", mdc(48, 18)); // 6
printf("MDC(100, 75) = %d\n", mdc(100, 75)); // 25
printf("MDC(17, 5) = %d\n", mdc(17, 5)); // 1 — primos entre si
return 0;
}
mdc(48, 18) passo a passo| Chamada | a | b | a % b | Próxima |
|---|---|---|---|---|
mdc(48, 18) | 48 | 18 | 12 | mdc(18, 12) |
mdc(18, 12) | 18 | 12 | 6 | mdc(12, 6) |
mdc(12, 6) | 12 | 6 | 0 | mdc(6, 0) |
mdc(6, 0) | 6 | 0 | — | caso base! retorna 6 |
#include <stdio.h>
void imprimirDigitos(int n) {
if (n < 10) { // caso base: um único dígito
printf("%d ", n);
return;
}
imprimirDigitos(n / 10); // imprime todos exceto o último
printf("%d ", n % 10); // depois imprime o último
}
int main() {
printf("Digitos de 7483: ");
imprimirDigitos(7483);
printf("\n");
return 0;
}
Qualquer problema resolvível com recursão também pode ser resolvido com loops, e vice-versa. A escolha depende de qual deixa o código mais claro e eficiente para aquele problema específico.
| Critério | Recursão | Iteração (loop) |
|---|---|---|
| Problemas naturalmente hierárquicos (árvores, grafos) | ✅ Natural e elegante | ❌ Exige pilha manual — muito complexo |
| Legibilidade para divisão em subproblemas | ✅ Espelha a definição matemática | ⚠️ Pode ser mais verboso |
| Uso de memória | ⚠️ Cada chamada usa a stack | ✅ Apenas as variáveis do loop |
| Risco de stack overflow | ⚠️ Sim, em recursões profundas | ✅ Não |
| Desempenho geral | ⚠️ Overhead de chamadas de função | ✅ Geralmente mais rápida |
| Problemas de sequência simples | ⚠️ Funciona, mas é desnecessário | ✅ Natural e direto |
long fatorial(int n) {
if (n == 0) return 1;
return n * fatorial(n - 1);
}
✅ Idêntico à notação matemática.
⚠️ n chamadas empilhadas.
long fatorial(int n) {
long r = 1;
for (int i = 2; i <= n; i++)
r *= i;
return r;
}
✅ Memória mínima (O(1)).
✅ Sem risco de stack overflow.
| Aspecto | C | Java |
|---|---|---|
| Sintaxe da recursão | Idêntica — função chama a si mesma | Idêntica — método chama a si mesmo |
| Tamanho padrão da stack | Tipicamente 1–8 MB (dependente do SO) | 512 KB a 1 MB por thread (configurável com -Xss) |
| Stack overflow | Falha com sinal do SO (segfault) | Lança StackOverflowError (capturável) |
| Recursão mútua (A chama B, B chama A) | Requer protótipos antecipados para A e B | Métodos podem se chamar mutuamente sem declaração prévia |
long fatorial(int n) {
if (n == 0) return 1;
return n * fatorial(n - 1);
}
static long fatorial(int n) {
if (n == 0) return 1;
return n * fatorial(n - 1);
}
A estrutura é praticamente idêntica. A única diferença é que em Java o método precisa ser static para ser chamado sem instância de objeto, enquanto em C funções já são naturalmente estáticas no sentido de pertencer ao arquivo.