ALGORITMOS COM PASCAL

 

Prof. Miguel Jonathan – Dept. Ciência da Computação – IM/UFRJ – 2003 – versão 1.0

 

CAPíTULO 6

 

Sub-programas em Pascal: Funções e Procedimentos

 

 

Conceituação:

Sub-programas são partes de um programa que podem ser consideradas como tarefas relativamente independentes das demais. Por isso, podem ser programadas de forma separada, recebem um nome, e depois podem usadas várias vezes dentro do programa bastando serem “chamadas” pelo nome.

Há duas categorias clássicas de sub-programas: funções e procedimentos.

 

Funções:

Uma função é um sub-programa que realiza uma tarefa de calcular e produzir como resultado um valor, o qual será usado por outras partes do programa:

Ex: o programa abaixo usa a função pré-definida sqrt(n), que calcula a raiz quadrada de um real n, e dá como resultado um valor real:

Exemplo 6-1:

 

program ex6_1;

var L1,L2,H: real;

begin

 L1:= 3.0;

 L2:= 4.0;

 H:= sqrt(L1*L1+L2*L2);

 writeln('A hipotenusa do triangulo retangulo de lados ', L1:3:1, ' e ',

   L2:3:1, ' e'' ' ,H:3:1);

end.

 

No exemplo acima, a função sqrt já vem pré-definida na linguagem Pascal, e pode ser utilizada diretamente. Note o seguinte:

a)      a função tem um argumento real n, ou seja, para usá-la é preciso fornecer um valor real entre parênteses. A definição de uso é sqrt(n) onde n deve ser um valor real.

b)      esse argumento pode ser qualquer expressão real, no exemplo foi L1*L1+L2*L2.

c)      a função realiza uma tarefa, através de um algoritmo próprio, para calcular a raiz quadrada. Essa tarefa constitui o sub-programa, que nesse caso já está armazenado no computador em forma compilada.

d)      quando o programa for executar o comando:    H:= sqrt(L1*L1+L2*L2);

vai ocorrer o seguinte:

-         primeiro,  a expressão do argumento será calculada, para obter o seu valor. No caso, será calculado o valor 3.0*3.0 + 4.0*4.0  que dá 25.0.

-         em seguida o programa entrará em um estado de suspensão da sua execução, e o controle da execução passará para o primeiro comando do sub-programa sqrt.

-         antes de iniciar o sub-programa, o valor calculado do argumento, no caso 25.0, será passado como valor inicial de uma variável especial do sub-programa que representa o argumento da função. Essa variável é chamada de parâmetro de valor e a operação é chamada de passagem de parâmetro.

-         o sub-programa executará seus comandos, e a raiz quadrada do argumento será calculada, obtendo o valo 5.0, nesse caso.

-         ao terminar, o sub-programa repassará o valor obtido de volta para o programa. Isso é feito substituindo a aplicação da função, sqrt(L1*L1+L2*L2), pelo resultado obtido.

-         nesse ponto o controle da execução voltará para o programa no ponto em que havia interrompido, e o programa executará o comando:  H:= 5.0; continuando a execução normalmente a partir desse ponto.

 

Funções definidas no próprio programa:

No exemplo acima, foi usada uma função pré-definida na linguagem Pascal. Mas a linguagem permite que qualquer função possa ser definida no próprio programa.

O programador deve escrever a definição da função na área de declarações do programa.  Essa é a área antes do trecho  begin ....end. do programa.

No exemplo abaixo, o programa declara, e depois usa, uma função para calcular o fatorial de um inteiro. A função está destacada em azul. Note que a variável x faz parte das declarações do programa, e não da função. A função tem sua própria área de declarações de variáveis.

 

Exemplo 6-2:

 

program ex6_2;

var

  x:integer;

function fat(n:integer): longint;

 var

   i: integer;

   f: longint;

 begin

   f:=1;

   for i:= 2 to n do f:= f*i;

   fat:= f;

 end; {fim da declaracao da funcao}

 

