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

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.

 

Voltar para o Índice