Curso de Pascal
Função, Procedimento e Recursividade


  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:
  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:   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

Roda código em verde - Sentido das chamadas >>>>>>
if (i<10) then begin
  i := i + 1;
  x := i; { x=2 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=3 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=4 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=5 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=6 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=7 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=8 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=9 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i; { x=10 }
  loop;
  write(' ', x);
end;
if (i<10) then begin
  i := i + 1;
  x := i;
  loop;
  write(' ', x);
end;
<<<<<< Sentido do desempilhamento - Roda código em azul.
  Obs: O trecho de código em vermelho não é executado, pois o valor máximo de i foi atingido.

  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:
  1. Recebe valor 4.
  2. Passo 1:  fatorial := 4*fatorial(3);
  3. Fatorial é chamada de dentro com o valor 3.
  4. Passo 2: fatorial := 3*fatorial(2);
  5. Passo 3: fatorial := 2*fatorial(1);
  6. Se valor for menor ou igual a 1, retorna 1. Fim das chamadas sucessivas.
  7. Vamos desempilhar.
  8. Passo 3: fatorial := 2*1;
  9. Passo 2: fatorial := 3*2;
  10. Passo 1: fatorial := 4*6;
  11. Retorna o valor 24 para a chamada do corpo principal do programa.


<< Anterior Pascal Próxima >>


/MARMSX/CURSOS/PASCAL