begin {inicio do programa}

 writeln('Digite varios numeros para saber o fatorial');

 writeln('Termine com -1');

 read(x);

 while x > 0 do begin

  writeln ('fatorial de ', x, ' = ', fat(x) );

  read(x);

 end;

end.

 

 

 

Ao executar o programa, o seguinte diálogo poderá ocorrer: (dados do usuário em itálico):

Digite varios numeros para saber o fatorial.

Termine com –1:

4

Fatorial de 4 = 24

5

Fatorial de 5 = 120

6

Fatorial de 6 = 720

-1    (aqui o programa termina)

 

As seguintes observações são importantes para um bom entendimento de funções e do programa acima:

 

a)      A declaração da função fica na área de declarações, junto com as declarações de variáveis e constantes:

function fat(n:integer): longint;

 var

   i: integer;

   f: longint;

 begin

   f:=1;

   for i:= 2 to n do f:= f*i;

   fat:= f;

 end; {fim da declaracao da funcao}

b)      O formato da declaração da função é bastante parecido com o de um programa, apenas no cabeçalho a palavra program é substituída pela palavra function. A declaração da função tem sua própria área de declarações e sua área de comandos. Mas termina com end;  e não com end.

c)      Além disso, no cabeçalho da função são declarados os seus argumentos, agrupados por tipo, seguidos do tipo do resultado que produz. A forma sintática do cabeçalho é:

function <nome da função> (<lista de argumentos>:<tipo>; <lista de argumentos>: tipo; etc):<tipo do resultado>

No caso do fatorial, há apenas um parâmetro inteiro (chamado n) e o resultado será do tipo longint, porque os fatoriais crescem muito rapidamente:

function fat(n:integer): longint;

d)      No corpo da função (parte executável, começando após o primeiro begin) o nome da função deve receber por atribuição pelo menos uma vez o valor do resultado:

fat:= f;

É dessa forma que o resultado calculado será repassado de volta ao programa, onde irá substituir a aplicação da função. Note que, apesar de receber um valor por atribuição, o nome da função não é  uma variável. Caso o nome da função receba mais de um valor durante a execução da função, o valor que finalmente será repassado para o programa será o último valor atribuído ao nome da função antes dela terminar sua execução.

e)      Outro ponto importante a registrar é que os identificadores definidos na função só têm significado dentro da função, e não para fora dela. Chama-se a isso o escopo dos identificadores. No exemplo acima, as variáveis n, i e f não são “vistas” pelo programa que usa a função, e são chamadas variáveis locais da função. A variável n é especial, chamada de parâmetro formal de valor. É ela que receberá o valor do argumento a ser usado no inicio da execução da função. A partir daí, os parâmetros de valor se comportam como qualquer outra variável local.

 

Voltando então para a execução do Exemplo 6-2, vemos que o laço de repetição fará com que a função fat seja chamada várias vezes, cada vez com um argumento diferente. A cada vez, o programa será interrompido, o controle será passado para a função, um resultado será obtido e repassado de volta ao programa, substituindo a aplicação da função.

 

Tipos dos argumentos e do resultado de funções:

Em uma linguagem de programação como Pascal, as funções podem ter argumentos de tipos diferentes. Argumentos podem ser de tipos escalares simples, como integer, longint, real, char e boolean, e podem ser também de tipos estruturados, como vetores, strings, matrizes e registros (records), esse último tipo ainda por ser estudado.

Por outro lado, o resultado da função tem maiores limitações: o tipo do resultado de uma função só pode ser um tipo escalar simples, com a exceção do tipo string, que também pode ser um resultado. Portanto, vetores comuns não podem ser resultado de funções, Veremos mais adiante que, para contornar essa limitação, podemos usar procedimentos para obter resultados de sub-programas que sejam tipos estruturados. Mas, para isso, teremos que usar parâmetros de referência, outro assunto que será visto mais adiante.

Os exemplos a seguir mostram várias funções com tipos diferentes de argumentos e de resultados.

Exemplo 6-3:

Neste exemplo,  a função maiuscula(c) é usada para obter a letra maiúscula correspondente ao caractere c, se este for uma letra minúscula, caso contrário o valor retornado é o próprio caractere original:

 

program ex6_3;

var

 frase:string;

 i: integer;

function maiuscula(c:char):char;

{essa funcao converte letras minusculas em maiusculas}

 begin

 case c of

 'a'..'z': maiuscula:= chr(ord('A') + ord(c)-ord('a'));

 else maiuscula:= c;

 end;

end;

begin

 writeln('Digite uma frase. Ela sera'' re-escrita em letras maiusculas: ');

 readln (frase);

 for i:=1 to length(frase) do frase[i] := maiuscula(frase[i]);

 writeln(frase);

end.

 

Note que a função foi aplicada a um argumento do tipo char, e que o valor resultante também é do tipo char. Para alterar as letras da string frase, foi necessário usar um laço de repetição para aplicar a função sucessivamente a cada caractere da frase.

No próximo exemplo, a função será aplicada a toda a frase de uma vez (o argumento é do tipo string) e o resultado também será do tipo string, isto é, a função retorna a frase inteira alterada:

 

program ex6_4;

var

 frase:string;

 i: integer;

function maiusc_str(s:string):string;

{essa funcao retorna uma string com todas as letras maiusculas}

 var i: integer;

 begin

 for i:= 1 to length(s) do

 case s[i] of

   'a'..'z': s[i]:= chr(ord('A') + ord(s[i])-ord('a'));

 end;

 maiusc_str:= s;

end;

begin

 writeln('Digite uma frase. Ela sera'' re-escrita em letras maiusculas: ');

 readln (frase);

 writeln(maiusc_str(frase));

end.

 

 

Uma observação importante: a função não altera a string frase. Ela retorna outra string, com as letras alteradas, que substituirá a expressão maiusc_str(frase). É essa string retornada que será impressa. O valor original da string frase não foi alterado.

 

 

No exemplo seguinte, a função cripto(c,n) onde c é um caractere, e n é um inteiro, positivo, tem o seguinte valor:

-         se c for uma letra (maiúscula ou minúscula), cripto(c,n) retorna a letra n posições adiante no alfabeto, considerado circular (depois do ‘z’ vem o ‘a’, depois do ‘Z’ vem o ‘A’).    Ex: cripto(‘Z’, 3)  retorna  ‘C’ 

-         se c for um dígito, cripto(c,n) retorna o dígito n posições adiante, também circular (depois do ‘9’ vem o ‘0’)

Ex: cripto(‘3’, 11)  retorna ‘4’.

-         para qualquer outro caractere, a função retorna o próprio caractere sem alteração. 

 

Esse exemplo é mais complexo, e é interessante para ressaltar a importância de se separar as tarefas em sub-programas separados. Dessa forma, o programa que usa a função fica muito mais compreensível, e também a função pode ser testada e aperfeiçoada independentemente dos programas que a usarão. O programa que usa a função é bem simples, e toda a complexidade da criptografia fica encapsulada na definição da função.

 

 

 

 

 

 

Exemplo 6-5:

 

program ex6_5;

var

 frase: string;

 i,n: integer;

 

