Curso de Pascal
Função, Procedimento e Recursividade
Você está em: MarMSX >> Cursos >> Pascal
Função e Procedimento (Function e Procedure)
O Pascal é uma linguagem estruturada, diferente do Basic que não é. No Basic, o código do programa é uma lista enorme, sem apresentar divisão textual da lógica do programa. Já em uma linguagem estruturada como o Pascal, o programa é dividido em módulos ou sub-rotinas. Dessa maneira, o código fica mais legível, além de ser mais fácil portar, dar manutenção e ser mais confiável.
Uma sub-rotina é uma porção de código para realizar uma tarefa especifica. Ela pode ser definida como um grupo de comandos, constituindo um trecho de algoritmo, com uma função bem definida e o mais independente possível em relação ao resto do algoritmo.
O ideal é que os módulos não sejam grandes demais, pois senão acabam sendo multifuncionais e de difícil compreensão, de modo que o modulo deve ser implementado apenas as estruturas de dados necessários para atingir ao objetivo do modulo.
Uma sub-rotina é declarada da seguinte forma:
subrotina NOME(lista-de-parâmetros)
declarações dos objetos locais das sub-rotinas
comandos das sub-rotinas
fimsubrotina
A lista de parâmetros é opcional. Caso seja usada elas podem ser de duas formas:
- Passagem por valor
- Passagem por referência
Fonte: Wikipedia - Modularidade
Tanto o procedimento como a função são subrotinas que tem como missão realizar alguma tarefa específica. A diferença entre procedimento e função é que a função retorna algum valor, enquanto que o procedimento não retorna.
Para enter bem a diferença entre procedimento e função, vejamos duas situações distintas:
- Desejo criar uma sub-rotina para imprimir uma mensagem de boas vindas.
- Desejo calcular o produto de dois números.
No caso da impressão de uma mensagem, não é necessário o retorno de qualquer informação. Assim, pode-se utilizar um procedimento. Já no caso do cálculo do produto de dois números, é desejável o retorno do valor calculado. Nesse caso, deve-se utilizar uma função.
O exemplo a seguir ilustra o uso de um procedimento e de uma função:
var prod : integer;
procedure mostra_mensagem();
begin
writeln('Ola. Bem vindo ao programa de procedures e functions.');
end;
function calcula_produto(a, b : integer) : integer;
begin
calcula_produto := a * b;
end;
begin
mostra_mensagem();
prod := calcula_Produto(4,3);
writeln('Resultado de 4 x 3: ',&prod);
end.
Saída:
Ola. Bem vindo ao programa de procedures e functions.
Resultado de 4 x 3: 12
Sintaxe para o procedimento:
procedure nome(lista de parametros);
lista de variaveis locais
begin
código
end
Sintaxe para a função:
function nome(lista de parametros) : tipo_de_retorno;
lista de variaveis locais
begin
código
nome := valor de retorno;
end
Na lista de parâmetros, os tipos iguais são separados por vírgula, enquanto que os tipos diferentes são separados por ponto e vírgula. Ex:
procedure calcula(a, b : integer; c, d : real);
O exemplo a seguir ilustra o uso de uma função:
function funcao_X(a : integer; b : real) : real;
var c : real;
begin
c := 0.45;
funcao_X := real(a) * b * c;
end;
Obs: as variáveis locais (variáveis declaradas dentro de uma função ou procedimento) tem duração limitada ao tempo de execução da sub-rotina. Ou seja, assim que essa sub-rotina encerra sua execução, esta variável é descartada juntamente com a informação contida nela.
No exemplo anterior, a variável "c" é descartada assim que a função "funcao_X" termina sua execução.
Já as variáveis globais (declaradas no corpo principal do programa) tem duração longa e só são descartadas quando o programa termina sua execução. Elas estão o tempo todo disponíveis e podem ser acessadas a partir de sub-rotinas.
É possível declarar variáveis locais com o mesmo nome de uma variável global. Entretanto, a variável local terá precedência sobre a variável global dentro da sub-rotina, ou seja, o valor acessado é o da variável local. Ex:
var x, y : integer;
procedure teste();
var x : integer;
begin
x := 4;
writeln('Valor de X:',&x);
writeln('Valor de Y:',&y);
end;
begin
x := 2;
y := 5;
teste();
writeln('Valor de X:',&x);
writeln('Valor de Y:',&y);
end.
Saída:
Valor de X:4
Valor de Y:5
Valor de X:2
Valor de Y:5
Passagem por referência e valor
A esta altura, o leitor já deve ter perguntado o seguinte: e se eu alterar o valor de uma variável passada como parâmetro para uma sub-rotina, o que acontece? Essa alteração é refletida na variável que foi utilizada na chamada da função?
Bem, na verdade as mudanças podem ou não refletir na variável utilizada na chamada da função, dependendo de como os parâmetros são declarados.
Conforme visto anteriormente, há dois tipos de passagem de parâmetros: por valor ou por referência. Na passagem por valor, é passada uma cópia da variável original e assim, qualquer alteração dessa variável dentro da função NÃO irá refletir na variável original. Já na passagem por referência, a variável original é passada diretamente para dentro da sub-rotina e qualquer alteração IRÁ refletir na variável original.
Até o presente momento, todos os parâmetros utilizados nos exemplos fazem passagem por valor (ou cópia). Para realizar a passagem por referência, deve-se utilizar a palavra reservada "var" antes da declaração do parâmetro. Ex:
var x : integer;
procedure valor(a : integer);
begin
a := 10;
end;
procedure referencia(var a : integer);
begin
a := 15;
end;
begin
x := 5;
writeln('Valor de X:',&x);
valor(x);
writeln('Valor de X:',&x);
referencia(x);
writeln('Valor de X:',&x);
end.
Saída:
Valor de X:5
Valor de X:5
Valor de X:15
Obs: ao utilizarmos o mesmo nome da variável utilizada na chamada da função nos parâmetros da sub-rotina, terá o mesmo efeito que a declaração de uma variável local, salvo quando há utilização de passagem por referência. Assim, ao substituirmos todas as variáveis "a" por "x" no exemplo anterior, iremos obter as mesmas respostas.
Procedimentos e funções que retornam mais de um valor
Conforme visto até aqui, os procedimentos não retornam valor e as funções retornam apenas um valor. E se eu quiser retornar mais valores? Como fazer?
A solução é utilizar parâmetros que passam valor por referência. Assim, é só modificar o valor desses parâmetros, que o retorno será feito. Ex:
procedure retorna_dados (var nome : string; idade : integer; peso : real);
begin
nome := 'Pedro';
idade := 31;
peso := 80.0;
end;
var nome : string;
idade : integer;
peso : real;
begin
retorna_dados(nome, idade, peso);
writeln('Nome: ', nome);
writeln('Idade: ', idade);
writeln('Peso: ', peso:2:1); { Formatação de um número real: no_casas_int:no_casas_fração }
end.
Saída:
Nome: Pedro
Idade: 21
Peso: 80.0
Passando vetor ou registro como parâmetro de função
Para passar um vetor como parâmetro para uma função ou procedimento, é necessário criar um tipo que o identifique. Exemplo.
const size = 5;
type vetor = array[1..size] of integer;
var i : integer;
vt : vetor;
procedure imprime_vetor(v : vetor);
var i : integer;
begin
for i:=1 to size do
write(v[i], ' ');
writeln;
end;
begin
for i:=1 to size do
begin
write('Entre o numero ',i,':');
readln(vt[i]);
end;
imprime_vetor(vt);
end.
Saída:
Entre o numero 1: 34
Entre o numero 2: 22
Entre o numero 3: 15
Entre o numero 4: 44
Entre o numero 5: 32
34 22 15 44 32
Um exemplo que passa um "tipo record" para a função.
type ficha_mod = record
nome : string[20];
idade : integer;
peso : real;
end;
var ficha : ficha_mod;
procedure imprime(f : ficha_mod);
begin
writeln('Nome: ', f.nome);
writeln('Idade: ', f.idade);
writeln('Peso: ', f.peso:2:1);
end;
begin
ficha.nome := 'Pedro';
ficha.idade := 31;
ficha.peso := 80.0;
imprime(ficha);
end.
Saída:
Nome: Pedro
Idade: 21
Peso: 80.0
É possível também passar o "tipo record" como ponteiro, que é útil no caso das listas encadeadas.
Funções que retornam registros e strings
A linguagem Pascal mais moderna permite o retorno de "tipo registro" estático. Entretanto, o Turbo Pascal do MSX não admite isso. Veja o exemplo a seguir.
type ficha_mod = record
nome : string[20];
idade : integer;
peso : real;
end;
var ficha : ficha_mod;
function cria_dados : ficha_mod;
begin
cria_dados.nome := 'Pedro';
cria_dados.idade := 31;
cria_dados.peso := 80.0;
end;
begin
ficha := cria_dados;
writeln('Nome: ', ficha.nome);
writeln('Idade: ', ficha.idade);
writeln('Peso: ', ficha.peso:2:1);
end.
Existe uma solução alternativa para o Turbo Pascal do MSX: uma função retorna um ponteiro para uma variável "tipo record".
Assim, o programa acima pode ser reescrito da seguinte forma:
type p_ficha = ficha_mod;
ficha_mod = record
nome : string[20];
idade : integer;
peso : real;
end;
var ficha : p_ficha;
function cria_dados : p_ficha;
var p : p_ficha;
begin
new(p)
p.nome := 'Pedro';
p.idade := 31;
p.peso := 80.0;
retorna_dados := p;
end;
procedure imprime(p : p_ficha);
begin
writeln('Nome: ', p.nome);
writeln('Idade: ', p.idade);
writeln('Peso: ', p.peso:2:1);
end;
begin
ficha := cria_dados;
imprime(ficha);
end.
Saída:
Nome: Pedro
Idade: 21
Peso: 80.0
Para criar um retorno de uma string para uma função, é intuitivo fazer o seguinte:
function teste : string[20];
Entretanto, este procedimento é errado.
Para a função retornar uma string, devemos criar um tipo para ela. Porém, não é necessário o uso de um ponteiro.
Veja o exemplo a seguir.
type str = string[40];
function retorna_string : str;
begin
retorna_string = 'Mensagem da função';
end;
begin
writeln(retorna_string);
end.
Saída:
Mensagem da função
Forma correta de uso dos registros com as funções
As seções anteriores apresentaram diversas formas de interação entre os registro e funções, como prova de conceito. Entretanto, há uma forma correta de uso, dependendo do se o registro criado é estático ou dinâmico.
Sendo o registro estático e instanciado como uma variável global, NÃO é necessário passá-lo ou recebê-lo como parâmetro para uma função, pois ele é já enxergado em qualquer parte do código. Veja o exemplo a seguir.
type personagem = record
nome : string[20];
idade : integer;
peso : real;
altura : real;
forca : integer;
experiencia : integer;
end;
var personagens : array[1..5] of personagem;
i : integer;
procedure cria_personagem(i : integer);
begin
writeln('Personagem no ',i,':');
write('Nome: ');
readln(personagens[i].nome);
write('Idade: ');
readln(personagens[i].idade);
write('Peso: ');
readln(personagens[i].peso);
write('Altura: ');
readln(personagens[i].altura);
write('Forca: ');
readln(personagens[i].forca);
write('Experiencia: ');
readln(personagens[i].experiencia);
end;
procedure lista_personagem(i : integer);
begin
writeln('Personagem ',i,':');
writeln(' Nome: ', personagens[i].nome);
writeln(' Idade: ', personagens[i].idade);
writeln(' Peso: ', personagens[i].peso:2:2);
writeln(' Altura: ', personagens[i].altura:3:2);
writeln(' Forca: ', personagens[i].forca);
writeln(' Experiencia: ', personagens[i].experiencia);
writeln;
end;
begin
for i:=1 to 5 do
cria_personagem(i);
writeln('Lista de Personagens:');
for i:=1 to 5 do
lista_personagem(i);
end.
O programa acima cria 5 personagens e os armazena em um vetor na memória. Os procedimentos de criação e impressão de personagens apenas recebem como parâmetro a posição do vetor em que se deseja ler ou modificar os dados.
De forma diversa, um registro criado de forma dinâmica precisa ser passado para ou recebido de uma função através de ponteiros. Assim, um função pode ser responsável por alocar o registro e devolver o ponteiro para ele, assim como receber o local do registro dinâmico na memória para realizar modificações, conforme foi visto na seção anterior no procedimento:
procedure imprime(p : p_ficha);
Há mais exemplos de interação entre registro e funções na seção de listas encadeadas do capítulo 8.
Recursividade
Após trabalhar com sub-rotinas, você já deve ter se perguntado: posso chamar um procedimento ou função de dentro dela mesmo? A resposta é sim. A esse tipo de invocação de sub-rotina para ela mesmo é chamado de recursividade.
Exemplo:
{$A-}
var i : integer;
procedure loop;
begin
loop;
end;
begin
loop;
end.
Obs: Para os sistemas CP/M-80 que utilizam o Pascal, no caso, o modelo do MSX, deve-se utilizar a diretiva {$A-} para ativar as recursividades.
Observe que esse procedimento é infinito, conforme ilustra o código anterior. Uma vez posto em execução, a recursividade ficará executando infinitamente (até estourar a pilha).
O problema em questão é sinalizar para que esse procedimento pare em um determinado instante, sob alguma condição.
O código a seguir impõe uma condição para que a recursividade pare, quando i atingir o valor igual a 10. Para isso, é necessário que a sub-rotina modifique o valor de i e que a condição de parada seja obrigatoriamente atingida.
{$A-}
var i : integer;
procedure loop;
begin
if (i<10) then
begin
i := i + 1;
loop;
end;
end;
begin
i:=1;
loop;
end.
Cada vez que o procedimento loop é chamado no exemplo anterior, a variável global é incrementada (aumentada) de 1 unidade. Assim, logo chegará ao valor 10.
Por outro lado, a cada chamada recursiva, os valores das variáveis locais são empilhados em memória e uma nova chamada ao procedimento é feita. Em outras palavras, quando uma nova chamada recursiva é feita, novos valores para as variáveis locais são estabelecidos. Obviamente, isso não acontece para as variáveis globais, porque essas possuem escopo global.
Assim, caso fosse utilizada uma variável local no procedimento loop, o valor seria sempre reinicializado e nunca atingiria o valor igual a 10.
Exemplo de ERRO ao utilizar uma variável local como critério de parada:
{$A-}
procedure loop;
var i : integer;
begin
i := 1;
if (x<10) then
begin
i := i + 1;
loop;
end;
end;
begin
loop;
end.
Vamos modificar o programa anterior a este, de forma a informar o valor de i.
{$A-}
var i : integer;
procedure loop;
begin
if (i<10) then
begin
i := i + 1;
write(' ', i);
loop;
end;
end;
begin
i:=1;
loop;
end.
Saída:
2 3 4 5 6 7 8 9 10
Agora, vamos inverter as linhas do write e loop e ver o que acontece:
{$A-}
var i : integer;
procedure loop;
begin
if (i<10) then
begin
i := i + 1;
loop;
write(' ', i);
end;
end;
begin
i:=1;
loop;
end.
Saída:
10 10 10 10 10 10 10 10 10
Como assim? Escreveu tudo 10?
Na verdade, quando uma sub-rotina chama outra sub-rotina (ou ela mesmo), a execução da sub-rotina pára exatamente na linha onde essa chamada é feita. Então, a sub-rotina chamada entra em execução. Ao terminar a execução da sub-rotina chamada, a primeira sub-rotina continua a executar, a partir da linha seguinte daquela chamada. Para entender melhor isso, veja o exemplo abaixo:
procedure proc2;
begin
writeln('linha 3');
writeln('linha 4');
end;
procedure proc1;
begin
writeln('linha 1');
proc2;
writeln('linha 2');
end;
begin
proc1;
end.
Saída:
linha 1
linha 3
linha 4
linha 2
Observe que em proc1 foi feita uma chamada para proc2. Proc1 executou até esse linha, imprimindo a mensagem "linha1". Então, proc2 entrou em execução e imprimiu as mensagens "linha 3" e "linha 4". Ao terminar a execução de proc2, o programa voltou para proc1 e continuou a executar na linha seguinte da chamada de proc1, imprimindo a mensagem "linha 2".
O mesmo aconteceu no exemplo da recursividade que imprimiu tudo 10. Chamadas sucessivas ao loop são feitas, e isso só para quando uma dada condição é satisfeita, no caso i atingir o valor 10 (ou maior). Quando i=10, nenhuma nova chamada é feita e o retorno em cascata é realizado.
Como i é uma variável global, todas os procedimentos empilhados irão imprimir o mesmo valor.
Para demonstrar o efeito de pilha em chamadas recursivas, vamos armazenar o valor de i em uma variável local chamada x:
{$A-}
var i : integer;
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i;
loop;
write(' ', x);
end;
end;
begin
i:=1;
loop;
end.
Saída:
10 9 8 7 6 5 4 3 2
A ilustração a seguir mostrará o passo a passo do programa na recursividade.
De forma a facilitar o controle do valor da variável x em cada chamada ao "loop", logo após a atribuição de x := i, adicionamos um comentário com o novo valor de x.
O trecho do programa marcado em verde é a porção do código executado após a rotina ter sido chamada.
Primeira chamada à rotina "loop":
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i; { x = 2 }
loop;
write(' ', x);
end;
end;
Da segunda à nona chamada à rotina "loop":
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i; { x = 3, 4 ,5, 6, 7, 8, 9, 10 }
loop;
write(' ', x);
end;
end;
Quando realizamos a décima chamada à rotina "loop", i := 11:
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i;
loop;
write(' ', x);
end;
end;
Como uma chamada atingiu o final da rotina "loop", retorna-se o controle à chamada imediatamente a esta, executando a próxima linha dela. Veja a ilustação a seguir.
---> sentido das chamadas --->
┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐
Chamadas │ 1 │ │ 2 │ │ 3 │ │...│ │ N │
Loop └───┘ └───┘ └───┘ └───┘ └───┘
<--- sentido dos retornos <---
A execução do retorno da chamada ao "loop" é assinalada em azul.
O primeiro retorno às chamadas "loop":
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i; { x = 10 }
loop;
write(' ', x);
end;
end;
O próximo retorno:
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i; { x = 9 }
loop;
write(' ', x);
end;
end;
Repetimos até a primeira chamada:
procedure loop;
var x : integer;
begin
if (i<10) then
begin
i := i + 1;
x := i; { x = 2 }
loop;
write(' ', x);
end;
end;
Agora, vamos criar uma função que calcula o fatorial de um número, utilizando a recursividade.
{$A-}
function fatorial(n : integer):integer;
begin
if n <= 1 then
fatorial:=1;
else
fatorial:=n*fatorial(n-1);
end;
begin
writeln(fatorial(4));
end.
A saída é 24.
Agora vamos entender as interações da função fatorial:
- Recebe valor 4.
- Passo 1: fatorial := 4*fatorial(3);
- Fatorial é chamada de dentro com o valor 3.
- Passo 2: fatorial := 3*fatorial(2);
- Passo 3: fatorial := 2*fatorial(1);
- Se valor for menor ou igual a 1, retorna 1. Fim das chamadas sucessivas.
- Vamos desempilhar.
- Passo 3: fatorial := 2*1;
- Passo 2: fatorial := 3*2;
- Passo 1: fatorial := 4*6;
- Retorna o valor 24 para a chamada do corpo principal do programa.