8
- SUBPROGRAMAÇÃO
Até agora, todos os nossos programas foram escritos
de forma que o corpo do programa consistia de um único bloco de comandos, e
isto nos parecia uma boa opção. No entanto, à medida que precisamos escrever
programas maiores (com mais de uma página de código, por exemplo) esta deixa de
ser uma boa técnica. Para programas com código fonte de mais de uma página de
comprimento devem ser utilizadas as facilidades de subprogramação.
8.1.
CONCEITOS
A técnica de subprogramação consiste em quebrar um
programa grande em unidades menores que sejam funcionalmente independentes mas
que levem à obtenção dos mesmos resultados. Cada uma dessas unidades será
denominada de subprograma. Os critérios para quebrar um programa em
subprogramas são empíricos mas em geral sugere-se que cada subprograma seja
funcionalmente independente e que desempenhe uma tarefa específica única - para
tentar assegurar estas características, o nome do subprograma deve ser extraído
de uma frase simples que descreva completamente a finalidade do subprograma. A
frase deve conter um único verbo e um único objeto: frases múltiplas, sentenças
compostas (com vírgulas, e, ou, ...) e verbos que indicam múltiplas ações são
indicativos de que mais de uma tarefa está sendo realizada pelo subprograma.
Cada subprograma deve possuir suas próprias variáveis
(variáveis locais) e receber e/ou retornar valores do/para seu chamador apenas
através dos parâmetros. As variáveis declaradas no bloco principal (programa
principal) são denominadas de variáveis globais. Apesar de as variáveis globais
poderem ser utilizadas em qualquer ponto também nos subprogramas, esta é uma
técnica não recomendável de programação e deve, portanto, ser evitada.
8.2.
CLASSES DE SUBPROGRAMAS
A linguagem PASCAL permite a utilização de duas
categorias distintas de subprogramas: as FUNÇÕES e os PROCEDIMENTOS.
Estes subprogramas tanto podem ser predefinidos, tais
como MOD, LENGTH, SQR; ou então
definidos pelo próprio programador.
O
Apêndice 2 contém uma lista das Funções Padrão e o Apêndice 3 contém uma lista
dos Procedimentos Padrão - devem ser consultados. Uma referência completa das
funções e dos procedimentos predefinidos (incluindo os subprogramas
predefinidos que não são padrão) pode ser obtida no Manual do Usuário do
compilador que você estiver utilizando.
Ao
definir seus próprios subprogramas, o programador deve ter em mente alguns
princípios de boa programação que examinaremos a seguir.
A
primeira dúvida que aparece é: quando devemos utilizar um subprograma? A
resposta depende do atendimento aos seguintes requisitos isolada ou
conjuntamente:
- O código
fonte do programa tem uma página ou mais de extensão;
- Alguma parte
do programa desempenha uma tarefa específica única;
- Algumas
linhas de código aparecem repetidas em trechos distintos do programa;
-
Algum trecho do programa poderá vir a ser utilizado em um outro programa agora
ou futuramente.
Outra
dúvida bastante freqüente é: como escolher entre utilizar função ou
procedimento? Apesar de que tudo o que pode ser feito com uma função também
pode ser feito com um procedimento (a recíproca não é verdadeira), a regra de
escolha é bastante simples: sempre que uma função puder ser empregada para
resolver o problema, um procedimento não deve ser empregado. O que nos resta
saber é: em que situações uma função é suficiente para resolver o problema? A
resposta para esta questão é bem objetiva: Uma função deve ser utilizada quando
se desejar obter um único valor como resultado da execução do subprograma -
além disso, deve ser lembrado que não são permitidas operações de entrada/saída
de dados nas funções. Em todas as demais situações a escolha recairá sobre os
procedimentos - inclusive quando se desejar um único valor como retorno mas
houver necessidade de efetuar entrada/saída de dados.
Os subprogramas são declarados na parte das
declarações nos programas em PASCAL. Um subprograma tanto pode estar contido
(declarado) em outro programa, quanto ser ativado por um subprograma, inclusive
por si próprio, o que constitui uma ativação recursiva. Os subprogramas devem
ser declarados antes de sua ativação.
A ativação de uma função pode substituir uma variável
em uma expressão ou em uma lista de saída de dados. A ativação de um
procedimento pode substituir um comando PASCAL.
8.2.1.
FUNÇÕES (FUNCTION)
Em PASCAL as
funções são declaradas com a forma geral:
FUNCTION nome_da_função
(lista_de_parâmetros_formais: tipo_dos_parâmetros):
tipo_da_função;
declarações; /* se necessário */
BEGIN
comandos(s);
nome_da_função := valor; /*
atribuição do valor a ser retornado */
END;
O próximo programa utiliza uma função para cálculo do
quadrado de um número real qualquer.
PROGRAM
COM_FUNÇÃO;
VAR
NUMERO: REAL;
FUNCTION
QUADRADO (NUMERO_ENT: REAL): REAL;
/* O
NUMERO QUE ENTRA E O QUADRADO SÃO DO TIPO REAL */
BEGIN
QUADRADO := NUMERO_ENT * NUMERO_ENT
END;
BEGIN /* INICIO DO CORPO DO PROGRAMA
PRINCIPAL */
WRITELN('INFORME UM VALOR REAL
P/DETERMINAÇÃO DO QUADRADO:');
READLN(NUMERO);
WRITELN('O VALOR LIDO FOI', NUMERO: 6:
2, 'SEU QUADRADO EH', QUADRADO(NUMERO): 10: 4)
END.
Os parâmetros que aparecem na declaração de um
subprograma são denominados de parâmetros formais. Os que aparecem na ativação
são denominados de parâmetros reais.
A ativação de uma função é feita, como pode ser
comprovado no exemplo anterior, escrevendo-se o nome da função seguido da lista
de parâmetros reais (valores que serão transferidos para os parâmetros formais
respectivos na função) entre parêntesis. A ativação pode ser feita dentro de
uma expressão à direita de um comando de atribuição ou então em algum outro
local onde uma variável possa ser referenciada (numa lista_de_saída de um
comando de saída de dados, ou até mesmo como parâmetro real de uma ativação de
um subprograma). O retorno do valor calculado pela função sempre acontece
através do nome da função - é por esta razão que as funções são declaradas como
de algum tipo de dado no seu cabeçalho.
8.2.2.
PROCEDIMENTOS (PROCEDURE)
A inclusão de um procedimento em um programa equivale
à adição de um novo comando à linguagem de programação. Um procedimento pode
retornar qualquer quantidade de valores através dos parâmetros variáveis (serão
discutidos na próxima seção).
Os procedimentos devem ser declarados na parte de
declarações dos programas (ou subprogramas) em PASCAL e a única restrição é que
a declaração ocorra antes de sua primeira utilização.
Em PASCAL os procedimentos são declarados com a forma geral:
PROCEDURE
nome_do_procedimento(lista_de_parâmetros_formais);
declarações;
BEGIN
comandos(s); /* corpo do procedimento */
END;
A ativação de um procedimento é feita, como pode ser
observado no programa apresentado a seguir, escrevendo-se o nome do subprograma
seguido da lista de parâmetros reais (valores que serão transferidos para os
parâmetros formais respectivos no procedimento) em qualquer lugar onde possa
ser escrito um comando, no corpo do programa ou de um subprograma.
O programa apresentado a seguir utiliza um
procedimento para cálculo do quadrado e do cubo de um número inteiro qualquer.
PROGRAM
QUAD_E_CUBO; /* CALCULA
QUADRADO E CUBO */
VAR
VALOR: INTEGER; /* O VALOR QUE
SERÁ LIDO */
QUADRADO, CUBO: INTEGER; /* OS VALORES QUE SERÃO OBTIDOS QDO O PROCEDI MENTO FOR EXECUTADO*/
PROCEDURE
CALC_QUAD_E_CUBO (NUMERO: INTEGER; VAR QUAD, CUB: INTEGER);
BEGIN
QUAD := NUMERO * NUMERO;
CUB := QUAD * NUMERO END; BEGIN
WRITELN('INFORME UM VALOR INTEIRO');
READLN(VALOR);
CALC_QUAD_E_CUBO(VALOR, QUADRADO,
CUBO);
/* VALOR SERÁ PASSADO PARA O
PROCEDIMENTO. QUADRADO E CUBO SERÃO RECEBIDOS DOS PARÂMETROS DE POSIÇÕES CORRESPONDENTES DO PROCEDIMENTO */
WRITELN('VALOR LIDO', VALOR: 5, 'O SEU
QUADRADO EH:', QUADRADO: 7, ' O SEU CUBO EH:', CUBO: 8)
END.
8.3.
CLASSES DE PARÂMETROS
Foi comentado anteriormente que as funções e os
procedimentos comunicam-se com seus ativadores por intermédio de variáveis
globais (serão detalhadas em REGRAS DO ESCOPO LÉXICO, a seguir), o que não é
uma técnica recomendável de programação, e dos parâmetros. Os parâmetros que
são declarados no cabeçalho dos subprogramas são conhecidos por parâmetros
formais. Os parâmetros formais são utilizados com a finalidade de especificar
formalmente para os parâmetros: quantos são, quais são, seus tipos, e se serão
utilizados apenas para trazerem valores para o subprograma (passagem por valor)
ou para retornarem com e/ou trazerem valores do/para o subprograma (passagem
por variável).
Os parâmetros formais que serão utilizados apenas
para introduzir valores nos subprogramas são chamados de parâmetros de valor e
devem ser os primeiros parâmetros da lista_de_parâmetros formais do
subprograma. Os parâmetros de valor são os únicos permitidos para utilização
nas funções. A lista de parâmetros formais passados por valor é concluída com
ponto e vírgula (;) caso venha seguida de parâmetros de variáveis.
Os parâmetros formais que serão utilizados para
retornar com valores dos procedimentos são denominados parâmetros de variáveis.
A declaração dos parâmetros de variáveis deve vir após a declaração de todos os
parâmetros de valor na lista_de_parâmetros_formais do procedimento, sendo
iniciada por VAR.
No programa QUAD_E_CUBO
anteriormente apresentado, NUMERO é
um parâmetro de valor, QUAD e CUB são parâmetros de variáveis.
Ao ativar um procedimento que possua parâmetros de
variáveis o programador deve lembrar-se que os parâmetros reais de posições
correspondentes aos parâmetros formais de variáveis não podem ser valores
constantes. No entanto, uma variável utilizada como parâmetro real pode assumir
um valor antes da ativação do subprograma: neste caso, o parâmetro formal
correspondente assumirá este valor que poderá inclusive ser utilizado nos
cálculos dentro do procedimento.
8.4.
REGRAS DO ESCOPO LÉXICO
A área de um programa ou subprograma onde um objeto
pode ser referenciado é conhecida como escopo léxico.
O escopo de um objeto (variável, subprograma, tipo) é
a rotina inteira onde ele foi declarado, incluindo subprogramas.
A figura apresentada a seguir serve como ilustração
das regras do escopo léxico: veja na tabela, que segue a figura, quais os
objetos que estão disponíveis para acesso nos vários locais do programa.
Escopo Léxico de
Identificadores
Observe
que o nível mais externo contém o cabeçalho do programa (Program ...) e foi
identificado como nível zero. Os objetos declarados no nível zero são globais:
estão disponíveis no nível zero e em todos os demais níveis.
OBJETOS | Estão
acessíveis em
DECLARADOS EM |
PROGRAM A | A, B, C, D, E, F,
G, H, I
PROCEDURE B | B, C, D, E, F
PROCEDURE C | C, D, E
FUNCTION D | D
PROCEDURE E | E
PROCEDURE F | F
FUNCTION G | G, H, I
PROCEDURE H | H
PROCEDURE I | I
TYPE Reg1 | C, D, E
TYPE Reg2 | I
8.5.
RECURSIVIDADE
Recursividade é o nome dado à facilidade existente em
algumas linguagens de programação no sentido de permitir que uma rotina ative a
si própria. Recursividade é bastante útil quando o problema a ser resolvido já
é definido recursivamente, o que ocorre com certa freqüência em matemática.
Tanto a ativação quanto o retorno são feitos para as rotinas recursivas do
mesmo modo que para as rotinas não recursivas. Quando se efetua chamadas
recursivas em subprogramas, o controle sobre que versão está ativa em um dado
instante é de responsabilidade apenas do compilador.
A função recursiva Numero_de_Digitos
apresentada a seguir serve para determinar a quantidade de dígitos de um
número inteiro recebido como parâmetro:
Function Numero_de_Digitos (N: integer):
integer;
Begin
if abs(n) <= 9 then
Numero_de_Digitos :=1
else
Numero_de_Digitos := 1 + Numero_de_digitos (N DIV 10)
End;
Para converter um número inteiro
decimal em seu correspondente binário, poderíamos fazer uso da procedure
recursiva Escrever_em_Binario mostrada a seguir:
Procedure escrever_em_binario(N: integer);
begin
if n <= 1 then
write(N: 1)
else begin
escrever_EM_binario(n div
2);
write(n mod 2 : 1)
end;
O cálculo
de x elevado a n não é disponível diretamente em Pascal, mas pode facilmente ser
obtido através da função apresentada a seguir:
function
potencia(x: real; N: integer): real;
begin
if n = 0 then
potencia = 1
else if n < 0 then
potencia := 1 / potencia(x,
abs(n))
else
potencia
:= x * potencia(x, n - 1)
end;
O algoritmo da função acima pode
ser melhorado, obtendo-se significativa melhoria no seu tempo de execução (o
número de ativações recursivas decresce de N
para log2(N):
function
potencia(x: real; n: integer): REAL;
begin
if n = 0 then
potencia = 1
else if n < 0 then
potencia := 1 / potencia(x,
abs(n))
else if odd(n) then
potencia := x *
sqr(potencia(x, (n - 1) div 2))
else
potencia
:= sqr(potencia(x, n div 2))
end;
A soma dos elementos de um array A de n inteiros pode ser
obtida com
FUNCTION
SOMA(A: TIPO_ARRAY; N: INTEGER);
BEGIN
IF N <= 0 THEN
SOMA := 0
ELSE
SOMA
:= A[N] + SOMA(A, N-1)
END;
Uma função para encontrar o menor
dos elementos de um array de
inteiros, analogamente, pode ser:
function
menor(a: tipo_array; n: integer): integer;
begin
if n <= 0 then
menor := maxint
else
menor
:= Min(a[n], menor(a, n-1)
end;
Onde MAXINT é uma constante simbólica definida em PASCAL e MIN é o nome de uma função que retorna
o menor entre dois inteiros.
Um outro exemplo clássico de definição recursiva é o
de fatorial de N:
N! = {1 se
N = 1, e
{
N(N-1)!, caso contrário.
Em termos de programação PASCAL isto poderia ser codificado
como no programa ENCONTRA_FAT a
seguir.
PROGRAM
ENCONTRA_FAT;
/* ENCONTRA O FATORIAL DE UM NUMERO
INTEIRO */
FUNCTION
FATORIAL (N: INTEGER): INTEGER;
/* Recebe um inteiro e retorna um
inteiro */ BEGIN
IF N = 1
THEN FATORIAL := 1 /* O FATORIAL DE 1 */
ELSE FATORIAL := N *
FATORIAL(N-1) /* ATIVAÇÃO RECURSIVA */
END;
VAR
NUMERO: INTEGER;
BEGIN /*
PROGRAMA PRINCIPAL */
WRITELN('INFORME UM NUMERO INTEIRO');
READLN(NUMERO);
WRITELN('O NUMERO LIDO FOI', NUMERO: 3,
' E SEU FATORIAL EH', FATORIAL(NUMERO)) /*
ATIVAÇÃO DA FUNÇÃO */
END.
Sugerimos implementar e testar estas rotinas no computador.