function cripto(c: char; n: integer): char;

 var pos,d:integer;

 begin

 n:= n mod 26;  {reduz logo o n }

 pos:= ord(c) + n; {posicao na tabela de caracteres n casas adiante de c}

 case c of

 'a'..'z': begin

           d:= pos -ord('z');  {d e' quanto pos passou de z}

           if d<=0 then cripto:= chr(pos)

            else cripto:= chr(ord('a') + d-1 )

           end;

 'A'..'Z': begin

           d:= pos -ord('Z');  {d e' quanto pos passou de Z}

           if d<=0 then cripto:= chr(pos)

            else cripto:= chr(ord('A') + d-1 )

           end;

 '0'..'9': begin

           d:= pos -ord('9');  {d e' quanto pos passou de 9}

           if d<=0 then cripto:= chr(pos)

            else cripto:= chr(ord('0') + d-1 )

           end;

  else cripto:= c; {se for outro caractere, nao altera}

 end;

end;

begin

 writeln('Digite uma frase: ');

 readln(frase);

 writeln('Escolha o valor de n para a criptografia: ');

 readln(n);

 for i:=1 to length(frase) do write(cripto(frase[i], n));

 readln;

end.

 

Procedimentos

 

Procedimentos representam outra categoria de sub-programas em Pascal. As diferenças entre procedimentos e funções são:

a)      um procedimento é um sub-programa que realiza uma tarefa, mas não retorna um valor de resultado, como as funções.

b)      o uso do procedimento é semelhante a um comando, e tem o mesmo valor sintático, isto é, onde na linguagem se espera um comando, pode ser colocada uma chamada de procedimento.

 

O exemplo abaixo ilustra a declaração e o uso de um procedimento de nome msg, que faz a tarefa de escrever uma mensagem (uma string) em um ponto determinado da tela:

Ele utiliza um procedimento pré-definido em Pascal, gotoxy(x,y), que posiciona o cursor no ponto correspondente à interseção da linha y com a coluna x:

 

Exemplo 6-6:

 

program ex6_6;

uses crt;  {necessario para usar gotoxy e clrscr}

 var linhamedia,colunamedia: integer;

    m:string;

 procedure msg(frase:string; x,y: integer);

  begin

  gotoxy(x,y);

  write(frase);

 end;

begin

 clrscr;

 msg('esta frase na linha 1 coluna 1', 1, 1);

 msg('esta frase na linha 3 coluna 2', 2, 3);

 msg('esta frase na linha 5 coluna 3', 3, 5);

 m:= 'meio da tela';

 linhamedia:=12;

 colunamedia:=40;

 msg(m,colunamedia-length(m) div 2,linhamedia);

end.

 

O procedimento msg tem 3 parâmetros formais de valor: frase, x e y. Ele é chamado 4 vezes pelo programa. Note que cada chamada tem a forma de um comando. A cada chamada, o valor de cada parâmetro é inicializado com o valor correspondente do argumento usado. Da mesma forma que para as funções, cada argumento usado na chamada de um procedimento pode ser qualquer expressão que tenha valor do tipo esperado.

Existem vários comandos que usamos normalmente na linguagem Pascal que são na realidade procedimentos pré-definidos. Os casos mais comuns são os procedimentos de leitura e escrita: read, readln, write, e writeln, são procedimentos bastante complexos, que têm a faculdade adicional de aceitar um numero variável de argumentos. Outros comandos, como clrscr, gotoxy, etc também são procedimentos.

 

 

Parâmetros de Referência

Até agora todos os parâmetros usados nos exemplos de funções e procedimentos foram parâmetros de valor. Um parâmetro de valor é uma variável local do sub-programa, que tem a propriedade de receber um valor inicial passado durante a chamada. Fora disso, comporta-se como uma variável local normal,  como as demais definidas na seção var.

As variáveis locais de um sub-programa existem em um espaço de memória próprio, que só é acessível ao sub-programa. Por esse motivo, os parâmetros de valor não podem ser ”vistos” pelo programa externo ao sub-programa. Apenas o resultado do cálculo da função pode retornar ao programa.

Os parâmetros de referência, ou parâmetros variáveis, permitem que valores calculados no sub-programa possam ser repassados ao programa, de forma diferente do mecanismo de retorno de função.

O mecanismo usado no parâmetro de referência é o seguinte:

a)      na definição do procedimento, os parâmetros de referência devem ser precedidos do prefixo var.

b)      Na chamada do procedimento, os argumentos usados no lugar de parâmetros de referência devem ser obrigatoriamente nomes de variáveis do programa, e não expressões.

