Ir para o conteúdo

PARTE VI - Recursividade. Variáveis de Texto Simples. Estrutura de tentativa. Lista padrão do Pascal.

Esta parte vai dar mais uns “toques” sobre Pascal, abordando a recursividade, uma capacidade muito importante do Pascal, terminar as variáveis Text com um exemplo prático sobre escrita, leitura e análise de ficheiros de texto simples, e a estrutura de tentativa, extremamente importante para garantir a estabilidade de um programa em processos que são susceptíveis de gerar erros inesperados.

1. Recursividade

Uma das características mais curiosas e impressionantes do Pascal é o facto de as funções e os procedimentos poderem chamar-se a si mesmos. Isto é, para realizar um processo ou um cálculo podem-se chamar a si mesmos para auxiliar ou completar tal acção. O exemplo mais famoso é, sem dúvida, o do cálculo do factorial de um número. Até agora, colocando um problema destes, utilizar-se-ia quase de certeza uma estrutura de repetição For… Downto… para resolver o problema. Contudo, existe uma forma que, até certa medida, é mais intuitiva. Aplicando-se o postulado matemático de que n! = n * (n-1)!, verifica-se que pode ser utilizada a capacidade da recursividade. Contudo, qualquer estrutura recursiva deve incluir uma estrutura de finalização, que mais não é, geralmente, uma estrutura de decisão If… Then… Else…. Vejamos a resolução do problema do factorial:

program factorial_recursivo;
uses crt;
var numero : integer;  // Só um número inteiro positivo “tem” factorial

function factorial(n : integer) : integer;
(* Função Recursiva *)
begin
     if n<0 then factorial := 0
     // se não é positivo, não há factorial: a função devolve um Integer, logo devolveremos “0”.
     else begin
          if n<=1 then factorial := 1  // Se é 0 ou 1, o factorial é 1 (0! = 1 por postulado matemático)
          else factorial := n * factorial(n-1);  // Restantes casos, aplica a fórmula n! = n * (n-1)!
     end;
end;

begin  (* BLOCO PRINCIPAL *)
     write('Introduza numero inteiro: ');
     readln(numero);
     writeln;
     writeln('O factorial de ', numero, ' e: ', factorial(numero));
     readkey;
end.

Caso a função fosse tão-somente

function factorial(n : integer) : integer;
begin
     factorial := n * factorial(n-1);  // Restantes casos, aplica a fórmula n! = n * (n-1)!
end;

O computador entraria num processo infinito, pois não há nada que lhe indique que o factorial é calculado por n! = n * (n-1) * (n-2) * … * 3 * 2 * 1. O programa iria abaixo quase instantaneamente.

2. Variáveis Text – parte 2

Após terem sido dadas as bases sobre ficheiros de texto simples e outros novos conhecimentos, será criado, para terminar este tipo de dado, dois programas distintos: um cria um ficheiro com dados do utilizador, e outro abre o ficheiro, analisa-o, e diz quais esses dados. Seja o nome desse ficheiro, criado e analisado, com extensão, dados.txt. O programa que escreve dados no ficheiro é simples, aplicando os conhecimentos adquiridos na Parte IV, somado aos exemplos. Neste programa, para distinguir algumas variáveis de constantes, as últimas estarão realçadas com maiúsculas.

program guarda;
(* Programa que guarda dados num ficheiro *)
var fich : text;  // variável TEXT
    dados : record   // Registo de dados
          nome : string[40];
          idade : 0..100;
          sexo : char;
          profissao : string[40];
    end;

const nome_fich = 'dados.txt';  // nome do ficheiro
      SEXOS = ['M', 'F'];   // dois sexos possíveis
      IDADES = [0..100];  // idade aceites

