1. Introdução ao uso de funções
Se você precisar de um algoritmo com um objetivo muito bem definido (e.g. um algoritmo para encontrar o
menor valor dentro uma lista dada)
é melhor encapsulá-lo em um bloco de modo a poder invocá-lo sempre que precisar desse algoritmo.
Geralmente todas as linguagens de programação possibilita esse encapsulamento e isso é feito na forma de uma
Para ampliar as vantagens do uso de funções as linguagens de programação permitem o
aninhamento de funções (funções dentro de funções) e
a declaração de
variáveis locais.
Se desejar examinar o aninhamento de funções em C siga essa URL.
Se desejar examinar o aninhamento de funções em Python siga essa URL.
2. Um paralelo entre função matemática e função em linguagens de programação
A ideia básica de uma função, implementada em alguma linguagem de programação, é encapsular um código que poderá ser invocado/chamado por qualquer outro trecho do programa. Seu significado e uso são muito parecidos com o de funções matemáticas, ou seja, existe um nome, uma definição e posterior invocação à função.
Vamos explicitar o conceito de função matemática para depois verificar que a contra-parte em computação é semelhante. Tomemos um exemplo simples, função cúbica: f(x) = x^2 é sua definição (eventualmente explicitamos f:IR → IR) e exemplos de seu uso poderia ser f(-1) e f(1/2), que devolvem, respectivamente, 1 e 1/4.
Por outro lado, existem funções mais "complicadas", como a função trigométrica cosseno(x),
que não pode ser escrita a partir de operações somas e multiplicações (é uma função transcendental)
e portanto é necessário algum "truque" matemático para obtê-la de modo aproximado.
Isso pode ser feito via série de Taylor, nesse paso poderíamos definir:
cos:IR → IR, cos(x) = 1 - x2/2! + x4/4! - x6/6! + x8/8! + ....
(1) cosseno(x) ≈ 1 - x2/2! + x4/4! - x6/6! + x8/8! + ....
Note que o lado direito da equação (1) é a definição da função e ela está escrita em termos do parâmetro formal x. Ao "chamarmos" a função com parâmetros efetivos, é sobre o valor desses parâmetros que computamos o lado direito da expressão, por exemplo, computar cos(0.1) ou de cos(1.1).
3. Por que usar o conceito de função?
Assim, implementar códigos com objetivos específicos (como computar o cosseno de qualquer valor) apresentam três grandes vantagens:
4. Sobre a definição (seus parâmetros) e uso de funções em Portugol
Assim, agrupar trechos com objetivos específicos, implementando-os na forma de uma função ajuda bastante no desenvolvimento e na organização de códigos em programação. Vejamos isso inicialmente uma pseudo-linguagem de programação, em Portugol.
Do ponto de vista prático, a estrutura básica de uma função em uma linguagem de programação está representada abaixo, com a declaração da função e sua lista de parâmetros formais, seguido de sua invocação (quando providenciamos os parâmetro efetivos).
Declaração: |
[eventual_tipo_de_retorno] nome_da_funcao (lista_parametros_formais) comando1 ... comandoN |
Uso: | var = nome_da_funcao(lista_parametros_efetivos) |
Note que, assim como em matemática a função compucional deve devolver algo, o que é feito no comando Portugol
devolva.
Neste texto consideraremos apenas funções que devolvem valores inteiros, mas poderia ser qualquer outro tipo
válido para a linguagem em foco.
No esquema Portugol acima, o tipo a ser devolvido está indicado no eventual_tipo_de_retorno.
Na linguagem C esse campo é obrigatório, enquanto em Python ele não é explicitado
(o tipo a ser devolvido é automaticamente deduzido a partir da variável no return).
Vide código 1 na seção 6.
Atenção ao devolva (geralmente nas linguagens práticas return), além dele estar associado
com o "tipo da função", ele é um comando muito especial:
se ele for alcançado (e.g., ele pode estar subordinado a um comando de seleção if),
então a execução da função é interrompida, retornando-se ao ponto imediatamente após o ponto de chamada da função
(com o valor de devolução).
No caso do comando ter uma expressão qualquer, como indicado no exemplo return EXP, o valor de EXP é devolvido
ao "ponto de chamada", neste caso a chamada deve estar dentro de uma expressão (lógica ou artimética de acordo com o tipo de EXP)
ou ser o lado direito de uma atribuição.
O primeiro caso ocorre nos exemplos do código 1 e o segundo caso é ilustrado na figura 1 (atribuições para a,
b e c.
A lista de parâmetros pode conter vários nomes de variáveis, geralmente, separadas por vírgula. Por exemplo, se houver necessidade de uma função que realiza vários cálculos com três variáveis pode-se usar como declaração da função algo como: nome_da_funcao (var1, var2, var3).
4.1 Parâmetros formais e efetivos
De modo geral, a diferença entre os parâmetros formais e efetivos é que o primeiro corresponde ao nome da variável utilizada dentro da função, enquanto o segundo é o nome da variável que será usado para iniciar o parâmetro formal ao iniciar a execução da função.
Assim durante a execução, ao encontrar uma chamada à função nome_da_funcao, o fluxo de execução segue a partir do código da função. Mas antes de executar a primeira linha de código da função, os valores dos parâmetros efetivos servem para inicializar os parâmetros formais (que são também variáveis locais à função). Após esta inicialização, inicia-se a execução do código da função e geralmente ao final, encontra-se um comando do tipo "retorne devolvendo um valor" (return).
4.2 Ilustrando a execução de um trecho de programa com 3 chamadas à mesma função
Suponhamos que precisemos computar o valor da combinação de N tomado k a k, ou seja, C(N,k) = N! / ( k! (N-k)!). Para isso percebe-se que é necessário implementar o cômputo de fatorial que será utilizado 3 vezes. Para facilitar a compreensão, podemos escrever um código com 3 variáveis auxiliares para armazenar, respectivamente, N!, k! e (N-k)!.
Fig. 1. Ilustração do fluxo de execução ao invocar uma função para computar o fatorial de um natural.
A figura 1 ilustra a execução do código para computar C(N,k), com o retângulo
à esquerda com a instrução que invoca a função fat e na direita o resumo da função fat.
Para entender o fluxo de execução destacaremos a execução da instrução na linha 2.
Como b = fat(k); é uma atribuição, primeiro computa-se o valor do lado direito da atribuição
e só depois atribui-se à variável, no lado esquerdo da atribuição, o valor que ela passará a ter, ou seja,
1. primeiro executa-se o cômputo de fat(k), para isso
2. pega-se o valor do parâmetro efetivo k e usa-o para iniciar o parâmetro formal n da função
3. então inicia-se a execução da função fat
4. ao final da função fat, o valor de n! (que está em ft) é devolvido para quem invocou fat(.),
5. então, este valor é atribuído à variável b
6. seguindo-se para a execução da próxima linha (3).
Vale notar que a execução da instrução c = fat(N-k); produzirá um fluxo de execução análogo aos 6 passos acima, mas
nesse caso, como o parâmetro efetivo é N-k, o parâmetro formal de fat(.) será iniciado com tal valor
(i.é., na prática, no início de fat(.), existe uma instrução ímplicita do tipo n = N-k).
Uma vez entendido como é executado uma chamada á função, podemos novamente comparar com o conceito usual de função matemática. Existe a declaração da função, com seu parâmetro formal
fat : IN -> IN (nome 'fat', com domínio e imagem nos naturais) fat(n) = { 1, se n=0; n x fat(n-1), se n>0 }E existe o uso da função, como em
C(N,k) = fat(N) / (fat(k) x fat(N-k)
5. Usando o conceito de função para desenvolver um algoritmo: usar funções para "quebrar" a complexidade do algoritmo
Uma aplicação interessante das 3 vantagens acima citadas é possibilitar quebrar o problema, para isso deve-se tentar
perceber alguma estrutura do problema que permita quebrá-lo em tarefas menores (e mais simples!).
Esta abordagem é conhecida em Matemática como a técnica
Essa técnica permite resolver problemas complexos como determinar a melhor maneira para mover os discos no problema da
Torre de Hanói.
Para uma explicação mais completa da técnica de divisão e conquista, podemos desenhar um algoritmo de modo
indutivo (que poderá resultar em um algoritmo recursivo) para resolver um problema de contagem/frequência:
determinar quantas ocorrência de determinado valor x existem em um vetor Vet.
Para um iniciante na arte de programar o raciocínio abaixo (os 4 itens) soará um tanto quanto abstrato, então sugerimos
que o estude de modo bastante atento, eventualmente revendo-o mais de uma vez.
Se você já estudou no Ensino Médio, lembre-se do processo de indução finita, é ele que fundamenta o raciocínio.
As funções que chamam a si própria recebem o nome
Se você dispuser de mais um tempo para estudo, experimente implementar a função fatorial, na linguagem que está aprendendo,
de modo recursivo (seguindo precisamente sua definição acima).
A seguir veja como implementar a função fatorial de modo iterativo.
6. Exemplos concretos em C e em Python - cuidado com a organização de seu código
Uma questão importante para o uso de funções (ou procedimentos) é a boa organização de código:
Para examinar exemplos concretos usando a linguagem C
clique aqui.
Para examinar exemplos concretos usando a linguagem Python
clique aqui.
C | Python | ||||
|
|
|
6.1 Variáveis locais
Note na linha 2 do código acima que são declaradas duas novas variáveis, ft e i, dentro da fução fat. Isso significa que as variáveis ft e i são variáveis locais à função, ou seja, pode-se utilizar variáveis com os mesmos nomes em outras funções sendo que elas não terão qualquer relação. Em particular, na linha 11 dos dois exemplos (no código 1), poderia-se usar uma variável com nome n, ft ou i e ainda assim, está variável não teria qualquer relação com as correspondentes da função fat.
6.2 Para que serve função?
Suponhamos que o objetivo seja conseguir um código para computar a combinação de n elementos, tomados k a k, que representaremos por C(n,k). Como sabemos do Ensino Médio, C(n,k) = n! / (k! (n-k)!).
Nesse contexto, tente esquematizar uma implementação sem o uso de funções! O núcleo desse código deverá ter cerca de 12 linhas, pois precisariamos repetir as linhas 2 a 5 (ou 6) três vezes, uma para fat(n), uma para fat(k) e outra para fat(n-k). Por outro lado, usando função (no código 1) foram necessárias apenas 11 linhas no total! Mais ainda, o código 1 é certamento mais claro que a versão sem funções.
Portanto, o uso de função simplifica o código. Mas existe uma outra razão (não tão óbvia) para usar funções: a maior facilidade para escrever um programa (como explorado na seção 5). E isso se deve à vários fatores, mas principalmente à quebra de um problema maior em outros menores.
Portanto, desenvolver códigos quebrando-o em funções menores facilita o desenvolvimento e reduz a incidência de erros de programação, pois, pode-se implementar cada função separadamente, testando-a até que ela fique pronta e sem erros.
Resumidamente, usar funções: resulta em códigos mais fáceis de serem entendidos, mais fáceis de serem testados e, muito possivelmente viabilizando a obtenção do próprio algoritmo (que sem a "quebra" em funções menores e mais simples, seria muito mais difícil).
Leônidas de Oliveira Brandão
http://line.ime.usp.br