c)      Quando o procedimento é chamado, o parâmetro de referencia recebe, não mais um valor, mas uma referência para a variável correspondente do programa. Na prática, isso significa que o parâmetro do sub-programa, e a variável correspondente do programa, passam a ser a mesma variável, como se fossem sinônimos. O efeito é que tudo que acontece com um parâmetro de referência do sub-programa vai se refletir imediatamente na variável correspondente do programa. Ou seja, quando o sub-programa termina, e devolve o controle de execução ao programa, as variáveis passadas por referência para o sub-programa podem ter sido alteradas.

 

O exemplo a seguir define dois procedimentos, que são usados no programa.

O primeiro procedimento chama-se altera_variavel, e tem dois parâmetros: o parâmetro de referência  x e o parâmetro de valor n. O efeito desse procedimento é fazer com que uma variável do programa tenha o valor somado a n pelo procedimento. A variável que deverá ser alterada será passada por referência. Já a quantidade a ser somada, será passada por valor.

O segundo procedimento elimina os n primeiros caracteres de uma string do programa. A string a ser alterada é passada por referência. A quantidade de caracteres a serem eliminados é passada por valor:

Exemplo 6-7:

program ex6_7;

uses crt;

var a,b,c: integer;

    nome: string;

procedure altera_variavel(var x:integer; n:integer);

 {a e' parametro de referencia, n e' parametro de valor}

 begin

  x:= x+n;

 end;

