Ir para o conteúdo

CCT-UFCA/Ciência da Computação/Introdução à Programação/Função e Recursividade

De Wikiversidade

Função e Recursividade

[editar | editar código]

Ao desenvolver programas, é comum que determinadas tarefas precisem ser executadas diversas vezes em momentos diferentes. Escrever o mesmo bloco de código repetidamente, além de tornar o programa mais extenso e difícil de entender, aumenta a chance de erros e dificulta sua manutenção. Para resolver esse problema, as linguagens de programação oferecem as funções, que permitem organizar o código em blocos reutilizáveis e bem definidos.

Na linguagem C, uma função é um conjunto de instruções que executa uma tarefa específica e pode ser chamado de diferentes partes do programa, sempre que for necessário realizar essa mesma tarefa. Além de tornar o código mais organizado e legível, o uso de funções permite dividir problemas complexos em partes menores e mais fáceis de resolver.

Definição e Estrutura de uma Função em C

[editar | editar código]

Cada função em C possui três elementos fundamentais: o tipo de dado que ela retorna, o seu nome e uma lista (possivelmente vazia) de parâmetros, que são os dados necessários para que a função realize sua tarefa. A definição de uma função também inclui seu corpo, ou seja, o bloco de código que contém as instruções a serem executadas.

A estrutura básica de uma função é a seguinte:

tipo_de_retorno nome_da_funcao(parâmetros) {
    // corpo da função
    return valor;
}

O tipo de retorno indica o tipo de dado que a função irá devolver ao seu chamador, como int, foat, char, entre outros. Se a função não precisar retornar nenhum valor, utiliza-se o tipo void. O nome da função é um identificador que permite referenciar e executar aquele bloco de código. Já os parâmetros, colocados entre parênteses, são variáveis que recebem valores na chamada da função e podem ser utilizados dentro do seu corpo. Veja um exemplo concreto do que foi visto até aqui:

#include <stdio.h>

int soma(int a, int b) {
    return a + b;
}

void printar_resultado(double numero) {
    printf("%.3f", numero);
}

int main() {
    
    int resultado = soma(5, 7);
    printar_resultado(resultado);
    
    return 0;
}

O programa faz a soma de 5 e 7 utilizando a função soma e exibe o resultado utilizando a função printar_resultado. Vamos analizar melhor essas funções:

A função possui retorno do tipo int e é nomeada como main. Essa é a função principal, obrigatória em todos os programas escritos na linguagem C. O corpo dessa função consiste em uma chamada para a função soma, passando os valores 5 e 7 como argumentos. O retorno dessa chamada é armazenado na variável resultado e, em seguida, passado como argumento para a função printar_resultado.

Além disso, na linha 16, há a palavra reservada return seguida de 0, que indica que o programa foi executado com sucesso, sem erros.

⚠️ Observe que resultado é uma variável do tipo int, enquanto a função printar_resultado recebe um argumento do tipo double. Nesse caso, ocorre uma conversão implícita de tipo (coerção), ou seja, o valor inteiro é convertido automaticamente para double na chamada da função.

A função soma possui retorno do tipo int e é responsável por calcular e retornar a soma de dois números inteiros recebidos como parâmetros (a e b).

printar_resultado:

[editar | editar código]

A função printar_resultado possui retorno do tipo void, ou seja, ela não retorna nenhum valor. Por esse motivo, não utiliza a palavra reservada return com valores, embora seja possível utilizar apenas return; para encerrar a execução da função antecipadamente.

Veja um exemplo de como ela é estruturada:

void printar_resultado(double numero) {
    printf("%.3f", numero);
    return;
}

Atenção: não é permitido adicionar nenhuma variável nem valor após a palavra return em funções do tipo void. Apenas return; isolado pode ser utilizado, se necessário.

Declaração de Protótipos

[editar | editar código]

Quando uma função é utilizada antes de sua definição no código, é necessário informar ao compilador sua existência por meio de um protótipo. O protótipo é uma declaração que especifica o tipo de retorno, o nome da função e os tipos dos parâmetros, mas sem seu corpo. Ele permite que o compilador conheça a assinatura da função antes de sua implementação.