begin (* BLOCO PRINCIPAL *)
     assign(fich, nome_fich);
     rewrite(fich);
     writeln('Ficheiro aberto.');
     writeln;
     writeln('Introduza os seguintes dados...');

     begin (* LEITURA DOS DADOS *)
          with dados do begin
               write('   Nome: '); readln(nome);
               repeat
                     write('   Idade (0-100): '); readln(idade);
               until idade in IDADES;
               repeat
                     write('   Sexo (M/F): '); readln(sexo);
               until upcase(char(sexo)) in SEXOS;
               write('   Profissao: '); readln(profissao);
          end;
     end;

     begin (* GRAVAÇÃO DOS DADOS *)
          with dados do begin
               writeln(fich, 'DADOS');
               writeln(fich, 'Nome: ', nome);
               writeln(fich, 'Idade: ',idade);
               writeln(fich, 'Sexo: ',sexo);
               writeln(fich, 'Profissao: ',profissao);
          end;
     end;
     close(fich);
     writeln;
     write('Dados gravados. Ficheiro encerrado.');
     readln;
end.

Como se verifica, o programa lê os dados e só de seguida os grava no ficheiro. Para melhor estruturação, estas duas funções diferentes foram inseridas dentro de blocos Begin… End.

O programa que e analisa os dados é o mais complicado. O método abordado irá permitir entender como ler o ficheiro e obter dele certas informações de forma individualizada. Por exemplo, ao ler no ficheiro Nome: Thoga31, poderá, num outro programa, ser de interesse gravar somente o nome em si, e não a linha de texto toda. Para tal, o programa que se segue, apesar de não utilizar os dados para nada em especial, vai abordar o método geral que permite esta diferenciação. Isto tornar-se-á útil no caso de se criar um programa com opções e, para que o utilizador não esteja sempre a redefini-las, possas definir de uma vez e ficar gravado num ficheiro que é acedido pelo programa para que este se “personalize” conforme as preferências ditadas pelo utilizador numa anterior utilização. Antes de apresentar o programa, há que saber a função padrão eof: retorna um booleano, e será True quando atinge o fim de um ficheiro.

program analisa;
(* Programa que analisa os dados do ficheiro *)
uses crt;
var fich : text;
    linha : string;  // uma linha do conteúdo do ficheiro
    conteudo : array[1..2] of string;  // diferentes conteúdos do ficheiro:
    // e.g.: "NOME: Thoga31" - conteudo[1] = "NOME:"; conteudo[2] = "Thoga31".
    i, j : integer; // contadores

const nome_fich = 'dados.txt';

begin (* BLOCO PRINCIPAL *)
     assign(fich, nome_fich);
     reset(fich);
     writeln('Ficheiro aberto.');
     writeln;

     while not eof(fich) do begin  (* ANALISA CONTEÚDO *)
     (* Enquanto não chega ao fim do ficheiro de texto FICH... *)
           readln(fich, linha); // lê a presente linha.
           conteudo[1] := ''; // no início, não há conteúdo.
           conteudo[2] := ''; // idem.
           j := 1; // começa pelo conteúdo nº1.
           for i:=1 to length(linha) do begin
           // analisa LINHA, caracter a caracter.
               if linha[i]=' ' then j += 1
               // se é espaço, o conteúdo muda (aumenta 1).
               else begin
                    if j in [1, 2] then conteudo[j] += linha[i];
                    // o array só tem 2 elemento. Logo, se, por erro, j>2, nada é feito;
                    // o caracter lido é adicionado a conteudo[j].
               end;
           end;
           if linha <> 'DADOS' then write('   ');
           // faz "tabulação" aos dados em si, não ao título do ficheiro
           writeln(conteudo[1],' ',conteudo[2]);
     end;

     close(fich);
     writeln;
     write('Ficheiro encerrado.');
     readln;
end.

Neste programa, como o ficheiro a analisar tem no máximo um espaço em cada linha (excepto em possível alteração feita fora do programa), definiu-se um array unidimensional com somente dois conteúdos: o conteúdo da linha nº 1 – antes do espaço, e o nº 2 – depois do espaço. Como explicado em comentário, caso o programa leia, por exemplo, Nome: Thoga31, ele irá definir que conteúdo[1] = “Nome:”, que é o texto que está antes do espaço, e conteúdo[2] = “Thoga31”, que é o texto depois do espaço, e que seria o dado de destaque em toda a linha lida. O método utilizado é exagerado para este caso particular, pois bastava ler a linha e escrevê-la na janela do programa. Contudo, como explicado, este método pretende mostrar ao mesmo tempo como separar exemplarmente o conteúdo de uma linha para fins já exemplificados e referidos.

