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 lê 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.
-
maxint é o identificador que dá o valor inteiro (Integer) máximo suportado pelo Pascal. ↩