Por exemplo:

#include <stdio.h>

int soma(int a, int b);
void printar_resultado(double numero);

int main() {
    
    int resultado = soma(5, 7);
    printar_resultado(resultado);
    
    return 0;
}

int soma(int a, int b) {
    return a + b;
}

void printar_resultado(double numero) {
    printf("%.3f", numero);
}

Escopo e Tempo de Vida de Variáveis

[editar | editar código]

Ao trabalhar com funções, é fundamental compreender o conceito de escopo, que define onde uma variável pode ser acessada dentro do código. Variáveis declaradas dentro de uma função são chamadas de variáveis locais, e só existem durante a execução dessa função. Por outro lado, variáveis declaradas fora de qualquer função possuem escopo global e podem ser acessadas por qualquer parte do programa.

Outro conceito relacionado é o tempo de vida das variáveis. Variáveis locais possuem um tempo de vida limitado à execução da função. Sempre que a função é chamada, a variável é criada, e quando a função termina, a variável é destruída. Se for necessário manter o valor de uma variável local entre diferentes chamadas da função, utiliza-se o modificador static. Isso faz com que a variável seja criada uma única vez e preserve seu valor entre as execuções da função.

Recursividade

[editar | editar código]

A recursividade é um conceito importante na programação, no qual uma função chama a si mesma para resolver um problema. Essa abordagem é particularmente útil para problemas que podem ser divididos em subproblemas menores e semelhantes ao problema original.

Como funciona a recursividade:

[editar | editar código]

Uma função recursiva precisa obrigatoriamente conter dois elementos fundamentais:

  • Caso base: uma condição que encerra as chamadas recursivas e impede que a função se chame indefinidamente.
  • Chamada recursiva: é a situação em que a função se chama novamente, mas com um problema menor.

Sem o caso base, a recursividade se tornaria infinita, causando um erro chamado estouro de pilha, que acontece quando a memória destinada às chamadas de funções (a pilha de execução) se esgota.

Exemplos:

[editar | editar código]
Fibonacci
[editar | editar código]

A sequência de Fibonacci é uma série de números em que cada número é a soma dos dois anteriores, começando por 0 e 1. A definição matemática é:

  • fibonacci(0) = 0
  • fibonacci(1) = 1
  • fibonacci(2) = 1 + 0 = 1
  • fibonacci(3) = fibonacci(2) + fibonacci(1) = 2
  • fibonacci(4) = fibonacci(3) + fibonacci(2) = 3
  • ...

A definição matemática é:

  • fibonacci(0) = 0
  • fibonacci(1) = 1
  • fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)

Em C ficaria:

int fibonacci(int n) {
    if (n == 0) return 0;
    if (n == 1) return 1;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

O fatorial de um número inteiro n é o produto de todos os números inteiros positivos menores ou iguais a n. Por definição, fatorial de 0 é 1.

A definição matemática é:

  • fatorial(0) = 1 (caso base)
  • fatorial(n) = n × fatorial(n-1) (chamada recursiva)

Em C ficaria:

int fatorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n * fatorial(n - 1);
    }
}

Vantagens, Desvantagens e Cuidados com a Recursividade

[editar | editar código]

A recursividade permite resolver problemas de forma mais elegante e próxima da lógica matemática, sendo especialmente útil em algoritmos que lidam com estruturas hierárquicas, como árvores e grafos. No entanto, ela pode ser menos eficiente em termos de tempo e uso de memória, devido ao alto custo das chamadas de função empilhadas. Sempre que possível, uma solução iterativa tende a ser mais eficiente na linguagem C.

Ao utilizar recursão, é fundamental garantir a existência de um caso base bem definido e que cada chamada recursiva reduza o problema, aproximando-o desse caso base. Além disso, é importante avaliar se a recursão realmente traz benefícios, pois, em muitos casos, uma abordagem com laços (for, while) pode ser mais adequada e performática.