3. Sucessor e predecessor

Em muitos tipos de dados pode ser definido o conceito de successor e predecessor

  • Sucessor – elemento que se segue ao actual;
  • Predecessor – elemento que antecede o actual.

Por exemplo, o predecessor de “C” é “B”, e o sucessor é “D”. O predecessor de “6” é “5”, e o sucessor é “7”. Os comandos que nos dão o sucessor e o predecessor de um elemento são:

succ(elemento);  // Sucessor
pred(elemento);  // Predecessor

4. Estrutura de tentativa – Try… Except… Finally…

Aquando a sua origem, o Pascal não incluía estruturas de tentativa. Contudo, com o evoluir do mundo da programação, o Pascal também o teve de fazer, e, hoje em dia, qualquer compilador moderno reconhece e compila esta estrutura. Teoricamente, esta permite que o programa tente realizar uma operação de forma segura, que, de outra forma, poderia criar um erro inesperado e encerrar o programa instantaneamente. O exemplo típico é o da divisão de um número por zero. Teoricamente, a estrutura de tentativa apresenta este aspecto:

 Tentar
       Executar Processo
 Em erro
       Realizar uma série de acções

Caso o processo gira um erro inesperado, a tentativa falha, pois tal não é suportado, pelo que realiza os códigos seguintes. Ou seja, caso as acções a tentar falhem, ocorre uma Excepção (exception). Em Pascal, ainda é suportado o seguinte: independentemente da tentativa falhar ou não, pode-se “acrescentar” algo para além da futura excepção – para este “acrescento” existem várias designações a circular. Contudo, uma boa designação será, porventura, sufixo – o “acrescento” acaba por ser um sufixo (mensagem) da tentativa, quer tenha sucesso quer não. Para utilizar esta estrutura, é necessária uma nova biblioteca: a sysutils – utilidades do sistema. A estrutura básica é a seguinte:

try
   comandosA;
except
      comandosB;
end;

No caso de se querer fazer o “acrescento” à tentativa:

try
   try
      comandosA;
   finally
          comandosB;
   end;
except
   comandosC;
end;

Este último modelo é considerado por muitos o modelo padrão. Contudo, devido ao facto de o sufixo não ser obrigatório, e muitas das vezes importante, a primeira estrutura pode ser considerada o modelo padrão, por ser a mais simples e a que executa as grandes operações-objectivo desta estrutura. Veja-se, então, a aplicação do exemplo da divisão por zero, onde esta operação se torna obrigatória:

program tentativa;
uses crt, sysutils;
var i, j, resultado : integer;

begin
     i := 5;
     j := 0;  // o denominador vale zero
     write('A tentar dividir, de forma segura, 5 por 0... ');
     try  (* ESTRUTURA DE TENTATIVA *)
        resultado := i DIV j;
        write(resultado);
     except  (* No caso de excepção *)
           ON E:Exception do begin
           // EXCEPTION é um tipo de dado que indica o tipo de erro ocorrido
              writeln('Falhou');
           end;
     end;
     readln;
end.

Por exemplo, para aplicar aqui a estrutura Finally, pode-se indicar ao utilizador que a operação se realizou. Desta vez perguntar-se-á os valores da operação ao utilizador:

program tentativa;
uses crt, sysutils;
var i, j, resultado : integer;

begin
     write('Introduza numerador: '); readln(i);
     write('Introduza denominador: '); readln(j);
     writeln;
     write('A tentar dividir, de forma segura, ',i,' por ',j,'... ');

     try  (* ESTRUTURA DE TENTATIVA *)
        try  // Tentativa com “sufixo”
           resultado := i DIV j;
           write(resultado);
        finally  // …“FINALMENTE” – sufixo
               write(' [Terminado]');
     end;
     except  (* EXCEPÇÃO *)
           ON E:Exception do begin
              write('[Falhou]');
           end;
     end;  (* FIM DA ESTRUTURA *)

     writeln;
     readln;
