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.
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.
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.
Em muitos tipos de dados pode ser definido o conceito de successor e predecessor
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
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.
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.
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
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 |
O que foi dito sobre as funções padrão aplicam-se aos procedimentos padrão.
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):
Um sumário dos tipos de dados abordados até agora:
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.