procedure reduz_string (var frase:string; n:integer);

 {esse procedimento elimina os n primeiros caracteres da frase}

 var i:integer;

     s:string;

 begin

  s:=''; {s e'  inicializada com uma string vazia, ou nula}

  for i:= n+1 to length(frase) do s:=s+frase[i]; {operador + de concatenacao}

  frase:=s; {o conteudo de frase no programa sera’ alterado}

 end;

 

begin  {aqui inicia a execucao do programa}

clrscr;

 a:=10;

 b:=20;

 c:=30;

 writeln(a:4,b:4,c:4);

 altera_variavel(a, 5); {a variavel a tera' seu valor alterado para a+5}

 altera_variavel(b, 7); {a variavel b tera' seu valor alterado para b+7}

 altera_variavel(c, 15); {a variavel c tera' seu valor alterado para c+15}

 writeln(a:4,b:4,c:4);

 nome:= 'Antonio Claudio da Sliva';

 writeln('nome antes : ', nome);

 reduz_string(nome,8); {deve eliminar os 8 caracteres antes de Claudio}

 writeln('nome depois: ', nome);

 readln;

end.

 

 

 

O próximo exemplo mostra um programa que usa vários procedimentos e funções.

Um processo de criptografia de frases de até 100 caracteres, com código (chave) igual a k<=10, consiste em colocar os caracteres da frase em uma matriz, usando apenas as primeiras k colunas. No exemplo abaixo,  com k=8, são usadas as 8 primeiras colunas de uma matriz 10x10 para criptografar a frase:

   UNIVERSIDADE FEDERAL DO RIO DE JANEIRO

O número de linhas necessárias para conter todos os caracteres da frase é dado pela expressão:

  no. de linhas = menor inteiro maior ou igual a tamanho da frase/k

Para k=8, e como a frase acima tem 37 caracteres, o número necessário de linhas é: 37/8= 4,6 => 5.

 

Inicialmente, os caracteres da string são colocados na matriz linha a linha, ocupando as k primeiras colunas apenas:

                                                      1     2    3     4      5     6    7    8    9  10

U

N

I

V

E

R

S

I

 

 

D

A

D

E

 

F

E

D

 

 

E

R

A

L

 

D

O

 

 

 

R

I

O

 

D

E

 

J

 

 

A

N

E

I

R

O

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                           1

                                           2

                                           3

                                           4

                                           5

                                           6

                                           7

                                           8

                                           9

                                         10

 

A codificação é completada reescrevendo a matriz em forma de string, desta vez escrevendo os caracteres segundo as colunas, a partir da coluna 1, apenas para as linhas usadas.

Completando o exemplo acima, a frase codificada fica da forma seguinte:

 

UDERANARINIDAOEVEL IE  DRRFDEOSEO  ID J

 

O interessante é que o mesmo processo pode ser usado tanto para codificar como para decodificar. Para decodificar, toma-se a frase codificada, desta vez com chave (no. de colunas) igual ao número de linhas usado na codificação original, obtendo a matriz transversa abaixo:

                                                          1     2    3     4     5   6   7  8  9  10

U

D

E

R

A

 

 

 

 

 

N

A

R

I

N

 

 

 

 

 

I

D

A

O

E

 

 

 

 

 

V

E

L

 

I

 

 

 

 

 

E

 

 

D

R

 

 

 

 

 

R

F

D

E

O

 

 

 

 

 

S

E

O

 

 

 

 

 

 

 

I

D

 

J

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                            1

                                            2

                                            3

                                            4

                                            5

                                            6

                                            7

                                            8

                                            9

                                          10

 

Basta usar o processo de codificação para obter a frase original, lendo coluna a coluna.

O número mínimo de linhas necessárias para codificar uma frase com ncar caracteres, e chave k, é dada pela expressão:   round(ncar/k + 0.4)

A solução abaixo usa 2 procedimentos e 2 funções.

Os procedimentos le_frase e le_chave são usados para solicitar do usuário a frase a ser codificada, e a chave a ser usada. Ambos usam parâmetros de referência var para passar os valores lidos para o programa.

A função linhas_necessarias(f, k) calcula e retorna o menor número de linhas necessárias para codificar a frase f com a chave k.

A função codifica(frase,k) é que faz o trabalho de codificar a string frase com a chave k, e retorna a string codificada. A função utiliza o procedimento limpa_matriz,  para inicializar os elementos da matriz com espaços em branco. Note que o  procedimento é interno à função, e que a matriz é acessada como variável global ao procedimento, não sendo necessário passar parâmetro.

A função usa uma matriz 10x10, podendo acomodar frases de até 100 caracteres.

O programa propriamente dito consiste apenas de chamadas dos procedimentos. Todo o trabalho real é feito nos sub-programas, o programa apenas coordena a chamada dos mesmos para realizar uma tarefa completa.

 

program codifica_matr;

{Este programa codifica textos usando o metodo da matriz transposta}

var

    frase: string;

    chave:integer;

 

procedure le_frase (var frase:string);

begin

 writeln('Digite a frase a ser codificada ate'' 100 caracteres:' );

 readln(frase);

end;

 

procedure le_chave(var chave:integer);

 var i: integer;

begin

 writeln('Digite a chave de codificacao < 10: '); readln(chave);

end;

 

function linhas_necessarias(f:string; k: integer):integer;

{retorna o menor numero de linhas necessarias para codificar a string f

 com a chave k}

 begin

 linhas_necessarias:= round(length(f)/k +0.4);

 end;

 

function codifica (frase:string; k: integer):string;

 var

    m: array[1..10,1..10] of char;

    i,j,n,lin,col,linhas, colunas:integer;

    s: string;

 procedure limpa_matriz;

  var i,j:integer;

  begin

  for i:= 1 to 10 do

   for j:= 1 to 10 do m[i,j]:= ' ';

 end; {limpa_matriz}

 begin

  limpa_matriz;

  colunas:= k;

  linhas:= linhas_necessarias(frase,k);

  for i:= 1 to length(frase) do begin

   lin:= (i- 1) div colunas+1;

   col:= i mod colunas;  if col=0 then col:=colunas;

   m[lin,col]:= frase[i];

  end;

  s:='';

  for j:=1 to colunas do

   for i:= 1 to linhas do s:=s+m[i,j];

  codifica:=s;

end; {codifica}

begin

 le_frase(frase);

 le_chave(chave);

 frase:=codifica(frase,chave);

 writeln('Frase codificada com a chave ', chave,': ', frase);

 chave := linhas_necessarias(frase, chave);

 frase:= codifica(frase,chave);

 writeln('Decodificando com a chave ', chave, ': ', frase);

 readln;

end.