end.

Vejam-se dois diferentes outputs (os dados introduzidos pelo utilizador encontram-se sublinhados):

 Introduza numerador: 32
 Introduza denominador: 4

 A tentar dividir, de forma segura, 32 por 4... 8 [Terminado]
 Introduza numerador: 375
 Introduza denominador: 0

 A tentar dividir, de forma segura, 375 por 0...  [Terminado][Falhou]

A estrutura Finally serviu, neste caso, para indicar quando a tentativa terminou. Em caso de excepção, mostra a mensagem adicional “Falhou”. Caso tenha sucesso, antes da mensagem “Terminado”, é mostrado o resultado da divisão. Qualquer processo que seja susceptível de ocorrer um erro pode ser encaixado numa estrutura de tentativa, pois evita sistemas complicados de análise e de segurança: quando ocorre o erro, o programa nada mais faz do que, após ter tentado, anunciar a excepção ocorrida. Alguns procedimentos padrão já têm “imbuído” em si uma espécie de estrutura de tentativa, como é o caso de val – o terceiro parâmetro, quando declarado, armazena a localização do primeiro erro que não permitiu a conversão de uma string em valor numérico. Este procedimento, por defeito, e quando utilizado o terceiro parâmetro, não necessita de uma estrutura Try… Except.

5. Lista padrão do Pascal

Como qualquer linguagem de programação, existe uma lista padrão dos procedimentos e das funções padrão e das palavras reservadas. As listas que se seguem apenas contêm as palavras reservadas, funções e procedimentos padrão abordados até ao momento, pois existe mais.

5.1. Palavras reservadas

As palavras reservadas são aquelas que não podem ser redefinidas e fazem os pilares da sintaxe de uma linguagem de programação. Realizam acções e/ou processos bem definidos. Palavras Reservadas originárias

Palavras Reservadas
AND ELSE LABEL REPEAT WHILE
ARRAY END MOD SET WITH
BEGIN FILE NOT STRING
CASE FOR OF THEN
CONST FUNCTION OR TO
DIV GOTO PROCEDURE TYPE
DO IF PROGRAM UNTIL
DOWNTO IN RECORD VAR

Palavras Reservadas modernas

  • EXCEPT
  • FINALLY
  • TRY

5.2. Funções e identificadores padrão

As funções padrão são aquelas que já estão incluídas nas bibliotecas: algumas pré-existem mesmo sem qualquer biblioteca definida. Algumas destas funções são, de igual forma, identificadores, que são palavras que fazem a identificação, por exemplo, de tipos de dados pré-existentes – por exemplo, char é um identificador do tipo de variável caracter, bem como é a função que devolve o caracter de uma certa ordem na tabela ASCII. Segue-se a lista com as funções e identificadores:

Funções e Identificadores
abs false readln sqr write
arctan integer real sqrt writeln
boolean ln reset succ
char maxint1 rewrite text
cos ord round true
eof pred sin trunc

5.3. Procedimentos padrão

O que foi dito sobre as funções padrão aplicam-se aos procedimentos padrão.

  • assign
  • close
  • read
  • readln
  • reset
  • rewrite
  • write
  • writeln

5.4. Operadores

Os operadores têm uma ordem pela qual são executados. Por exemplo, nas quatro operações básicas, a multiplicação e a divisão são executadas antes que a soma e a subtracção. Segue-se a lista com os operadores ordenados na sua precedência natural, do mais alto (1) para o mais baixo (4):

  - NOT
  - * / DIV MOD AND
  - + - OR
  - = <> < <= > >= IN

5.5. Tipos de dados

Um sumário dos tipos de dados abordados até agora:

  • array
  • boolean
  • char
  • integer
  • real
  • record
  • set
  • string

5.6. Tabela ASCII

De computador para computador, a tabela ASCII pode sofrer alterações. Contudo, segue-se uma tabela ASCII completa, geralmente sendo designada por tabela padrão, a mais comum de se ver.

Tabela ASCII (1)

Tabela ASCII (2)


  1. maxint é o identificador que dá o valor inteiro (Integer) máximo suportado pelo Pascal.