Introdução às funções

From Applied Science

Não tem muito como fugir, mas função num programa é análoga a função na matemática. No ensino médio, em algum momento aprendemos que f(x) = x2 é uma função que, para cada x, corresponde um x2. Você também deve se lembrar que uma função só é uma função se para cada x, há apenas um y correspondente. Da definição matemática: função pode ter argumentos diferentes e devolver o mesmo resultado (exemplo, uma função constante). Mas uma função não pode, para um mesmo argumento, devolver dois resultados diferentes. Isto não é uma função. Assim é impossível uma função receber um valor e devolver um resultado diferente em diferentes chamadas ou mais de um resultado diferente ao mesmo tempo. Apenas uma ressalva: até o ensino médio e o primeiro semestre de cálculo todas as funções na matemática são de uma variável; é somente mais tarde que se começam a estudar funções de mais de uma variável, mas isso não interfere no entendimento dos múltiplos parâmetros das funções na computação. No caso da computação existem alguns outros detalhes técnicos, mas por enquanto vamos começar do exemplo mais simples possível.

Suponha que você pegue a fórmula de Bháskara e escreva um algoritmo que encontre as raízes da equação de segundo grau. No seu programa, se você precisar desse algoritmo mais de uma vez, o seu código vai ficar confuso e grande. Ao invés disso, use uma função. Toda vez que você precisar da fórmula de Bháskara, chame a função. Você pode encarar funções como fórmulas para fazer alguma coisa. Você reaproveita a fórmula quando precisar.

Na disciplina de introdução a recomendação usual é que você pratique com muitos problemas que precisem de funções, porque é somente aplicando os conceitos que se entende como funcionam e como se usam funções num programa.

Erros de lógica:

  • Confundir chamada com definição de função. Chamar a função com argumentos na ordem errada ou com valores errados;
  • A função faz o programa "travar" com ou sem mensagem de erro. Aí é erro no algoritmo que você fez;
  • A função tinha que calcular a raiz de 4, mas o resultado deu 5? Um dos dois erros anteriores
  • Se tudo o mais falhar: o algoritmo pode estar totalmente correto, mas a saída na tela pode estar com um erro na chamada do printf()


É preciso entender bem o conceito de declaração de uma variável e de variável local para não ter dúvidas aqui


  • Uma função que calcula xn:
/* a função recebe a base e o expoente, calcula base^exp e devolve o 
   resultado acumulado na variável base */
int x_elevado_n (int base, int exp) {
    int i;
    max = exp;
    exp = base;
    
    for (i = 1; i < max; i++) {
         base = base * exp;
    }

    return base;
}

x0 ou 00. O algoritmo ali não funciona nesses dois casos, mas é bem fácil escrever o IF / ELSE pra consertar isso. A função também só funciona com base inteira, mas basta modificar de 'int' para 'float'. Expoente não inteiro precisa de matemática avançada para saber como calcular. Em linguagem de programação é impossível representar "meias iterações".


  • Implementação da fórmula da soma da progressão aritmética na forma de função:
float some (int n, float a1, float an) {
    return (n*(a1 + an))/2;
}

Como a fórmula é simples, não precisa guardar o resultado numa variável para devolver o valor dessa variável. Isso se deve ao fato da função ter um lugar na memória assim como uma variável, com um valor associado e uma localização bem definida.

Tente pegar o programa que testa se um número é par ou ímpar do outro capítulo e reescreva com uma função.


  • O algoritmo que faz a soma dos n primeiros números primos do capítulo dos laços de repetição, mas agora com funções:
#define SIM 1
#define NAO 0

/* Recebe um número e verifica a divisibilidade, devolvendo o valor de eh_primo
   Se encontrar divisor, eh_primo muda de valor para NAO 
   Se não encontrar, eh_primo continua com o valor SIM */ 
int teste_primo (int primo) {
    int resto, divisor, eh_primo = SIM;
    
    for (divisor = 3; divisor < primo / 2; divisor += 2) {
         resto = primo % divisor;
         
         if (resto == 0)
             eh_primo = NAO;
    }
    return eh_primo;
}

/* Se o número de primos a ser somado é um, então o resultado é imediato,
   2 sem calcular
   Senão, conte números e verifique se é primo ou não
   O número 3 é primo e pode pular o teste de divisibilidade */
int some_primos (int numero) {
     int primo, achei_mais_um = 0, soma = 0;
     
     if (numero == 1)
         return 2;
     
     else {
         for (primo = 1; achei_mais_um < numero; primo += 2) {
              if (primo == 3) {
                  achei_mais_um = 1; 
                  soma = 2;
              }
             
              else {
                  teste_primo(primo);
             }
         if (teste_primo(primo)) {
             achei_mais_um++;
             soma = soma + primo;
         }
         }
     }
     return soma;
}

Funções são usadas para organizar um programa. Uma das ideias é manter uma função simples, para que possa atender a diversas situações diferentes sem mudanças. Se uma função é específica demais, o uso será limitado a poucas situações.

Cuidado com a confusão parâmetro x argumento de função! Fica bem claro quando uma função é chamada dentro de outra. Pode passar um valor para a função interna sem passar pela função externa? Este tipo de dúvida surge quando não se entende corretamente a diferença entre definir uma função e chamá-la.


  • Soma dos termos da série: [math]\displaystyle{ 2 + \frac{2^3}{3!} + \frac{2^4}{4!} + ... + \frac{2^n}{n!} }[/math]

Versão com funções separadas:

/* a função potência pode ser a mesma função "x elevado a n" do início
   do capítulo */

/* recebe n e calcula o fatorial n! */
float fatorial (int n) {
      int i, fat = 1;
      
      if (n == 0 || n == 1) return 1;
 
      for (i = n; i > 1; i--)
           fat = fat * i;

      return fat;
}

/* calcula a soma da série 2^n/n! até a o valor da precisao recebida
   pela função */
float serie (int precisao) {
       float soma = 0;
       int i, 
           termo = 0;
    
       for (i = 1; termo > precisao; i++)
            termo = potencia(2, i)/fatorial(i);
            soma += termo;

       return soma;
}

Qual cresce mais rápido: 2n ou n! ? Para n = 10, 10! > 210. Assim, os termos a serem somados são progressivamente menores, daí a condicional ser um limite de cálculo, não um limite de contagem. Neste tipo de exercício é bom resolver primeiro um problema, o cálculo da potência ou do fatorial. Depois o outro e por último a sequência em si.

O uso das funções torna o laço da série mais simples e mais fácil de ser lido, mas em troca, a execução é muito mais lenta. A cada novo termo da série as duas funções, 'potencia' e 'fatorial', começam o cálculo desde o começo.

Se a precisão for constante, temos aí um típico exemplo de função que não precisa receber valor nenhum, uma função sem parâmetros. Mas neste caso, se o resultado é sempre o mesmo, é muito mais fácil definir o próprio resultado da soma como constante. A função principal do programa é um exemplo de função sem parâmetros, todas as variáveis que o seu programa usa são locais da função principal, o sistema operacional não envia valor nenhum para o seu programa.

Versão sem usar funções para calcular a potência e o fatorial:

/* calcula a soma da série 2^n/n! até uma certa precisão */
float serie (int precisao) {
      float soma = 2;
      int i,
          num = 2,
          den = 1;

      for (i = 1; num/den > precisao; i++) {
           num = num * 2;
           den = den * i;
           soma += num/den;
      }
}

O fatorial e a potência são sequências em que uma acompanha a outra. Numa mesma iteração podemos calcular tanto um quanto o outro sem precisar de uma iteração separada para cada um.