Prévia do material em texto
Algoritmos e Programação de Computadores Norton Trevisan Roman norton@ic.unicamp.br http://www.ic.unicamp.br/~norton/ Material obtido na Internet e editado com autorização do autor através de e-mail disponibilizado na próxima página Prof. José Garibaldi de Carvalho zegariba@gmail.com Data: Wed, 23 Jun 2004 01:00:26 +0100 De: ntr <Norton.Roman@itri.brighton.ac.uk> Para: gariba@colegiosantanh.com.br Assunto: Apostilas disponibilizadas Boa noite. Bom, não posso responder pelos outros, mas da minha parte, tu podes usá- la à vontade. Aliás, me chamou a atenção... a apostila de grafos também é minha... tá errado no site (o link leva à minha página...) enfim, acredito que queiras a apostila de Pascal. Então não há problema algum. E se quiser a de grafos, pode usar também. Abraço Norton p.s. Por um lapso meu, a apostila não está completa... mas na página principal da disciplina, eu indico o que está completo e o que não está. Consulte: http://www.dcc.unicamp.br/~norton/paginas/mc102/mc102.html. p.s2. Gostaria muito de receber qualquer comentário sobre ela... partes que não estão claras, erros etc. Isso ajudaria muito a melhorá- la. Obrigado. Prezados Professores: Sou professor do curso Técnico de Informática (segundo grau) do Colégio Santa Catarina em Novo Hamburgo - RS. Ministro as disciplinas de Algoritmos e Lógica e Programação. Nosso colégio participou da Olimpíada 2004 na modalidade programação. Na página disponibilizada pela OBI, encontram-se apostilas (excelentes) elaboradas pelos Senhores. Minha pergunta é: Existe a possibilidade de as utilizarmos (impressas - resguardando autoria) com os alunos do Colégio em nossas aulas? Desde já agradeço pela atenção. Prof. José Garibaldi de Carvalho mail: gariba@colegiosantanh.com.br Colégio Santa Catarina Rua General Osório, 729 Cep: 93510-160 Novo Hamburgo - RS Fone: (51) 527-4862 SUMÀRIO INTRODUÇÃO ................................................................................................................5 ESTRUTURA DE UM PROGRAMA ............................................................................5 COMENTÁRIOS..............................................................................................................5 SAÍDA ...............................................................................................................................6 PROCEDIMENTOS.........................................................................................................9 IDENTIFICADORES.....................................................................................................11 EXPRESSÕES ................................................................................................................12 VARIÁVEIS ...................................................................................................................15 ATRIBUIÇÕES ..............................................................................................................15 O COMANDO FOR .......................................................................................................18 ITERAÇÃO.....................................................................................................................21 LAÇOS ANINHADOS ..................................................................................................24 ITERAÇÕES ANINHADAS .........................................................................................25 DOWNTO .......................................................................................................................26 RECURSÃO....................................................................................................................29 MEMÓRIA DINÂMICA ...............................................................................................37 LISTAS LIGADAS ........................................................................................................42 FILAS ..............................................................................................................................54 PILHAS ...........................................................................................................................54 ARQUIVOS DE ACESSO SEQÜENCIAL..................................................................56 STRINGS.........................................................................................................................67 VETORES (ARRAY).....................................................................................................72 MATRIZES .....................................................................................................................79 BUSCA BINÁRIA..........................................................................................................84 REGISTROS ...................................................................................................................88 TIPOS ENUMERADOS ................................................................................................98 PRED E SUCC..............................................................................................................100 O COMANDO CASE...................................................................................................102 TIPO SUBRANGE (INTERVALO) ...........................................................................107 TYPE .............................................................................................................................108 FUNÇÕES.....................................................................................................................111 PASSAGEM DE PARÂMETROS ..............................................................................116 POR VALOR E REFERÊNCIA ..................................................................................116 VARIÁVEIS LOCAIS E GLOBAIS...........................................................................124 PROCEDIMENTOS.....................................................................................................128 DIAGRAMAS DE EXECUÇÃO ................................................................................130 ENTRADA E SAÍDA...................................................................................................135 CARACTERES.............................................................................................................136 LENDO E ESCREVENDO CARACTERES .............................................................137 CARACTERES E A TABELA ASCII........................................................................140 TABELA ASCII............................................................................................................143 O COMANDO REPEAT..............................................................................................145 O COMANDO WHILE................................................................................................148 TIPO BOOLEAN..........................................................................................................153 OPERADORES BOOLEANOS ..................................................................................154 TIPO REAL...................................................................................................................155 CONVERSÃO DE TIPOS ...........................................................................................156 O COMANDO IF..........................................................................................................158 CONDIÇÕES ................................................................................................................159 ELSE..............................................................................................................................160 AND...............................................................................................................................162OR ..................................................................................................................................162 ANIMAÇÃO BÁSICA................................................................................................. 164 ENTRADA....................................................................................................................168 CONSTANTES.............................................................................................................171 5 INTRODUÇÃO Para a parte prática do curso, a linguagem ensinada é o Pascal. A partir de agora, as notas de aula se destinarão ao aprendizado desta linguagem. Todos os programas apresentados aqui funcionam corretamente se compilados usando o Pascal ESTRUTURA DE UM PROGRAMA A estrutura de um programa pascal é a seguinte: PROGRAM nome; {declarações} BEGIN {comandos} END. "PROGRAM nome" dá um nome ao programa, enquanto que "BEGIN" avisa o computador onde começa o programa e "END" onde termina. A parte de "{declarações}" será vista mais tarde. Já "{comandos}" deve conter os comandos a serem executados no programa (vistos a seguir). No caso acima, como não há nenhum comando no corpo do programa (espaço entre o BEGIN e o END), nosso programa não executa nada. COMENTÁRIOS Comentários são observações que o programador faz no código do programa para poder entendê-lo melhor mais tarde, ou permitir que outros possam entender mais facilmente o programa. É parte da documentação do programa. Em Pascal há 2 formas de escrevermos comentários: Entre (* e *) Entre { e } Assim, as palavras "declarações" e "comandos" no código acima são comentários. Uma observação importante sobre comentários é que você não precisa "casar" os { ou (*. O computador, ao encontrar um { ou (* ignora o que vem após até encontrar um } ou *) (se você abriu com (*, ele ignora até encontrar um *) e se abriu com { até encontrar um }). Assim, {{{{comentário} está certo, enquanto que {{com}} está errado, pois o primeiro } é suficiente para terminar o comentário, e o segundo será visto como erro. 6 SAÍDA A saída de dados padrão é a tela do computador. Então, agora vamos ver como escrever mensagens na tela da máquina. Para escrever algo na tela usamos o comando "write" ou "writeln". Write escreve a mensagem pura e simplesmente, enquanto que writeln escreve a mensagem e pula para a linha de baixo. Considere, então o seguinte programa: PROGRAM escreve; BEGIN write('alô'); write('você'); END. Sua saída será: alôvocê Notou a falta do espaço? Poi é, ele precisa ser explicitamente incluído: PROGRAM escreve; BEGIN write('alô '); write('você'); END. Sua saída será: alô você Agora, qual a diferença entre write e writeln? Vejamos o mesmo programa com writeln: PROGRAM escreve; BEGIN writeln('alô '); writeln('você'); END. Sua saída será: alô você 7 Notou? um em cada linha. O comando writeln pode também ser usado para incluir uma quebra de linha, assim: PROGRAM escreve; BEGIN write('alô '); writeln; write('você'); END. Gera: alô você E: PROGRAM escreve; BEGIN writeln('alô '); writeln; write('você'); END. Gera: alô você Podemos também combinar os comandos: PROGRAM escreve; BEGIN write('alô '); writeln('você'); write('aí!'); END. E teremos: alô você aí! Uma consideração final: e se quisermos imprimir a aspa simples (')? Como vimos, ela é parte integrante do write e writeln. Para imprimir uma aspa simples precisamos usar duas aspas simples. Assim: PROGRAM escreve; BEGIN write(''''); END. 8 Escreverá na tela: ' Ou seja: PROGRAM escreve; BEGIN write('copo d''água'); END. Escreverá na tela: copo d'água 9 PROCEDIMENTOS Suponha que queremos desenhar na tela dois quadros do tipo: **** * * * * **** Como faremos? Pelo que sabemos até agora, temos que: 1) escrever **** 2) escrever * * 3) escrever * * 4) escrever **** 5) escrever uma linha em branco 6) escrever **** 7) escrever * * 8) escrever * * 9) escrever **** Mas esse trabalho pode ser decomposto em duas partes: 1) desenho um quadrado 2) escrevo uma linha em branco 3) desenho outro quadrado Agora, como uso essa segunda abordagem no programa? Através de um procedimento. Um procedimento é um pedaço de programa que executa um conjunto de comandos. Vejamos como declarar um procedimento: PROCEDURE nome; {declarações} BEGIN {comandos} END; Notou a semelhança com o modo como definimos o programa? Pois é, podemos concluir, então, que um programa é um grande procedimento, ou que um procedimento é um pequeno programa dentro do programa. 10 Agora vamos inserir o código para desenhar um quadrado nesse nosso procedimento: PROCEDURE dois_quad; BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); END; Ótimo, mas agora onde colocamos isso no programa? PROGRAM quad; PROCEDURE dois_quad; BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); END; BEGIN {comandos} END. Simples não? Lembre que esse é o lugar da definição de procedimentos. Mas esse nosso programa não faz nada! Como fazemos para desenhar os dois quadrados? Bom, lembra o nosso segundo algoritmo? Desenho um quadrado, dou uma linha em branco e desenho o outro. Ou seja: PROGRAM quad; PROCEDURE dois_quad; BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); END; BEGIN dois_quad; writeln; dois_quad; END. 11 E a saída será: **** * * * * **** **** * * * * **** Você pode estar se perguntando: mas isso deu mais trabalho do que escrever os 2 quadrados. Sim, para dois deu, mas veja que para 3 não mais e, se o número de quadrados a serem desenhados for grande, você pode poupar muito tempo de trabalho. Essa é a vantagem do uso de procedimentos, você só precisa escrever códigos repetitivos uma só vez. Agora, como isso funciona? É, de fato, simples. Cada vez que o computador encontra o nome de um procedimento ele faz um desvio para o início deste procedimento, executa seu código (o que está entre seu BEGIN e END) e, ao terminar o procedimento, retorna ao comando seguinte à chamada deste. A figura abaixo ilustra essa situação: Por fim, note que declaramos o procedimento antes de usá-lo. Em Pascal temos que fazer isso sempre. IDENTIFICADORES Um identificador é um nome que damos a algo no pascal, seja um procedimento, o nome do programa etc. 12 Tais nomes, em Pascal estão sujeitos às seguintes regras: ? podem somente começar com letras ou "_". ? devem conter somente letras, números e "_". ? não devem ser palavras reservadas da língua, como "program", "procedure", "begin" etc. Uma observação importante é que em Pascal letras maiusculase minúsculas são consideradas iguais em identificadores, então, assim como chamamos o procedimento para escrever um quadrado com "um_quad", poderiamos chamar com "UM_quad" ou qualquer outra variação. Vale ressaltar também que é uma boa técnica de programação fazer com que os identificadores sejam uma pista do motivo pelo qual existem, assim como o procedimento "um_quad", que desenha um quadrado na tela. EXPRESSÕES O computador não serve somente para escrever na tela, podemos pedir para que ele execute operações aritméticas também: Comando Saída write(5); write(3 + 2); write('3 + 2'); 5 5 3 + 2 Note a terceira linha, que é lida como texto. As operações permitidas são: + Soma - Subtração * Multiplicação / Divisão DIV parte inteira da divisão. 11 DIV 4 = 2 MOD resto da divisão. 11 MOD 4 = 3 (o resto) O "-" pode também ser usado para dizer que um número é negativo: Comando Saída write(-(3*5)); write(-11 DIV 4); write(-11 DIV -4); write(-11 MOD 4); write(-11 MOD -4); -15 -2 2 -3 -3 13 Agora, vejamos um outro uso do write e writeln (os exemplos abaixo valem para os dois): Comando Saída write(5+9,' é a soma'); write('5 + 9 = ',5+9); write('5 + 9 = ',5+9,' (total)'); 14 é a soma 5 + 9 = 14 5 + 9 = 14 (total) Note o espaço no início da frase "é a soma". Se ele não existir, a saída será "14é a soma". Podemos ter, também, operações múltiplas: Comando Saída write(5 + 6 + 7); write(5 + 6 - 7); write(3 + 4 * 5); 18 4 ?? Ops. E agora? Qual o resultado de 3+4*5? Podemos escolher a operação que será executada antes usando parênteses: Comando Saída write(3 + (4 * 5)); write((3 + 4) * 5); 23 35 Porém, nesse caso simples, onde queremos efetivamente 3 + (4 * 5), não precisamos de parênteses. Como o computador sabe a ordem então? Em pascal, cada operador tem uma precedência, indicando a ordem em que as operações podem ser executadas. A ordem em que as operações serão executadas é: DIV, MOD, *, / , +, - Quando dois operadores de igual precedência ocorrem, são executados da esquerda para a direita. Então: 3 + 4 * 5 equivale a 3 + (4 * 5) 6 - 7 + 8 equivale a (6 - 7) + 8 Mas sempre podemos mudar a precedência, usando parênteses. Por exemplo, fazendo 4 - 6 + 7 estaremos fazendo (4 - 6) + 7. Agora, se colocarmos os seguintes parênteses 4 - ( 6 + 7 ) e s t a r e m o s f o r ç a n d o a a d i ç ã o a s e r c a l c u l a d a a n t e s . Nesse caso, o computador considera como sendo a subtração de dois termos, sendo um deles uma expressão que deve ser calculada antes da subtração. Isso nos mostra uma característica importante da linguagem: parênteses têm a maior precedência dentre todos os operadores e, se houverem parênteses dentro de parênteses, o mais interno tem precedência maior. 14 Então, de um modo geral: ? Calculamos o que está dentro dos parênteses (do mais interno ao mais externo): 3 + (4 + (5 + 6)) — › 5 + 6 = 11 4 + 11 = 15 15 + 3 = 18 ? Calculamos as operações de maior precedência da esquerda para a direita: 2 + 3 * 5 / 2 — › 3 * 5 = 15 15 / 2 = 7.5 ? Por fim, calculamos os termos de menor precedência da esquerda para a direita. 2 + 3 * 5 / 2 — › 2 + 7.5 = 9.5 Agora, veja a saída desse comando: Comando Saída writeln(2 + 3 / 2); 3.500000000000000e+00 Naturalmente, essa não é a saída desejada. Gostaríamos que tivesse, por exemplo, apenas 2 casas decimais apenas. Mas como fazer isso? Comando Saída writeln(2 + 3 / 2 : 2 : 2); 3.50 O número após o primeiro ":" indica o tamanho mínimo reservado para a variável, já o número após o segundo ":" indica o número de casas decimais (no caso de variável REAL). 15 VARIÁVEIS São lugares onde armazenamos e de onde lemos valores. Devem ser declaradas antes de usadas. Veja onde declarar variáveis: No programa No procedimento PROGRAM x; VAR variável : tipo; BEGIN ... END. PROCEDURE y; VAR variável ; tipo; BEGIN ... END; Uma variável declarada no programa é visível a partir de todo o programa (inclusive procedimentos) e é chamada de variável global. Já uma variável declarada em um procedimento só é visível dentro deste procedimento, não podendo ser vista por elementos de fora dele. Tal variável é chamada de local. Agora, o que acontece se temos uma variável local com um mesmo nome de uma variável global? Nada, só que dentro do procedimento onde esta local está declarada, a global não é vista. Então, de forma geral, a declaração de uma variável é: VAR var1 : tipo1; var2 : tipo2; var3, var4 : tipo3; O tipo da variável indica o tipo de dado que ela armazenará. O computador precisa desse dado para saber o quanto de memória vai reservar para guardar a variável. Veja alguns tipos numéricos: INTEGER inteiros de -32768 a 32767 REAL reais de 2.9E-39 a 1.7E38 (negativo inclusive) SHORTINT inteiros de -128 a 127 WORD inteiros de 0 a 65535 LONGINT inteiros de -2147483648 a 2147483647 BYTE inteiros de 0 a 255 CHAR caracter Há outros tipos, mas esses são os tipos mais simples (outros serão vistos mais tarde). ATRIBUIÇÕES Serve para que possamos por valores nas variáveis. Sua sintaxe é: 16 variável := expressão; Onde "expressão" pode ser um número ou uma expressão matemática, por exemplo: x := 3; O valor 3 é armazenado na variável x. y := 4; O valor 4 é armazenado na variável y. z := x + y; O valor que está em x é somado ao que está em y, sendo o resultado armazenado na variável z. Vale lembrar que quando guardamos um valor numa variável, o valor antigo que estiver lá será perdido. Agora não confunda! x := 2 não quer dizer "x é igual a 2", mas sim "o valor 2 será guardado em x". Assim: Comando Significado x := y; uma cópia do valor que está em y será guardada em x. O valor que estava antes em x, se havia algum, será perdido. x := x; uma cópia do valor que está em x será guardada em x de volta, perdendo o valor antigo. Dessa forma podemos fazer coisas do tipo: x := x + 1; O que isso faz? O computador pega o valor de x, soma 1 a esse e guarda o resultado novamente em x. Então, se x continha o valor 2, após a atribuição x := x + 1; ele conterá 3. Viram? Isso mostra por que não podemos ver x:=2 como "x é igual a 2", pois senão x:=x+1 seria um erro matemático. Agora que sabemos como abastecer valores em variáveis, como fazemos para escrever seus valores na tela? Comando O que faz x := 2; Abasteço x com o valor 2. write(x); Escreve o valor de x na tela, no caso, 2. mas podemos enfeitar ainda mais, veja abaixo: Comando Saída write('x = ',x); x = 2 write('x = ',x,' (total)'); x = 2 (total) 17 reparou nos espaços entre o "=" e o "'" e entre o "'" e "(total)"? São necessários, senão a saída seria x =2(total). Agora vamos ver uma aplicação bem simples: PROGRAM media; USES crt; {para entrada e saída} VAR p1 : REAL; {nota da prova 1} p2 : REAL; {nota da prova 2} p3 : REAL; {nota da prova 3} m : REAL; {média das provas} BEGIN {dou as 3 notas} p1 := 3.0; p2 := 5.5; p3 := 6.5; {calculo a média} m := (2*p1 + 3*p2 + 5*p3) / 10; writeln('A média de ',p1:3:2,', ',p2:3:2,' e ',p3:3:2,' é ',m:3:2,'.'); END. saída: A média de 3.00, 5.50 e 6.50 é 5.50. Veja a seguinte seqüência de comandos e a saída: Comandos Saída x := 2; y := 3; write(x,y); 23 Viu como as duas variáveis foram escritas lado a lado? Tome cuidado com isso!Por fim, uma outra boa dica é sempre inicializar as variáveis antes de usá-las, atribuindo a elas um valor inicial. 18 O COMANDO FOR Lembra de nosso procedimento para desenhar um quadrado na tela? Lembra como fazíamos para desenhar dois? Nós 1. desenhávamos um quadrado 2. dávamos uma linha em branco 3. desenhávamos outro quadrado 4. dávamos outra linha em branco Ou seja, nós fazíamos o seguinte programa: PROGRAM quadrados; PROCEDURE um_quad; {aqui ía o corpo do procedimento} BEGIN um_quad; writeln; um_quad; writeln; END. Mas e se quiséssemos desenhar 10 triângulos? Teríamos de escrever 10 vezes um_quad; e 10 vezes writeln;. Nossa! Isso não parece certo. Não seria mais fácil ter um algoritmo assim?: 1. faça 10 vezes: 1.1 desenhe um quadrado 1.2 dê uma linha em branco Ou seja, estaríamos dizendo uma só vez "desenhe 10 quadrados" em vez de dizer 10 vezes "desenhe um quadrado". Como fazemos isso em Pascal? Usando FOR: FOR cont:=1 TO 10 DO BEGIN um_quad; writeln; END; E se quisermos um número de quadrados diferente de 10? Basta substituir o 10 por esse número. Reparou na variável cont? Ela é INTEGER, não esqueça de declará-la no VAR. Mas afinal, o que o FOR faz? Vamos ler o comando: FOR cont:=1 TO 10 DO algo 19 Ou seja, "para cont valendo inicialmente 1 até 10 faça algo. Peraí, cont só recebeu 1. Como seu valor pode chegar a 10? é que, a cada repetição, o comando for incrementa cont, ou seja, soma 1 a cont. Assim, ele executa isso 10 vezes, pois após 10 vezes somar 1 a cont, esse chegará a ter 10 (se teve 1 como valor inicial). Então, o for age assim: 1. atribui um valor inicial a cont 2. verifica se esse valor é menor ou igual ao limite 10 3. se for: 3.1. executa o corpo do for (o que está entre o begin e o end) 3.2. faz cont := cont + 1 4. se não for: 4.1. sai do for 5. volta a 2. Agora repare que poderíamos por o writeln dentro do procediemtno um_quad. Façamos: PROCEDURE um_quad; BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); writeln END; Mas, e nesse caso, como usaríamos o for? simples: FOR cont:=1 TO 10 DO BEGIN um_quad END; Somente assim? Não. Há um outro modo: como o corpo do FOR (o que está entre BEGIN e END) contém um só comando, podemos fazer assim: FOR cont:=1 TO 10 DO um_quad; Economizamos duas linhas de escrita! Atenção! Não confunda! Se dentro do for você quiser mais de um comando, deve colocar esses comandos entre BEGIN e END. Por exemplo: FOR cont:=1 TO 10 DO um_quad; writeln('quad'); O que isso faz? Não deixe a edentação te enganar, isso é o mesmo que: 20 FOR cont:=1 TO 10 DO um_quad; writeln('quad'); Ou seja, isso desenha 10 quadrados dando uma linha em branco após cada um e então escreve 'quad'. Se quisermos escrever quad 10 vezes temos que fazer assim: FOR cont:=1 TO 10 DO BEGIN um_quad; writeln('quad'); END; Então, eis o programa completo: PROGRAM quad; VAR cont : integer; PROCEDURE um_quad; BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); writeln; END; BEGIN FOR cont:=1 TO 10 DO um_quad END Poderíamos também por tudo isso no procedimento. Veja onde foi posta agora a declaração da variável: PROGRAM quad; PROCEDURE dez_quad; VAR cont : integer; BEGIN FOR cont:=1 TO 10 DO BEGIN writeln('****'); writeln('* *'); writeln('* *'); writeln('****'); writeln END END; BEGIN dez_quad END. 21 ITERAÇÃO Suponha, agora, que queremos contar os quadrados desenhados. Então, queremos que a saída seja: quad 1 **** * * * * **** quad 2 **** * * * * **** . . . quad 10 **** * * * * **** Como fazemos? Podemos usar a variável cont. Para isso: BEGIN FOR cont:=1 TO 10 DO BEGIN writeln('quad ',cont); um_quad END END. Lembra que o for incrementava cont? Pois é. Podemos usar esse fator a nosso favor. Vejamos outro exemplo. Vamos imprimir os 10 primeiros pares: FOR cont:=1 TO 10 DO writeln(2*cont); E a saída é: 2 4 6 ... 20 E agora os 10 primeiros ímpares: 22 FOR cont := 1 to 10 do writeln(2*cont -1); E teremos: 1 3 5 ... 19 Agora vejamos alguns exemplos (estude-os cuidadosamente, vendo a importância de comentar o código. Fica mais claro não?): 1. Soma dos 100 primeiros inteiros VAR soma : integer; {soma dos números} cont : integer; {contador do for} BEGIN soma := 0; {inicializei a soma} FOR cont := 1 to 100 do soma := soma + cont; {vou somando os números} writeln(soma); END. 2. Soma dos termos de uma P.A. VAR soma : integer; {a soma dos termos} razao : integer; {a razão da PA} cont : integer; {contador do FOR} termo : integer; {cada termo da PA} BEGIN soma := 0; {inicializando a soma} razao := 3; {a razão da PA é 3} termo := 1; {primeiro termo da PA} FOR cont:=1 TO 10 DO BEGIN soma := soma + termo; {adiciono o termo anterior à soma} termo := termo + razão {calculo o próximo termo da PA} END; writeln('A soma de ',cont,' termos da PA de razão ',razao,' com início em 1 é ',soma) END. Vamos ver agora o conteúdo de cada variável após cada iteração: cont soma termo - 0 1 1 1 4 2 5 7 23 3 12 10 4 22 13 5 35 16 6 51 19 7 70 22 8 92 25 9 117 28 10 145 31 A soma de 10 termos da PA de razao 3 com início em 1 é 145 24 LAÇOS ANINHADOS Suponha que quiséssemos desenhar um quadrado 10 X 10 de *. Como faríamos? Faça 10 vezes: escreva uma linha com 10 * Naturalmente, um programa que use esse algoritmo fucniona: FOR cont:=1 TO 10 DO writeln('**********'); Mas, e se quisermos deixar o tamanho do quadrado como variável? FOR cont:=1 TO l DO escreva l vezes um * Como escrevemos l vezes um *? Da mesma forma que fizemos os l conjuntos de l *: faça l vezes: escreva um * Ou seja: FOR cont:=1 TO l DO write('*'); Então, para nosso quadrado: FOR cont:=1 TO l DO BEGIN FOR cont2 := 1 to l do write('*'); writeln; END; Rode o programa e veja sua saída. Será um quadrado com o lado que você determinar em l. Reparou que as duas variáveis contadoras são diferentes? Elas tem que ser diferentes, senão, ao mudarmos o valor de uma, estaremos mudando também o valor da que controla o outro for (pois são a mesma). Por exemplo, veja a execução do seguinte programa, observe o valor das variáveis: l := 3; FOR cont:=1 TO l DO BEGIN FOR cont := 1 to l do write('*'); 25 writeln END; cont 1 do primeiro FOR 1 do segundo FOR 2 do segundo FOR 3 do segundo FOR saída: *** Por que parou? Porque cont já chegou a l no laço interno (de fato, l é 4), então o laço externo entende que já fez as 3 vezes dele também. Não confunda! Não use a mesma variável! Mas, e se tivermos 2 laços disjuntos? Aí podemos usar a mesma variável. Quer um exemplo? Vamos desenhar 2 quadrados l X l, um ao lado do outro: FOR cont:=1 TO l DO BEGIN FOR cont2 := 1 TO l DO write('*'); write(' ');FOR cont2 := 1 TO l DO write('*'); writeln END; Então posso usar a mesma variável, mas somente para laços disjuntos. No caso acima, a coisa funciona porque quando o segundo FOR interno muda o valor de cont2, o primeiro laço não precisava mais desse valor. Mas se um FOR estiver dentro do outro, então eles não são disjuntos, e não posso usar a mesma variável para ambos os contadors. E se, agora, quisermos escrever n quadrados de lado l, um ao lado do outro? FOR cont:=1 TO l DO BEGIN FOR cont2 := 1 TO n DO BEGIN FOR cont3 := 1 TO l DO write('*'); write(' ') END; writeln END; saída (n := 3, l := 3): *** *** *** *** *** *** *** *** *** ITERAÇÕES ANINHADAS 26 Vejamos outro exemplo: desenhe o seguinte triângulo: ...*... ..***.. .*****. ******* Primeiro, vamos observar que esse é um retângulo de pontos e asteriscos de tamanho l X (2l-1), onde l é o número de linhas e (2l-1) o de colunas. Então temos uma relação entre as dimensões do retângulo. Note também que: ? O número de pontos na linha l é de (n-l) à esquerda dos * e (n-l) à direita. ? O número de * na linha l é (2l-1) Ótimo! Temos uma fórmula. Então vamos ao algoritmo: para cada linha l, até n linhas faça: desenhe n-l '.' desenhe 2l-1 '*' desenhe n-l '.' E como fazemos isso em pascal? FOR l:=1 TO n DO BEGIN FOR cont := 1 TO (n-l) DO write('.'); FOR cont := 1 TO (2*l-1) DO write('*'); FOR cont := 1 TO (n-l) DO write('.'); writeln END; Reparou que usei cont para todos? Mais do que isso, notou como os limites dos FOR internos depende do contador do FOR externo? Ou seja, tome o primeiro FOR interno, seu limite é (n-l), ou seja, a cada iteração do FOR externo, o valor de l aumenta, aumentando esse limite. Isso é uma iteração aninhada. Com isso vemos uma característica do FOR: o ponto de início e o de fim de um laço pode ser qualquer expressão de resultado inteiro. Note também que quando l valer n, o primeiro e o terceiro FOR interno não serão executados, pois (n-l) será 0, e o final do laço é menor que o início. DOWNTO 27 Como vimos, o comando FOR incrementa seu contador de forma crescente. Mas, e se quiséssemos decrementar o contador, ou seja, subtrair 1 dele a cada laço, fazendo uma contagem regressiva? Para tal usamos downto: FOR cont:=10 DOWNTO l DO writeln(cont); saída: 10 9 8 ... 3 2 1 Simples não? Vejamos um exemplo, vamos gerar dois triângulos, um em cima do outro, assim: ..*.. .***. ***** ***** .***. ..*.. Como fazemos isso? Usamos o seguinte algoritmo: escrevemos um triângulo normal escrevemos um triângulo de cabeça para baixo A primeira parte já vimos como fazer, mas e a segunda? Como desenhamos um triângulo de cabeça para baixo? Vamos primeiro ver como desenhávamos um triângulo normal: escrevíamos a primeira linha escrevíamos a segunda linha ... escrevíamos a n-ésima linha E como fazemos com o invertido? escrevemos a n-ésima linha escrevemos a (n-1)-ésima linha ... escrevemos a primeira linha 28 Ou seja: FOR l:=n DOWNTO 1 DO BEGIN FOR cont := 1 TO (n-l) DO write('.'); FOR cont := 1 to (2*l-1) DO write('*'); FOR cont := 1 to (n-l) DO write('.'); writeln END; Se antes o FOR não era executado se contador > limite, agora não será se contador < limite. Por exemplo, o seguinte for: FOR cont:=1 DOWNTO 2 DO write('não vai escrever'); não é executado nenhuma vez. 29 RECURSÃO Recursão é uma técnica de programação na qual chamamos uma função ou procedimento de dentro dela mesma. Ou seja, a definição de recursão poderia ser: Recursão -> vide Recursão Mas essa é uma definição meio falha, pois ela nunca pára. Então, para que a recursão seja perfeita, temos que dar uma condição de parada para a função. Assim, uma definição melhor é: Recursão -> se você não entendeu o que é vide Recursão Vamos ver um exemplo prático: como calcular n! Sabemos que n! = n × (n-1)! e que 0! = 1. Então: 1. FUNCTION fatorial(n : integer) : integer; BEGIN 2. IF n=0 THEN fatorial := 1 3. ELSE fatorial := n * fatorial(n-1) END; Estranho, não? O que acontece aqui? Cada vez que chamamos uma função ou procedimento recursivamente, essa chamada, juntamente com todos seus parâmetros e variáveis locais do procedimento ou função, é colocada em uma pilha. Assim, quando a última chamada é feita, ou seja, quando essa última chamada retorna, o computador vai desempilhando. Se quisermos acessar alguma variável da função ou procedimento, estaremos acessando a variável da última chamada feita, ou seja, da que está no topo da pilha. Vamos ver o funcionamento da pilha no caso da função acima, para 3! Na linha 1 chamei a função pela primeira vez: fatorial(3) Como 3 ����HQWão a linha 3 é executada, e chamo novamente a função fatorial(2) fatorial(3) Como 2 ����HQWão a linha 3 é executada, e chamo novamente a função 30 fatorial(1) fatorial(2) fatorial(3) Como 1 ����HQWão a linha 3 é executada, e chamo novamente a função fatorial(0) fatorial(1) fatorial(2) fatorial(3) agora 0 = 0, então a linha 2 é executada, e a função retorna pela primeira vez, retornando o valor 1. Essa chamada é desempilhada: fatorial(1) fatorial(2) fatorial(3) Como a chamada a fatorial(0) foi feita na linha 3 da chamada a fatorial(1), o programa continua a rodar de lá, fazendo 1 × 1. Ou seja, multiplicando o n pelo valor de retorno de fatorial(n-1). Esse valor é, então retornado, e a chamada é desemplidada: fatorial(2) fatorial(3) Como a chamada a fatorial(1) foi feita na linha 3 da chamada a fatorial(2), o programa continua a rodar de lá, fazendo 2 × 1. Esse valor é, então retornado, e a chamada é desemplidada: fatorial(3) Como a chamada a fatorial(2) foi feita na linha 3 da chamada a fatorial(3), o programa continua a rodar de lá, fazendo 3 × 2. Esse valor é, então retornado, e a chamada é desemplidada. No caso, esse é o valor de retorno da função, pois não há mais chamadas recursivas a serem desempilhadas. E eis que a resposta será 6, ou seja 3!. Mas como nem tudo é maravilha, apesar de facilitar nossa vida, recursão tem um preço: empilhar tudo isso gasta memória. Vejamos agora como fazer x^y (x elevado a y). Note que x^y = x × x^(y-1), e x^0 = 1. Então: 1. FUNCTION pot(x,y : integer) : integer; BEGIN 31 2. IF y=0 THEN pot := 1 3. ELSE pot := x * pot(x,y-1) END; Mas há outro modo de fazer isso, note que: x^1024 = (x^512)^2 x^512 = (x^256)^2 x^256 = (x^128)^2 ... x^4 = (x^2)^2 x^2 = x × x Então temos que x^y = 1 se y = 0 x × x^(y-1) se y for ímpar (x^(y/2))^2 se y for par Assim, se y for par, faremos menos chamadas recursivas. A função, então, é: 1. FUNCTION pot(x,y : integer) : integer; BEGIN 2. IF y=0 THEN pot := 1 ELSE 3. IF Odd(y) THEN pot := x * pot(x,y-1) 4. ELSE por := Sqr(pot(x, y DIV 2)) END; Opa! quem é esse Odd? Odd(x : Longint) : Boolean; retorna True se x for ímpar e False se não. Mas será que esse algoritmo é realmente melhor? Analizemos o primeiro para 3^4: Na primeira chamada temos: pot(3,4) como 4 ���H�p�SDU��D�OLQKD���p�H[HFXWDGD��FKDPDQGR�SRW�QRYDPHQWH�� pot(3,3) pot(3,4) 32 como 3 ���H�p�SDU��D�OLQKD���p�H[HFXWDGD��FKDPDQGR�SRW�QRYDPHQWH�� pot(3,2) pot(3,3) pot(3,4) assim vamos indo para 1: pot(3,1) pot(3,2) pot(3,3) pot(3,4) e, por fim pot(3,0) pot(3,1) pot(3,2) pot(3,3) pot(3,4) Agora, 0 = 0, então, pela linha 2,pot retorna 1 pot(3,1) pot(3,2) pot(3,3) pot(3,4) na linha 3, de onde pot(3,0) foi chamada, pot retorna x × 1 = 3 pot(3,2) pot(3,3) pot(3,4) na linha 3, de onde pot(3,1) foi chamada, pot retorna x × 3 = 9 pot(3,3) pot(3,4) na linha 3, de onde pot(3,2) foi chamada, pot retorna x × 9 = 27 pot(3,4) 33 finalmente, na linha 3, de onde pot(3,3) foi chamada, pot retorna x × 27 = 81. O resultado final. Note que chegamos a ter 5 elementos na pilha. Agora vamos analizar o segundo algoritmo: chamamos a função pot(3,4) como 4 é ���H�SDU��HQWão a linha 4 é executada: pot(3,2) pot(3,4) como 2 é ���H�SDU��HQWão a linha 4 é executada: pot(3,1) pot(3,2) pot(3,4) como 1 é ���H�tPSDU��HQWão a linha 3 é executada: pot(3,0) pot(3,1) pot(3,2) pot(3,4) agora, a linha 2 é executada, retornando 1 e desempilhando essa chamada: pot(3,1) pot(3,2) pot(3,4) como a chamada no topo parou na linha 3, e pot(3,0) retornou 1, termino a linha 3, retornando 3 × 1 = 3. pot(3,2) pot(3,4) como a chamada no topo parou na linha 4, e pot(3,1) retornou 3, termino a linha 4, retornando 3^2 = 9. pot(3,4) por fim, como a chamada no topo parou na linha 4, e pot(3,2) retornou 9, termino a linha 4, retornando 9^2 = 81. Eis nossa resposta. Qual o número máximo de elementos 34 na pilha? 4, 1 a menos que no algoritmo 1. E isso com y=4 somente. Se fizermos um y grande essa diferença sobe. Ou seja, esse algoritmo é realmente melhor. Agora vejamos um último exemplo. Vamos rever um algoritmo de ordenação, só que dessa vez, usando recursão. Lembra da ordenação por seleção? Seu algoritmo era: procuro o menor elemento do vetor coloco esse elemento na primeira posição ordeno o resto do vetor Esse "ordeno o resto do vetor" é recursivo. Então vamos lá, o procedimento de ordenação fica: {ordena o vetor v, com índices começando em início e terminando em fim} PROCEDURE ordena(VAR v : vetor; inicio, fim : integer); VAR menor : integer; {posição do menor elemento} BEGIN IF inicio < fim THEN BEGIN {senão já está ordenado, tem um só elemento} menor := PosMenor(v,inicio,fim); Troca(v[menor],v[inicio]); {ordeno o resto} Ordena(v,inicio + 1,fim) END END; Bem mais simples que a não recursiva, não é? Agora precisamos de uma função chamada PosMenor que me retorne o índice do menor elemento de v começando a busca no índice "inicio" e terminando em "fim", e um procedimento que troque dois valores no vetor: FUNCTION PosMenor(v : vetor; i,f:integer) : integer; VAR men : tipo; cont : integer; BEGIN men := v[i]; PosMenor := i; FOR cont:=i TO f DO IF men > v[cont] THEN BEGIN men := v[cont]; PosMenor := cont END END; PROCEDURE Troca(var a,b : tipo); 35 VAR c : tipo; BEGIN c := a; a := b; b := c END; Então o programa fica: PROGRAM Selecao; CONST MAX = 10; TYPE tipo = real; vetor = ARRAY[1..MAX] OF tipo; VAR v : vetor; FUNCTION PosMenor(v : vetor; i,f:integer) : integer; VAR men : tipo; cont : integer; BEGIN men := v[i]; PosMenor := i; FOR cont:=i TO f DO IF men > v[cont] THEN BEGIN men := v[cont]; PosMenor := cont END END; PROCEDURE Troca(var a,b : tipo); VAR c : tipo; BEGIN c := a; a := b; b := c END; PROCEDURE ordena(VAR v : vetor; inicio, fim : integer); VAR menor : integer; {posição do menor elemento} BEGIN IF inicio < fim THEN BEGIN {senão já está ordenado, tem um só elemento} menor := PosMenor(v,inicio,fim); Troca(v[menor],v[inicio]); {ordeno o resto} Ordena(v,inicio + 1,fim) END END; BEGIN {carrego o vetor vet} Ordena(vet); {imprimo vet} END. 36 o código para carregar e imprimir vet fica como exercício. 37 MEMÓRIA DINÂMICA Até agora, quando queríamos guardar muitos dados na memória, usávamos vetores. Só que vetores têm limite, ou seja, temos que definir de antemão o número máximo de elementos que um vetor pode ter. O que acontece se não tivermos esse limite? Ou seja, o que fazemos se esse limite não for definido? Uma alternativa seria criar um vetor imenso, gastando um mundo de memória. Mas ainda assim correríamos o risco de estourar o limite do vetor. Então, o que fazer? A solução ideal seria se pudéssemos, à medida que recebemos algum dado, separar um espaço na memória para ele e armazená-lo lá. Ou seja, nosso limite seria dado pela quantidade de memória disponível no computador. Naturalmente, como separamos um espaço na memória e guardamos um valor lá, temos que, de algum modo, saber qual é esse pedaço de memória, ou seja, precisamos de uma espécie de endereço que nos leve a esse pedaço para que, futuramente, possamos acessar o que guardamos lá ou mesmo guardar algo lá. Então como fazemos isso em Pascal? Com ponteiros. Ponteiros nada mais são do que variáveis que guardarm o endereço de uma região de memória, podendo essa região conter a informação que quisermos. Vejamos como declaramos ponteiros em Pascal: VAR nome_do_ponteiro : ^tipo_apontado; Nesse caso, tipo_apontado é qualquer tipo, simples ou definido pelo usuário, do Pascal. Ou seja, não posso fazer: VAR ptr : ^ARRAY[1..10] OF integer; mas posso fazer: TYPE vetor = ARRAY[1..10] OF integer; VAR ptr : ^vetor; Agora vamos ver como usar um ponteiro. Suponha a declaração: VAR ptr : ^integer; O que temos aqui? Apenas uma variável, chamada ptr, que é um ponteiro para um inteiro, ou seja, ela guarda um endereço de memória onde pode ser armazenado um inteiro. Mas de qual pedaço ela tem o endereço? Nenhum. Para efetivamente dar um valor a ptr temos que fazer: 38 new(ptr); A forma geral do new é New(variável_de_ponteiro); O que, então, isso faz? Esse comando simplesmente diz ao sistema operacional para que este separe um pedaço de memória em que caiba um inteiro (o tipo apontado por ptr), guardando o endereço deste pedaço em ptr. E qual é esse endereço? Não sabemos, e nem precisamos saber, pois o Pascal abstrai tudo isso. Ou seja, podemos agora usar a variável ptr quase como se fosse um integer comum. Por que quase? Veja abaixo: VAR ptr : ^integer; BEGIN new(ptr); {separei um pedaço de memória para um inteiro} ptr^ := 2; {coloquei o valor 2 nesse pedaço de memória} ptr^ := ptr^ * 3; {faço esse pedaço de memoria receber o triplo do que lá havia} END. Viu como fizemos? "ptr^" pode ser usada como qualquer variável integer. Veja agora o programa abaixo: VAR p1 : ^integer; p2 : ^integer; BEGIN new(p1); {reservei um pedaço de memória para um inteiro, fazendo p1 apontar para ele} p2 := p1; {fiz p2 apontar para o mesmo pedaço de memória que p1 aponta} p1^ := 4; {coloquei o valor 4 nesse pedaço de memória} writeln(p2^); {escrevi o valor que está no pedaço de memória apontado por p2, ou seja, 4, pois p2 aponta para o mesmo pedaço de memória que p1} p2^ := p2^ + 3; {adicionei 3 ao valor que havia no pedaço de memória apontado por p2 e armazenei novamente nesse pedaço o resultado} writeln(p1^); {escrevo o conteúdo da memória apontada por p1, ou seja, 7, pois p1 aponta para o mesmo pedaço de memória que p2 aponta} END. 39 Como isso funciona? Vejamos por partes: ? Quando fizemos new(p1), separamos (ou, como dizemos, alocamos) espaço na memória suficiente para um inteiro, guardando o endereço desse espaço em p1, ou seja, fizemos p1 apontar para lá: ? Ao fazer p2 := p1 (note que nãoé p2^ := p1^), guardamos uma cópia do endereço que estava em p1 em p2. Ou seja, fazemos p2 apontar para a mesma região de memória que p1 apontava: ? Ao fazermos p1^ := 4 guardamos 4 nessa região: ? Ao lermos p2^ no write, simplesmente lemos o valor que está na região da memória apontada por p2, ou seja, 4. ? Ao fazermos p2^ := p2^ + 3, pegamos o conteúdo da região de memória apontada por p2, somamos 3 a este e armazenamos o resultado de volta nessa mesma região: ? Ao lermos p1^ no write, novamente lemos o valor que está na região de memória apontada por p1, ou seja, 7, já que esta é a mesma região que é apontada por p2 e que foi modificada no passo anterior. Então não confunda! Se fazemos p2^ := p1^; estamos copiando o conteúdo da região de memória apontada por p1 para a região de memória apontada por p2. Já se fizermos p2 := p1; estaremos fazendo p2 apontar para a mesma região de memória apontada por p1. 40 Agora que sabemos como carregar valores com ponteiros, como fazemos para "zerar" um ponteiro? Ou seja, para dizer que ele não aponta para lugar nenhum? p1 := NIL; Se não fizermos isso, ele conterá um endereço de memória que pode já ter sido liberada pelo programa, ou que pode não pertencer ao programa, o que geraria um erro quando da execução. E, por falar em liberar memória, como fazemos se quisermos liberar a memória que reservamos com new? Dispose(variável_de_ponteiro); ou seja Dispose(p1); Dispose avisa ao sistema operacional que não vamos mais precisar desse pedaço de memória, liberando esta. SEMPRE libere a memória que não vai mais usar para não esgotar os recursos da máquina. Cuidado com o seguinte erro: VAR p1 : ^integer; p2 : ^integer; BEGIN new(p1); p2 := p1; p1^ := 4; Dispose(p2); writeln(p1^) END. isso pode gerar um erro, pois já liberei a memória apontada por p2, que é a mesma apontada por p1 Vale também lembrar que, ao final do programa, se você não liberou a memória usada, o computador a libera para você. Ainda assim é uma boa prática limpar seu lixo. E se agora quisermos um ponteiro para um tipo mais complexo, como registro? TYPE reg = RECORD 41 cp1 : real; cp2 : integer; cp3 : ARRAY[1..10] of char END; VAR p : ^reg; BEGIN New(p); END. Pronto! Aloquei espaço para o meu registro. Ou seja, na memória foi reservado espaço suficiente para guardar os 3 campos do nosso registro. E como acessamos ou mudamos o valor desses campos? TYPE reg = RECORD cp1 : real; cp2 : integer; cp3 : ARRAY[1..10] of char END; VAR p : ^reg; i : integer; BEGIN new(p); p^.cp1 := 3.12; p^.cp2 := Round(p^.cp1); {guardei 3} FOR i:=1 TO 10 DO p^.cp3[i] := Chr(Ord('a') + i); writeln(p^.cpq,' - ', p^.cp2,' - ',p^.cp3[5]); Dispose(p) END. Ou seja, agimos como se "p^" fosse o nome do registro. E se em vez de registro quisermos lidar com um vetor? TYPE vet = ARRAY[1..10] of integer; VAR p : ^vet; BEGIN new(p); p^[1] := 4; p^[2] := 5; p^[3] := p^[1] + p^[2]; writeln(p^[1],' + ',p^[2],' = ',p^[3]); Dispose(p) END. 42 Novamente, agimos como se "p^" fosse o nome do vetor. Com ponteiros, as operações permitidas são = e <>. Assim ,não posso fazer +, - etc. Ou seja, as únicas operações que posso usar com ponteiros são: p1 = p2 -> True se ambos apontam para a mesma região de memória p1 <> p2 -> True se ambos apontam para regiões diferentes Agora que já vimos o que são ponteiros, vamos aprender, na próxima seção, a usá-los. LISTAS LIGADAS Suponha que queremos ler uma lista de dados. Até aí tudo bem. Agora suponha que não conhecemos o número total de dados. Como fazemos então? Seria desejável seguir o seguinte algoritmo: inicialmente a lista está vazia enquanto o usuário não manda parar de ler leio um dado coloco este dado na lista assim fica fácil não? Mas como fazer isso em Pascal? Usando ponteiros. Como? Com uma lista ligada. O que, então, é uma lsita ligada? Uma lista ligada é uma lista onde cada elemento - chamado de nó - contém um valor e um ponteiro para o elemento seguinte. Assim, sabendo onde está o primeiro elemento da lista, podemos chegar a qualquer outro elemento. Há vários tipos de listas ligadas: ? Simples: O sinal de aterramento significa que este ponteiro é NIL, ou seja, não aponta para lugar nenhum. ? Circular: 43 ? Duplamente ligada: e muitas outras mais. O limitante é apenas sua imaginação. Aqui iremos usar somente listas simples. Mas e qual as vantagens e desvantagens de cada uma? Listas duplamente ligadas são fáceis de se percorrer, mas ocupam mais espaço na memória. Note que nessas eu posso, a partir de um elemento, voltar na lista, percorrê-la de trás para frente, pois tenho ponteiros que medizem onde estão o próximo elemento e o anterior. As circulares simples são fáceis de percorrer e não ocupam muita memória. Mas se o número de elementos for grande, vai se comportar quase como uma lista simples. Qual lista é a melhor? Depende do problema a ser resolvido.,p> Agora vamos definir operações básicas em listas: ? Criação de uma lista ? Inclusão de um elemento na lista ? Exclusão de um elemento da lista ? Esvaziamento da lista ? Contagem do número de elementos da lista ? Inclusão de um elemento na posição i ? Exclusão do iº elemento na lista ? Busca da posição do primeiro elemento de valor x ? Exclusão do primeiro elemento de valor x ? etc Vamos ver então como criar uma lista ligada. Nos exemplos que seguem estaremos usando a lista ligada simples, conforme foi apresentada acima. Antes de mais nada, vamos definir nossa lista: TYPE tipo = real; p_no = ^no; no = RECORD 44 valor : tipo; prox : p_no END; Agora vamos definir o procedimento de criação da lista. PROCEDURE Cria(VAR cabeca : p_no); BEGIN cabeca := NIL END; Pronto! O que fizemos com isso? Dissemos que a lista está vazia ao darmos um valor "nada" à cabeça desta. Se não fizermos isso, a cabeça pode conter lixo indesejável. Agora vamos incluir um elemento na lista. A questão é: onde? Você decide. Pode ser no início, pode ser no meio (caso você queira ordenar a lista enquanto a constrói) e pode ser no fim. Nesse caso, vamos por no fim. Então, como faremos? Suponha que temos a seguinte lista: Vamos, então, colocar um elemento em seu final ? Alocamos o espaço para o novo elemento, fazendo q apontar para ele, e o abastecemos com valor ? Usamos um ponteiro auxiliar, p, para apontar para a cabeça da lista ? Precorremos a lista com p até que este aponte para o último elemento 45 ? Fazemos o próximo elemento de p (que agora aponta para o fim da lista) ser q, ou seja, fazemos q ser o novo fim da lista E se a lista estiver inicialmente vazia? Então ? Alocamos o espaço para o novo elemento e o abastecemos com valor ? Fazemos a cabeça da lista ser esse novo elemento Assim, o procedimento fica: PROCEDURE Inclui(VAR cabeca : p_no; elemento : tipo); VAR q : p_no; {o novo elemento} p : p_no; {auxiliar} BEGIN {aloco espaço para o novo elemento} New(q); {abasteço os valores nesse} q^.valor := elemento; q^.prox := NIL; {vejo onde coloco q na lista} IF cabeca <> NIL THEN BEGIN {a lista não estava vazia} p := cabeca; {vou até o fim da lista} WHILE p^.prox <> NIL DO p := p^.prox; 46 {agora p é o último elemento, pois p^.prox = NIL} {faço o próximo elemento de p ser q, ou seja, q é o novo fim} p^.prox := q END ELSE {a lista está vazia. Façoq ser a cabeça} cabeca := q END; Vamos agora excluir um elemento da lista. Novamente, qual? Você escolhe! Vamos tomar como política a exclusão do primeiro elemento da lista. Como faremos isso? Suponha a lista: Vamos, então, retirar um elemento de seu início: ? Primeiro fazemos p apontar para o início da lista: ? Depois fazemos a cabeça apontar para o segundo elemento: ? Eliminamos agora o elemento apontado por p E o procedimento fica: PROCEDURE Exclui(VAR cabeca : p_no; VAR valor:tipo); 47 VAR p : p_no; {auxiliar} BEGIN IF cabeca <> NIL THEN BEGIN {a lista não está vazia} p := cabeca; {faço a cabeça apontar para o próximo} cabeca := cabeca^.prox; {retorno o valor que está em p} valor := p^.valor; {mato o elemento apontado por p} Dispose(p); END {else a lista está vazia, não há o que tirar} END; Suponha que queremos simplesmente matar a lista inteira, como fazemos isso? Da mesma forma que excluímos um elemento, só que não devolvemos valor algum: ? Coloco um ponteiro p na cabeça da lista ? Enquanto p não for nil o Faço a cabeça apontar para o próximo de p o Mato p o Faço p apontar para a cabeça O procedimento fica: PROCEDURE Limpa(VAR cabeca : p_no); VAR p : p_no; {auxiliar} BEGIN p := cabeca; WHILE p <> NIL DO BEGIN cab := p^.prox; Dispose(p); p := cab END END; E como fazemos para contar os elementos de uma lista? Basta criar um contador e percorrer a lista incrementando o contador: ? Coloco um ponteiro p na cabeça da lista e zero o contador cont ? Enquanto p não for nil o Incremento cont o Faço p apontar para o próximo elemento Ou seja: FUNCTION NumEl(cabeca : p_no) : integer; VAR p : p_no; {auxiliar} 48 BEGIN p := cabeca; NumEl := 0; WHILE p <> NIL DO BEGIN p := p^.prox; NumEl := NumEl + 1 END END; Agora, usando algumas das funções acima, vamos incluir um novo nó na posição i da nossa lista. Como faremos isso? Suponha a seguinte lista ? Primeiro fazemos p apontar para o início da lista: ? Em seguida movemos p até o elemento anterior à posição em que queremos incluir (suponha que queremos incluir na 3ª posição): ? Criamos, então, espaço para o novo elemento, fazendo q apontar para ele: ? Fazemos, então, o próximo elemento de q ser o próximo de p: 49 ? E o próximo elemento de p ser q: pronto! Incluímos na posição i = 3. E se quisermos incluir na última posição? Funciona? Claro! Pois pararei com p no último elemento. E se a lista estiver vazia? Nesse caso, basta criar o elemento e fazer a cabeça apontar para ele (veja abaixo como incluir na primeira posição). Note que nesse caso, o único i aceitável é 1. E se a posição pedida for 1? Esse é um caso um pouco mais complicado, pois não há como parar um elemento antes, o que indica que nosso algoritmo falha. Como fazer, então? ? Primeiro criamos espaço para o novo elemento, fazendo q apontar para ele: ? Fazemos, então, o próximo elemento de q ser a cabeça: 50 ? E a cabeça apontar para q: Pronto! Incluímos na primeira posição. Vamos agora ao procedimento: PROCEDURE IncluiEmPos(VAR cabeca:p_no;valor:tipo;posicao:integer); VAR p : p_no; {auxiliar} q : p_no; {o novo elemento} i : integer; {contador do FOR} BEGIN {testo se a posição é válida} IF (posicao <= (NumEl(cabeca) + 1)) AND (posicao > 0) THEN BEGIN {é uma posição válida. O +1 é porque se tem 5 elementos posso por na posição 6, por exemplo (embora não na 7)} {crio o novo elemento} new(q); q^.valor := valor; q^.prox := NIL; IF posicao=1 THEN {quero na primeira posição} BEGIN {se a lista estiver vazia, não há problema aqui} {faço o próximo de q ser a cabeça} q^.prox := cabeca; {faço a cabeça ser q} cabeca := q END ELSE BEGIN {é outra posição} {note que aqui a lista jamais estará vazia, senão NumEl retornaria 0 e posição > 1} {faço p apontar para a cabeça} p := cabeca; {movo p até o elemento anterior à posição. Como p já está em 1, movo posicao-2 posições} 51 FOR i:=1 TO posicao-2 DO p := p^.prox; {agora p aponta para o elemento anterior} {faço o próximo de q ser o próximo de p} q^.prox := p^.prox; {faço o próximo de p ser q} p^.prox := q END END {ELSE não faz nada!} END; Ótimo! Vamos agora excluir um elemento da posição i. Suponha a seguinte lista: ? Primeiro fazemos p apontar para o início da lista: ? Em seguida movemos p até o elemento anterior à posição do elemento que queremos excluir (suponha que queremos excluir da 3ª posição): ? Marcamos a posição a ser excluída com um ponteiro q: ? Fazemos, então, o próximo elemento de p ser o próximo de q: 52 ? Eliminamos q: pronto! Excluímos o nó da posição i = 3. E se a lista estiver vazia? Nesse caso, Nada poderá ser excluído. E se a posição pedida for 1? Esse, novamente, é um caso um pouco mais complicado, pois não há como parar um elemento antes, o que indica que nosso algoritmo falha. Como fazer, então? Basta seguir o algoritmo dado acima para retirar um elemento do início da lista. Vamos, então, à função que retira o iº elemento da lista. Essa função retorna o valor que estava nesse elemento: FUNCTION TiraDaPos(VAR cabeca:p_no;posicao:integer):tipo; VAR p : p_no; {auxiliar} q : p_no; {auxiliar} i : integer; {contador do FOR} BEGIN {testo se a posição é válida} IF (posicao <= NumEl(cabeca)) AND (posicao > 0) THEN BEGIN {é uma posição válida. Note que se a lista estiver vazia nada é feito, pois posicao >=1, o que é > 0} {faço p apontar para a cabeça} p := cabeca; IF posicao=1 THEN {quero na primeira posição} BEGIN {faço a cabeça ser o segundo elemento} cabeca := cabeca^.prox; {dou o valor de retorno} TiraDaPos := p^.valor; {mato o nó} Dispose(p) END ELSE BEGIN {é outra posição} {movo p até o elemento anterior à posição. Como p já 53 está em 1, movo posicao-2 posições} FOR i:=1 TO posicao-2 DO p := p^.prox; {agora p aponta para o elemento anterior} {faço o próximo de p ser q} q := p^.prox; {faço o próximo de p ser o próximo de q} p^.prox := q^.prox; {retorno o valor de q} TiraDaPos := q^.valor; {mato o nó apontado por q} Dispose(q) END END END; Agora vamos ver como buscar a posição do primeiro elemento que contenha o valor x na nossa lista. Para tal, o procedimento é simples: ? Coloco um ponteiro p na cabeça da lista e crio um contador, inicializando-o com 1. ? Enquanto p não for NIL e não encontrei o valor vou incrementando p e o contador. ? Se encontrei o valor, retorno o valor do contador, senão, retorno 0. Vamos, então, à função: FUNCTION Pos(cabeca:p_no; elemento:tipo):integer; VAR p : p_no; {auxiliar} BEGIN {inicializo o contador} Pos := 1; {aponto p para a cabeça da lista} p := cabeça; WHILE (p <> NIL) AND (p^.valor <> elemento) DO BEGIN p := p^.prox; Pos := Pos+1 END; IF p = NIL THEN Pos := 0 {o else é automático, pos terá o valor certo} END; E para excluir o primeiro elemento de valor x? Basta ? Buscar a posição do primeiro elemento de valor x ? Excluir o elemento nessa posição 54 Ou seja: PROCEDURE ExcluiX(VAR cabeca:p_no; elemento:tipo); VAR el : tipo; {o elemento a ser excluído, é auxiliar} BEGIN el := TiraDaPos(cabeca,Pos(cabeca,elemento)) {excluí, el jogo fora} END; FILAS Uma fila é uma lista que tem como característica o fato de que o primeiro elemento a entrar nela é o primeiro a sair. Assim, ela funciona como uma filacomum. Pense numa fila de banco. Quem será o primeiro a ser atendido? O primeiro que chegou. E onde os que chegam depois devem entrar na fila? No final desta. Assim é uma fila: se quero retirar um elemento, retiro da frente; se quero incluir, incluo no final da fila. Note que este foi o modo como implementamos as listas acima, ou seja, quando fazíamos nossa lista, estávamos na verdade construindo uma fila. A única diferença é que em uma fila não posso retirar um elemento do meio da fila, ou lá colocar um, coisa que implementamos acima. Mas, de resto, as funções são as mesmas. PILHAS Uma pilha é uma estrutura de dados que, pasmem, funciona como uma pilha! Pense numa pilha de livros. É assim que ela funciona. Se queremos por mais um livro na pilha, onde colocamos? No topo! E se queremos tirar um livro, de onde tiramos? Do topo. Então, uma pilha nada mais é do que uma lista ligada que tem como característica o fato de que, se queremos incluir um elemento na lista, temos de incluí-lo no início desta, e se queremos tirar um elemento da lista, tiramos do início desta. Dessa forma, uma pilha tem as seguintes funções: ? Criação da pilha (como nas listas acima) ? Empilhamento (quando coloco um elemento no topo da pilha, ou seja, no início da lista) ? Desempilhamento (quando tiro um elemento do topo da pilha, ou seja, do início da lista) Note que não há como eu tirar um elemento do meio da pilha. 55 É como nossa pilha de livros, para retirar um livro do meio, tenho que desempilhar todos os que estão acima. Da mesma forma, para tirar um dado do meio da pilha, tenho que desempilhar os que estão antes dele. Vejamos, agora, funções para criação, empilhamento e desempilhamento: TYPE tipo = real; p_pilha = ^pilha; pilha = RECORD dado : tipo; prox : p_pilha END; VAR topo : p_pilha; PROCEDURE IniciaPilha(VAR topo : p_pilha); BEGIN topo := NIL END; PROCEDURE Empilha(VAR topo : p_pilha; dado : tipo); VAR p : p_pilha; {auxiliar} BEGIN {crio o novo elemento e dou valor a ele} new(p); p^.valor := dado; {faço ele apontar para o topo da pilha} p^.prox := topo; {faço o topo apontar para ele} topo := p END; FUNCTION Desempilha(VAR topo : p_pilha) : tipo; VAR p : p_pilha; {auxiliar} BEGIN {faço p apontar para o topo} p := topo; {retorno o valor de p} Desempilha := p^.valor; {baixo o topo, para o segundo elemento} topo := topo^.prox; {elimino p} Dispose(p) END; Acima você encontra ilustrações de como retirar um elemento do início da lista, ou seja, do topo da pilha, e de como incluir um elemento na posição 1 da lista, ou seja, no topo da pilha. 56 ARQUIVOS DE ACESSO SEQÜENCIAL Até agora, todos os dados lidos por nossos programas vinham do teclado, e todas as saídas íam para a tela. Mas, e se quiséssemos guardar alguns resultados para reaproveitar mais tarde? Como faríamos? Simples, basta guardarmso esses dados em arquivos no computador. Quando digitamos um texto, para guardá-lo, não salvamos em um arquivo? E quando queremos usar esse texto novamente não lemos o que salvamos anteriormente no arquivo? O princípio é o mesmo. Então, como usamos arquivos em Pascal? Antes de mais nada, devemos declarar uma variável de arquivo. Como fazemos isso? Atenção!!! Os programas escritos até a metade desta lição não devem ser executados. A razão é que eles estão incompletos. Mais adiante veremos o que falta neles. VAR nome_da_variável : FILE OF tipo_do_dado; Assim, para definirmos uma variável de arquivo, temos que saber o tipo de dado que colocaremos lá. Então, suponha que nosso arquivo é texte, então teremos: VAR arq : FILE OF char; Mas, afinal, para que precisamos de uma variável de arquivo? Mais adiante veremos que é através dela que teremos acesso a nosso arquivo. Esse tipo de arquivo texto já é pré-definido no pascal, ou seja, o Pascal tem um tipo pré- definido para arquivos texto: o text. Então, as duas declarações abaixo são equivalentes VAR arq : FILE OF char; VAR arq : text; pois o pascal já fez essa declaração para você: TYPE text = FILE OF char; Naturalmente, se você quiser arquivos de algum outro tipo que não char, você deve fazer, por exemplo: VAR arq1 : FILE OF integer; 57 arq2 : FILE OF real; etc só que, nesse caso, os arquivos são tratados de modo um pouco diferente pelo Pascal. Confira a lição de arquivos de acesso aleatório para ver como o Pascal trata esses arquivos. Nesse momento, trataremos somente de arquivos texto. Bom, agora já sabemos como declarar uma variável de arquivo. Agora, como a usamos? Antes de querer lidar com o arquivo, precisaremos associar essa variável a um arquivo real no disco. Ou seja, precisamos associá-la ao nome de um arquivo que queiramos criar ou ao nome de um já existente que queiramos abrir. Como fazemos isso? Assign(variável_de_arquivo, nome_do_arquivo); Por exemplo, o programa: VAR arq : text; BEGIN Assign(arq,'oba.txt'); END. associa à variável "arq" o arquivo de nome "oba.txt". Lembre-se que tal arquivo não necessariamente precisa existir no disco. Após associar o nome ao arquivo, qual o próximo passo? Aí depende: queremos criar um novo arquivo ou abrir um existente? Suponha que queremos criar um arquivo novo: VAR arq : text; BEGIN Assign(arq,'oba.txt'); Rewrite(arq); END. Pronto! O comando Rewrite cria um novo arquivo e o abre para gravação (escrita). E se o arquivo "oba.txt" já existir no disco? Nesse caso o Rewrite o apaga, escrevendo o novo conteúdo por cima dele. Ou seja, cuidado! Pois se o arquivo a ser criado já existir no disco, tudo que havia nele antes do Rewrite será perdido. Assim, a forma geral do Rewrite é: 58 Rewrite(variável_de_arquivo); Agora que criamos o arquivo, como fazemos para guardar algo nele? Como fazemos para escrever nele? De um modo bem semelhante a como escrevíamos na tela: VAR arq : text; BEGIN Assign(arq,'oba.txt'); Rewrite(arq); write(arq, 'mensagem'); writeln(arq, 'para'); write(arq,'o arquivo') END. Isso irá gravar mensagempara o arquivo no arquivo. Ou seja, do mesmo modo que o write e o writeln agem na tela, agem no arquivo. O write e o writeln também podem ser usados para escrever outros tipos de dados. Eles convertem automanticamente os valores de dados numéricos para strings de dígitos antes de armazenar no arquivo texto. Assim, posso escrever: VAR arq : text; idade : integer; str : string; BEGIN Assign(arq,'oba.txt'); Rewrite(arq); idade := 25; writeln(arq,'João',idade); str := 'Pedro Souza'; idade := 12; writeln(arq,str,idade) END. E se, em vez de cirarmos um arquivo, quisermos escrever em um já existente, sem apagar o conteúdo prévio deste? Neste caso, em vez de Rewrite usamos Append: VAR arq : text; 59 idade : integer; str : string; BEGIN Assign(arq,'oba.txt'); Append(arq); idade := 25; writeln(arq,'Jnova linha') END. Nesse exemplo, se o arquivo continha: linha 1 escrevo linha 2 agora terá: linha 1 escrevo linha 2 nova linha se "escrevo linha 2" foi gravada com writeln, ou: linha 1 escrevo linha 2nova linha se "escrevo linha 2" foi gravada com write. Assim, Append abre um arquivo já existente, de modo que possamos adicionar texto ao FINAL deste. Note que só adiciono ao fim do arquivo. A forma geral do comando é: Append(variável_de_arquivo); Como o Append assume que o arquivo existe, se este não existir, um erro em tempo de execução será gerado. Agora, digamos que queremos apenas ler o conteúdo de um arquivoque está no disco, como fazemos? Com Reset: Reset(variável_de_arquivo); Por exemplo, o seguinte programa 60 VAR arq : text; s : string[5]; s1 : string; BEGIN Assign(arq,'oba.txt'); Reset(arq); read(arq,s); readln(arq,s1); END. lê 2 strings de "oba.txt". O read lê até o tamanho máximo de s (5). Já o readln lê uma linha inteira do arquivo, armazenando-a em s1 (lê a linha ou até 255 caracteres, que é o máximo de string). Assim, se o arquivo contiver este texto é grande eu o escrevi s terá "este " (repare que o espaço foi junto) e s1 terá o resto da linha, ou seja, "texto é grande". Então, Reset abre um arquivo existente somente para leitura. da mesma forma que o write, o read pode transformar o texto lido em valores numéricos. Por exemplo, lembra nosso arquivo anterior onde gravamos "João 25"? Como lemos isso? VAR arq : text; s : string; id : integer; BEGIN Assign(arq,'oba.txt'); Reset(arq); readln(arq,s,id); END. Aqui, s terá "João" e id terá 25. assim, tanto real quanto integer podem ser lidos, lembrando que, em um arquivo texto, quando lemos várias variáveis, como no exemplo acima, os caracteres delimitadores são os espaços, tabulação e CR (enter). Se tentarmos ler uma seqüência não numérica com read e guardarmos em uma variável numérica, um erro ocorre (da mesma forma como quando recebemos do teclado). 61 Assim, é melhor escrever informações diferentes em linhas diferentes, ou seja, se estou guardando nomes e idades, gravo numa linha o nome e na outra a idade. É bem verdade que precisarei de dois writeln para gravar e dois readln para ler, mas fica mais organizado. Suponha, então, que temos um arquivo chamado "idades.txt" com nomes e diades: Rolando Caio da Rocha 23 Jacinto Dores 12 Armando Machado 34 Como fazemos para imprimir na tela o conteúdo desse arquivo? VAR arq : text; s : string; id : integer; BEGIN Assign(arq,'oba.txt'); Reset(arq); WHILE NOT eof(arq) DO BEGIN readln(arq,s); readln(arq,id); writeln(s,' - ',id) END END. O que eof faz? Sua sintaxe é: Eof(variável_de_arquivo) : boolean; e ele retorna True se o arquivo chegou ao fim (EOF = End Of File). É útil quando não sabemos quantas linhas tem o arquivo. Por fim, já que com Reset, Rewrite e Append abrimos um arquivo, sempre devemos fechá-lo. Como? Close(variável_de_arquivo); Então, nosso programa acima fica: VAR arq : text; 62 s : string; id : integer; BEGIN Assign(arq,'oba.txt'); Reset(arq); WHILE NOT eof(arq) DO BEGIN readln(arq,s); readln(arq,id); writeln(s,' - ',id) END; Close(arq) END. Sempre feche seus arquivos assim que terminar de usá-los. Vale notar que, apesar de fechar o arquivo, close não termina com a associação entre a variável de arquivo e o nome deste. Ou seja, podemos jsar Reset, Rewrite e Append novamente para tratar com o mesmo arquivo sem precisarmos usar Assign. Assim, valem as seguintes observações: ? Um arquivo texto só pode ser aberto para uma operação por vez: leitura, escrita ou junção (append). ? O nome do arquivo, passado com assign, deve conter o caminho até este no diretório. Se não contiver, o compilador assumirá que está no mesmo diretório do programa. Então, em suma, para ler ou escrever em um arquivo texto temos que: ? Definir uma variável de arquivo texto e associar a ela o nome que o arquivo tem ou terá no disco. ? Abrir o arquivo para ler ou escrever. ? Ler os dados do arquivo, ou escrever os dados nele. ? Fechar o arquivo. Como um arquivo texto só pode ser aberto para inclusão em seu final, como podemos fazer para modificar um dado no meio deste? E apagar algum dado do meio? Considere um sistema onde guardamos o nome da pessoa e sua data de nascimento. Então, vamos criar as estruturas para tal: TYPE data = RECORD dia : word; mes : word; ano : word END; pessoa = RECORD nome : string[25]; 63 nasc : data END; Agora, vamos criar o arquivo e abaster os dados: PROGRAM PROG; CONST NOMEARQ = 'niver.txt'; TYPE data = RECORD dia : word; mes : word; ano : word END; pessoa = RECORD nome : string[25]; nasc : data END; VAR sai : boolean; arq : text; p : pessoa; BEGIN sai := false; Assign(arq,NOMEARQ); Rewrite(arq); WHILE NOT sai DO BEGIN write('nome: '); readln(p.nome); IF p.nome='sai' THEN sai:=true ELSE BEGIN write('nascimento(dd mm aa):'); readln(p.nasc.dia,p.nasc.mes,p.nasc.ano); writeln(arq,p.nome); writeln(arq,p.nasc.dia:2,' ',p.nasc.mes:2,' ',p.nasc.ano:2) {o :2 é para escrever com 2 dígitos} END END; Close(arq) END. Esse programa fica executando até que o usuário digite 'sai'. Note que, se o arquivo existir, o que havia nele será apagado. Se você não quiser que isso ocorra use Append em ves do Rewrite. Então, nosso arquivo será: Rolando Caio da Rocha 64 12 05 98 Jacinto Dores 01 01 87 Armando Machado 15 06 76 Paula Aguiar Cavalo 23 02 88 Agora suponha que queremos apagar do arquivo o Armando Machado, como fazemos? Parece estranho, mas como esse tipo de arquivo é seqüencial, ou seja, não consigo modificar o meio dele, devemos usar o seguinte algoritmo: Abro "niver.txt" para leitura Crio "temp.txt" Enquanto "niver.txt" não chegar ao fim Leio nome e data do "niver.txt" Se nome Senão ignoro e não copio nada Após rodarmos o algoritmo no arquivo da esquerda, teremos o da direita: NIVER.TXT Rolando Caio da Rocha 12 05 98 Jacinto Dores 01 01 87 Armando Machado 15 06 76 Paula Aguiar Cavalo 23 02 88 TEMP.TXT Rolando Caio da Rocha 12 05 98 Jacinto Dores 01 01 87 Paula Aguiar Cavalo 23 02 88 Note que "temp.txt" é o "niver.txt" que queríamos. Então completo meu algoritmo: Abro "niver.txt" para leitura Crio "temp.txt" Enquanto "niver.txt" não chegar ao fim Leio nome e data do "niver.txt" Se nome Senão ignoro e não copio nada Apago "niver.txt" Troco o nome de "temp.txt" para "niver.txt" Agora podemos passar ao programa que faz isso: PROGRAM PROG; CONST NOMEARQ = 'niver.txt'; 65 NOMEAUX = 'temp.txt'; TYPE data = RECORD dia : word; mes : word; ano : word END; pessoa = RECORD nome : string[25]; nasc : data END; VAR arq1 : text; arq2 : text; p : pessoa; proc : string[25]; BEGIN sai := false; proc := 'Armando Machado'; {abro o arquivo de dados para leitura} Assign(arq1,NOMEARQ); Reset(arq1); {crio o arquivo temporário} Assign(arq2,NOMEAUX); Rewrite(arq2); WHILE NOT Eof(arq1) DO BEGIN readln(arq1,p.nome); readln(arq1,p.nasc.dia,p.nasc.mes,p.nasc.ano); IF p.nome<>proc THEN BEGIN writeln(arq2,p.nome); writeln(arq2,p.nasc.dia:2,' ',p.nasc.mes:2,' ',p.nasc.ano:2) END END; {fecho os arquivos} Close(arq1); Close(arq2); {apago niver.txt} Erase(arq1); {renomeio temp.txt para niver.txt} Rename(arq2,'niver.txt') END. Viu como apagamos e renomeamos um arquivo? Erase(variável_de_arquivo); {apaga o arquivo do disco} Rename(variável_de_arquivo,'novo_nome'); {renomeia o arquivo, dando 'novo_nome' a ele} 66 Agora suponha que, nesse arquivo de onde "Armando Machado" foi removido,queremos mudar a data de nascimento de "Jacinto Dores" para 01/02/87, como fazemos? O algoritmo é muito semelhante: Abro "niver.txt" para leitura Crio "temp.txt" Enquanto "niver.txt" não chegar ao fim Leio nome e data do "niver.txt" Se nome Senão gravo os dados modificados Apago "niver.txt" Troco o nome de "temp.txt" para "niver.txt" ou seja: PROGRAM PROG; CONST NOMEARQ = 'niver.txt'; NOMEAUX = 'temp.txt'; TYPE data = RECORD dia : word; mes : word; ano : word END; pessoa = RECORD nome : string[25]; nasc : data END; VAR arq1 : text; arq2 : text; p : pessoa; proc : string[25]; nova : data; BEGIN sai := false; proc := 'Jacinto Dores'; nova.dia := 1; nova.mes := 2; nova.ano := 87; {abro o arquivo de dados para leitura} Assign(arq1,NOMEARQ); Reset(arq1); {crio o arquivo temporário} Assign(arq2,NOMEAUX); Rewrite(arq2); WHILE NOT Eof(arq1) DO BEGIN 67 readln(arq1,p.nome); readln(arq1,p.nasc.dia,p.nasc.mes,p.nasc.ano); writeln(arq2,p.nome); IF p.nome<>proc THEN writeln(arq2,p.nasc.dia:2,' ',p.nasc.mes:2,' ',p.nasc.ano:2) ELSE {substituo a data} writeln(arq2,nova.dia:2,' ',nova.mes:2,' ',nova.ano:2) END END; {fecho os arquivos} Close(arq1); Close(arq2); {apago niver.txt} Erase(arq1); {renomeio temp.txt para niver.txt} Rename(arq2,'niver.txt') END. Pronto, modificamos. STRINGS Até agora vimos como ler, escrever e lidar com caracteres. O problema é que, muitas vezes, queremos lidar com palavras ou frases. Como fazemos, então, para ler uma frase que o usuário digita? Um possível algoritmo seria: enquanto o usuário não digitou < enter > leio o caracter guardo o caracter lido e onde guardaríamos esses caracteres? Poderia ser em um vetor de caracteres. Um tanto quanto complicado, não? Bom, acontece que o Pascal descomplicou isso, criando o tipo String PROGRAM P; VAR s : String; BEGIN writeln('Sua frase: '); readln(s); writeln('Você disse: ',s) END. 68 O tipo string guarda uma cadeia de caracteres, da mesma forma que um vetor de caracteres. Assim, ao rodar o programa acima teríamos sua frase: essa é minha frase< enter > Você disse: essa é minha frase Simples, não? Uma coisa interessante sobre Strings é que o Pascal os trata como vetores de caracteres. Na verdade, String não é exatamente um ARRAY[1..255] OF char, mas é quase. Agora espera um pouco. De onde veio esse 1..255? Pois é! String, em Pascal, é limitado a 255 caracteres. Mas, e se quisermos um string maior ou menor? Basta definir o tamanho quando da declaração da variável: VAR s1 : String[10]; {s1 conterá no máximo 10 caracteres} s2 : String[1000]; {s2 conterá no máximo 1000 caracteres} Ou seja, de uma forma geral: VAR nome : String[limite]; ou VAR nome : String; {255 é o limite} Também posso declarar tipos com strings: TYPE nomes : String[20]; endereco : String[300]; VAR nome : nomes; end : endereco; Pelo fato do String ser semelhante a um vetor, posso fazer: PROGRAM P; VAR s : String; BEGIN writeln('Sua frase: '); readln(s); 69 writeln('Você disse: ',s); writeln(s[4]); {escreve o "a" de "essa é minha frase"} s[5] := 'z'; {agora s contém "essazé minha frase" - mudei o 5º caracter} END. Ou seja, posso usar o String como vetor de caracteres. mas será que a única vantagem de Strings é dar um nome a ARRAY[1..255] OF char? Não! Pascal tem facilidades embutidas. Por exemplo, para concatenar (juntar) 2 strings basta usar o sinal "+": PROGRAM P; VAR s1,s2,s3 : String; BEGIN readln(s1); readln(s2); writeln(s1+s2); s3 := s1 + s2; writeln(s3) END. Nesse caso, se a entrada for: Alô Mundo A saída será: AlôMundo AlôMundo Sendo o segundo "AlôMundo" o conteúdo da variável s3. Agora, como abastecemos uma variável string? PROGRAM P; VAR s1,s2,s3,s4 : String; BEGIN readln(s1); {leio s1} 70 readln(s2); {leio s2} s4 := 'Alô mundão!'; {carrego s4} s3 := s1 + ' -- ' + s2; s2 := s4; writeln('s1: ',s3); writeln('s2: ',s3); writeln('s3: ',s3); writeln('s4: ',s3) END. Repare nas aspas simples. É como em char. Se a entrada para esse programa for: Alô Mundo A saída será: Alô Alô mundão! Alô -- Mundo Alô mundão! Agora vamos ver algumas funções para tratamento de strings: Length(s : String) : Integer: Retorna o número de caracteres do string s. VAR s : string; n : integer; BEGIN s := 'Alô mundo!'; n := Length(s); {n terá 10} n := Length('Teste do length'); {n terá 15} END. Copy(s : String; inicio, tamanho : integer) : String: Retorna um substring de s, começando em início e com o tamanho especificado. VAR s : string; l : string; BEGIN s := 'Alô mundo!'; l := Copy(s,2,5); {l terá 'lô mu'} 71 l := Copy('Testando copy!',5,4); {l terá 'ando'} END. Delete(VAR s : String; começo, tamanho : integer): Elimita parte do string s, começa em início com o tamanho especificado. VAR s : string; l : string; BEGIN s := 'Alô mundo!'; Delete(s,3,5); {s terá 'Aldo', eliminando 'ô mun'} END. Str(x; VAR s : string) ou str(x:largura; VAR s : string) ou str(x:largura:decimais; VAR s : string): Transforma um número x em sua prepresentação string. x pode ser integer ou real, s terá x na forma string com largura e casas decimais especificadas. VAR s : string; x : real; BEGIN x := 3.14; Str(x,s); {s terá '3.14'} Str(-12,s); {s terá '-12'} END. 72 VETORES (ARRAY) Lembra de nosso programa que tirava a média de n números inteiros? Vamos repetí-lo para 10 números: PROGRAM notas; VAR num : integer; {número digitado} media : real; {média dos dados} cont : integer; {contador do for} soma : integer; {soma dos dados} BEGIN soma := 0; FOR cont := 1 TO 10 DO BEGIN write('entre um dado: '); readln(num); soma := soma + num END; media := soma / n; writeln('a média é ',media:4:2) END. Mas, e se quiséssemos guardar os vários valores de num? Como fazemos? Usamos um vetor (array): PROGRAM notas; 1. VAR num : ARRAY[1..10] OF integer; {vetor dos números digitados} media : real; {média dos dados} cont : integer; {contador do FOR} soma : real; {soma dos dados} BEGIN soma := 0; FOR cont := 1 TO 10 DO BEGIN write('entre um dado: '); 2. readln(num[cont]); 3. soma := soma + num[cont] END; media := soma / n; writeln('a média é ',media:4:2) END. Agora vamos por partes. antes de mais nada, o que é um vetor? Um vetor pode ser pensado como uma variável que consegue guardar vários valores de um determinado tipo. Assim, quando definimos: VAR num : ARRAY[1..10] OF integer; {vetor dos números digitados} 73 estamos criando uma variável, chamada num, que é capaz de guardar até 10 inteiros. Ou seja, na prática, esta seria uma variável assim: 1 2 3 4 5 6 7 8 9 10 onde em cada quadrado cabe um inteiro. Então, quando fazemos n[5] := 20; estamos colocando o inteiro 20 na 5ª posição do vetor: 1 2 3 4 5 6 7 8 9 10 20 Veja que, na linha 2 do nosso programa acima, abastecemos cada posição de "num" (usando, para isso, o contador "cont") com o inteiro dadopelo usuário. Agora, como usamos esses valores armazenados? Da mesma forma que uma variável comum, só que com sintaxe um pouco diferente (veja a linha 3 do nosso programa): VAR n : ARRAY[1..10] OF integer; x : integer; BEGIN x := n[8]; {x recebe o valor que está na 8ª posição do vetor} x := n[3] + 2*n[5]; {x recebe o dobro do valor que está na 5ª posição somado ao que está na 3ª posição do vetor} A forma geral da declaração de um vetor é: ARRAY[limite_inferior..limite_superior] OF tipo; Naturalmente, podemos definir um tipo com o vetor: TYPE vetor = ARRAY[1..10] OF integer; VAR v : vetor; 74 A vantagem desse uso é que posso passar um vetor como parâmetro de uma função ou procedimento. Assim, posso fazer: TYPE vetor = ARRAY[1..10] OF integer; VAR v : vetor; PROCEDURE P(x : vetor); BEGIN ... END; Vale notar que, como na passagem por valor, o valor da variável é copiado para o parâmetro, x será um vetor que conterá uma cópia dos valores contidos no vetor a ele passado. Como isso pode consumir muita memória, se o vetor for grande, é aconselhável efetuar a passagem por referência, desse modo: TYPE vetor = ARRAY[1..10] OF integer; VAR v : vetor; PROCEDURE P(VAR x : vetor); BEGIN ... END; Essa, na verdade, é a única maneira de se passar um vetor como parâmetro, já que o seguinte não é permitido: PROCEDURE P(x : ARRAY[1..10] OF integer;); BEGIN ... END; Da mesma forma, posso usar esse tipo como o retorno de uma função: TYPE vetor = ARRAY[1..10] OF integer; VAR v : vetor; FUNCTION F(x : real) : vetor; BEGIN ... END; BEGIN v := F(2.3); END. 75 enquanto que o mostrado abaixo não é permitido: TYPE vetor = ARRAY[1..10] OF integer; VAR v : vetor; FUNCTION F(x : real) : ARRAY[1..10] OF integer; BEGIN ... END; BEGIN v := F(2.3); END. Atenção: alguns compiladores só aceitam tipos simples como retorno de função, ou seja, nem esse artifício acima funciona. Para esses, você não poderá usar funções, terá que escrever tudo na forma de precedimentos. Vamos agora dar uma olhada no tipo dos dados que um vetor pode conter. Primeiro, que tipos são aceitos? Qualquer um! Então, posso ter: TYPE cor = (verde,vermelho,azul); complexo = RECORD re :real; im : real END; vetor1 = ARRAY[1..10] OF cor; vetor2 = ARRAY[1..10] OF complexo; vetor3 = ARRAY[1..10] OF real; VAR v1 : vetor1; v2 : vetor2; v3 : vetor3; BEGIN {carrego a 2ª posição de v1 com "verde"} v1[2] := verde; {carrego a 5ª posição de v2 com "3 - 4i"} v2[5].re := 3; v2[5].im := -4; {carrego a 3ª posição de v2 com "2 + 3i"} v2[3].re := 2; v2[3].im := 3; {somo o imaginário da 3ª posição ao da 5ª e guardo na 10ª} v2[10].re := v2[5].re + v2[3].re; v2[10].im := v2[5].im + v2[3].im; {guardo na 1ª posição de v3 o módulo do complexo na posição 10 de v2} v3[1] := Sqr(v2[10].re) + Sqr(v2[10].im) END. 76 Reparou como faço para guardar e usar valores registro que estão dentro de vetores? É só fazer variável_do_vetor[índice].nome_do_campo E quanto aos limites do vetor? Bom, esses, na verdade, devem ser um conjunto de valores seqüenciais: TYPE limites = 15..20; cor = (vermelho, verde, azul); inferior = 12; superior = 15; VAR v1 : ARRAY[inferior..superior] OF real; v2 : ARRAY[limites] OF integer; v3 : ARRAY[cor] OF char; v4 : ARRAY['a'..'z'] OF cor; v5 : ARRAY['1'..'5'] OF boolean; e como acessamos ou guardamos valores nos vetores acima? TYPE limites = 15..20; cor = (vermelho, verde, azul); inferior = 12; superior = 15; VAR v1 : ARRAY[inferior..superior] OF real; v2 : ARRAY[limites] OF integer; v3 : ARRAY[cor] OF char; v4 : ARRAY['a'..'z'] OF cor; v5 : ARRAY['1'..'5'] OF boolean; BEGIN v1[14] := 3.16; v2[16] := 12; v3[verde] := 'a'; v4['d'] := azul; v5['3'] := true; v1[2] := v1[14] + v2[16] / 5; v3[azul] := Chr(Ord(v3[verde]) + 3); {terá 'd'} v4[v3[verde]] := vermelho; {v4['a'] := vermelho} v5['2'] := v5['3'] OR v5['5'] END. Note que os limites não precisam começar de 1. Esses valores são apenas índices, podem começar de qualquer valor, desde que o limite inferior seja maior que o superior e os valores escolhidos como índices sejam seqüenciais. 77 Agora, como exemplo do uso de vetores, vamos fazer um programa que conte o número de vezes que cada letra foi digitada pelo usuário. O usuário termina o texto com '#'. Repare como fazemos para contar cada letra: PROGRAM contador; TYPE vetor = ARRAY['A'..'Z'] OF integer; VAR ch : char; {o caracter digitado} v : vetor; {guarda o número de vezes que cada letra foi digitada} cont : integer; {contador do FOR} {retorna true se c for letra, false se não} FUNCTION letra(c : char) : boolean; BEGIN letra := ((c>='a') AND (c<='z')) OR ((c>='A') AND (c<='Z')) END; BEGIN ch := readkey; WHILE ch<>'#' DO BEGIN {escrevo o ch na tela} write(ch); {se for letra...} IF letra(ch) THEN BEGIN {transformo em maiúscula} ch := Upcase(ch); {conto a letra correta - veja como faço} v[ch] := v[ch] + 1 END; {leio o próximo} ch := readkey END; {imprimo o vetor} writeln('Resultado:'); FOR cont:='A' TO 'Z' DO BEGIN writeln('v[',cont,'] = ',v[cont]) END END. Ao final desse programa, o vetor v será algo como: 'A' 'B' 'C' 'D' 'E ' 'F ' 'G' 'H' ' I ' ' J ' 'K ' 'L ' 'M' 'N' 'O' 'P ' 'Q' 'R' 'S ' 'T ' 'U' 'V' 'W' 'X' 'Y' 'Z ' 100 20 10 5 7 6 12 3 43 23 6 7 54 3 6 0 23 65 23 1 9 57 0 12 5 0 78 Ou seja, cada índice contém, em seu elemento correspondente no vetor, o número de vezes que a letra do índice foi digitada no texto. Vejamos outro exemplo, a coidificação de um texto. Nesse exemplo, o usuário digita um caracter e, se este for letra, nós o codificamos conforme a seguinte chave: Caracter Código Caracter Código 'A' 'C' 'N' 'K' 'B' 'Z' 'O' 'L' 'C' 'O' 'P' 'S' 'D' 'R' 'Q' 'E' 'E' 'A' 'R' 'H' 'F' 'G' 'S' 'M' 'G' 'B' 'T' 'P' 'H' 'N' 'U' 'I' 'I' 'U' 'V' 'X' 'J' 'T' 'W' 'Q' 'K' 'D' 'X' 'V' 'L' 'Y' 'Y' 'J' 'M' 'F' 'Z' 'W' Queremos, então, um programa que leia o caracter digitado pelo usuário, o substitua pelo caracter do código e escreva este último, mostrando ao usuário. Novamente, façamos o texto ser terminado por '#'. PROGRAM codigo; TYPE vetor = ARRAY['A'..'Z'] OF integer; VAR ch : char; {o caracter digitado} chave : vetor; {guarda a chave do código} {retorna true se c for letra, false se não} FUNCTION letra(c : char) : boolean; BEGIN letra := ((c>='a') AND (c<='z')) OR ((c>='A') AND (c<='Z')) END; {retorna true se c for letra maiuscula} FUNCTION maiuscula(c : char) : boolean; BEGIN maiuscula := letra(c) AND (c >= 'A') AND (c <= 'Z') END; {retorna a correspondente minúscula de c} FUNCTION mai_para_min(c : char) : char; BEGIN IF maiuscula(c) THEN mai_para_min := Chr(Ord(c) - Ord('A') + Ord('a')) ELSE mai_para_min := c 79 END; {inicializa a chave} PROCEDURE inicializa(c : vetor); BEGIN v['A'] := 'C'; v['B'] := 'Z'; v['C'] := 'O'; v['D'] := 'R'; v['E'] := 'A'; v['F'] := 'G'; v['G'] := 'B'; v['H'] := 'V'; v['I'] := 'U'; v['J'] := 'T'; v['K'] := 'D'; v['L'] := 'Y'; v['M'] := 'F'; v['N'] := 'K'; v['O'] := 'L'; v['P'] := 'S'; v['Q'] := 'E'; v['R'] := 'H'; v['S'] :='M'; v['T'] := 'P'; v['U'] := 'I'; v['V'] := 'X'; v['W'] := 'Q'; v['X'] := 'V'; v['Y'] := 'J'; v['Z'] := 'W' END; BEGIN ch := readkey; WHILE ch<>'#' DO BEGIN IF letra(ch) THEN BEGIN IF maiuscula(ch) THEN write(v[ch]) ELSE {é minúscula} write(mai_para_min(v[Upercase(ch)])) END ELSE {não é letra} write(ch); {leio a próxima} ch := readkey END END. Estude bem esse programa. MATRIZES 80 Vetores também podem ser usados para representar matrizes. Considere a matriz: [ 1 23 4 ] Seria interessante podermos representar isso, não? Como faríamos? Podemos criar um vetor de vetores: TYPE coluna = ARRAY[1..2] OF integer; matriz = ARRAY[1..2] OF coluna; e como abasteceríamos a matriz nessa estrutura de dados? TYPE coluna = ARRAY[1..2] OF integer; matriz = ARRAY[1..2] OF coluna; VAR m : matriz; BEGIN m[1][1] := 1; m[1][2] := 2; m[2][1] := 3; m[2][2] := 4 END. Bastante truculento, não é? Bem, felizmente o Pascal tem um modo mais fácil de representar uma matriz: TYPE matriz = ARRAY[1..2,1..2] OF integer; VAR m : matriz; BEGIN m[1,1] := 1; m[1,2] := 2; m[2,1] := 3; m[2,2] := 4 END. Simples, não? Nós agora representamos uma matriz 2 × 2. Considere agora a matriz 3 × 3: A = [ 1 2 3 4 5 6 7 8 9 ] Como podemos representá-la em Pascal? 81 TYPE matriz = ARRAY[1..3,1..3] OF integer; VAR A : matriz; BEGIN A[1,1] := 1; A[1,2] := 2; A[1,3] := 3; A[2,1] := 4; A[2,2] := 5; A[2,3] := 6; A[3,1] := 7; A[3,2] := 8; A[3,3] := 9 END. Note que a prepresentação do elemento Ai,j é A[i,j]. Realmente mnemônico. Como representamos agora uma matriz 3 × 2, como a abaixo? A = [ 1 2 3 4 5 6 ] Assim: TYPE matriz = ARRAY[1..3,1..2] OF integer; VAR A : matriz; BEGIN A[1,1] := 1; A[1,2] := 2; A[2,1] := 3; A[2,2] := 4; A[3,1] := 5; A[3,2] := 6 END. E a 2 × 3 abaixo? A = [ 1 2 34 5 6 ] Assim: TYPE matriz = ARRAY[1..2,1..3] OF integer; VAR A : matriz; 82 BEGIN A[1,1] := 1; A[1,2] := 2; A[1,3] := 3; A[2,1] := 4; A[2,2] := 5; A[2,3] := 6 END. Agora podemos criar funções para soma e multiplicação de matrizes: PROGRAM matrizes; TYPE matriz = ARRAY[1..2,1..2] OF integer; VAR m1,m2,m3 :matriz; FUNCTION soma(m1,m2:matriz) : matriz; BEGIN soma[1,1] := m1[1,1] + m2[1,1]; soma[1,2] := m1[1,2] + m2[1,2]; soma[2,1] := m1[2,1] + m2[2,1]; soma[2,2] := m1[2,2] + m2[2,2] END; BEGIN m1[1,1] := 1; m1[1,2] := 2; m1[2,1] := 3; m1[2,2] := 4; m2[1,1] := 5; m2[1,2] := 6; m2[2,1] := 7; L A Ç O S A N I N H A D O S m3 := soma(m1,m2); writeln(m3[1,1],' ',m3[1,2],' ',m3[2,1],' ',m3[2,2]) END. Novamente, alguns compiladores podem não aceitar isso, então, uma versão alternativa com procedimentos é: PROGRAM matrizes; TYPE matriz = ARRAY[1..2,1..2] OF integer; VAR m1,m2,m3 :matriz; PROCEDURE soma(m1,m2:matriz; VAR m3:matriz); BEGIN m3[1,1] := m1[1,1] + m2[1,1]; m3[1,2] := m1[1,2] + m2[1,2]; m3[2,1] := m1[2,1] + m2[2,1]; m3[2,2] := m1[2,2] + m2[2,2] END; 83 BEGIN m1[1,1] := 1; m1[1,2] := 2; m1[2,1] := 3; m1[2,2] := 4; m2[1,1] := 5; m2[1,2] := 6; m2[2,1] := 7; m2[2,2] := 8; soma(m1,m2,m3); writeln(m3[1,1],' ',m3[1,2],' ',m3[2,1],' ',m3[2,2]) END. as funções para subtração, multiplicação e divisão ficam como exercício. Então, como vimos, matrizes nada mais são que vetores em 2 dimensões. Mas, será que posso fazer em 3 dimensões? Posso fazer em quantas eu quiser... PROGRAM matrizes; TYPE matriz3D = ARRAY[1..2,1..2,1..2] OF integer; matriz5D = ARRAY[1..3,1..5,1..2,1..2,1..1] of integer; etc E como acesso valores nessas matrizes? PROGRAM matrizes; TYPE matriz3D = ARRAY[1..2,1..2,1..2] OF integer; matriz5D = ARRAY[1..3,1..5,1..2,1..2,1..1] of integer; VAR m1 : matriz3D; m2 : matriz5D; BEGIN m1[1,2,1] := 3; m2[1,1,3,2,1] := 5; etc... END. Quando você usará isso? Não sei, mas se precisar... Por fim, como fazemos para ler todos os valores de uma matriz n × m, dados n e m, e imprimí-los na tela? (Suponha que ela já está carregada) PROGRAM matrizes; 84 CONST n = 20; m = 30; TYPE matriz = ARRAY[1..n,1..m] OF integer; VAR mat : matriz; {a matriz} cont1 : integer; {contador do primeiro FOR} cont2 : integer; {contador do segundo FOR} BEGIN FOR cont1:=1 TO n DO FOR cont2:=1 TO m DO writeln('mat[',cont1,',',cont2,'] = ',mat[cont1,cont2]) END. E para "zerarmos" uma matriz n × m? PROGRAM matrizes; CONST n = 20; m = 30; TYPE matriz = ARRAY[1..n,1..m] OF integer; VAR mat : matriz; {a matriz} cont1 : integer; {contador do primeiro FOR} cont2 : integer; {contador do segundo FOR} BEGIN FOR cont1:=1 TO n DO FOR cont2:=1 TO m DO mat[cont1,cont2] := 0 END. Simples, não? Apenas 2 FORs. Na verdade, você deve usar um FOR para cada dimensão do vetor. Como uma matriz é um vetor em 2 dimensões, usamos 2 FORs, se tivesse k dimensões, teríamos que usar k FORs. BUSCA BINÁRIA Suponha que temos um vetor ordenado, com n elementos, e queremos verificar se um dado elemento, x, está nesse vetor. Como faríamos? A primeira idéia é comparar x a todo elemento do vetor. Mas isso é lento, pois temos que fazer n comparações, onde n é o número de elementos do vetor. Podemos usar do fato do vetor estar ordenado para reduzir o tempo de busca. Considere o algoritmo: 1. Seja y0 o 1º elemento do vetor e y1 o último. 85 2. Seja y o elemento do meio do vetor, ou seja, y = (y0 + y1) DIV 2. 3. Comparo x com y e, se x > y, então x está na metade superior do vetor. Nesse caso faço y0 = y + 1 e volto a 2. Se y0 = y1, x não está no vetor. 4. Se x < y, x está na metade inferior. Então faço y1 = y - 1 e volto a 2. Se y0 = y1, x não está no vetor. 5. Se x = y, achei. y0 = y1, x não está no vetor. Notou como é mais rápido? Não comparo com todos, apenas com alguns. Vejamos um exemplo: suponha que queremos saber seo 27 está no vetor abaixo: 1 2 3 4 5 6 7 8 1 5 8 9 12 15 27 30 vamos rodar o algoritmo: 1: y0 = 1 e y1 = 8 2: y = 4 3: como 27 > 4, ignoro a 1ª metade do vetor, fazendo y0 = 5. Ou seja, nosso vetor ficou: 5 6 7 8 12 15 27 30 2: y = 6 3: como 27 > 15, ignoro a 1ª metade do novo vetor, fazendo y0 = 7. Nosso vetor ficou: 7 8 27 30 2: y = 7 3: falhou, pois 27 não é > 27 4: falhou, pois 27 não é < 27 5: como 27 = 27, achei. Agora vamos procurar o número 7: 1 2 3 4 5 6 7 8 86 1 5 8 9 12 15 27 30 1: y0 = 1 e y1 = 8 2: y = 4 3: falha, pois 7 não é > 9 4: Como 7 < 9, ignoro a 2ª metade do vetor, fazendo y1 = 3. Nosso vetor fica: 1 2 3 1 5 8 2: y = 2 3: como 7 > 5, ignoro a 1ª metade do vetor, fazendo y0 = 3. Nosso vetor fica: 3 8 2: y = 3 3: falha, pois 7 não é > 8. 4: 7 < 8, mas como y0 = y1, então 7 não está no vetor. Agora, vamos estruturar mais nosso algoritmo: Seja y0 o 1º elemento do vetor, V, e y1 o último. Seja y o elemento do meio do vetor, ou seja, y = (y0 + y1) DIV 2. enquanto x 0 < y1 se x > V[y] faço y0 = y + 1 senão se x < V[y] faço y1 = y - 1 faço y = (y0 + y1) DIV 2 se x = V[y] achei senão não achei Em Pascal isso fica: (Suponha que o vetor já foi carregado com os valores) CONST MAX = 8; VAR v : ARRAY[1..MAX] OF integer; y0 : integer; y1 : integer; 87y : integer; x : integer; BEGIN {carreguei o vetor...} {começo o algoritmo} y0 := 1; y1 := MAX; y := (y0 + y1) DIV 2; write('Entre o inteiro a ser buscado: '); readln(x); WHILE (x <> v[y]) AND (y0 < y1) DO BEGIN IF x > v[y] THEN {parte superior} y0 := y + 1 ELSE IF x < v[y] THEN y1 := y - 1; y := (y1 + y0) DIV 2 END; IF x = v[y] THEN writeln('encontrado na posição: ',y) ELSE writeln('elemento não encontrado no vetor) END. O nome dessa busca é busca binária, pois, a cada passo, jogo fora metade do vetor em que estou trabalhando. 88 REGISTROS Registros são variáveis que podem guardar mais de um dado, inclusive de tipos diferentes. Por exemplo, suponha que queremos trabalhar com datas, no formato dia, mês e ano. Como faríamos? Uma alternativa é transformar nossa data em dias (ou meses ou anos) e então, após termos trabalhado com ela, transformar novamente para dia, mês e ano. Trabalhoso, não? Seria muito mais fácil se pudéssemos trabalhar no formato dia, mês e ano. Como fazemos isso? TYPE meses = (jan, fev, mar, abr, mai, jun, jul, ago, set, out, nov, dez); dias = 1..31; VAR data : RECORD dia : dias; mes : meses; ano : integer END; Aqui definimos uma variável do tipo "data", que é um registro composto de 3 campos: dia, do tipo "dias"; mes, do tipo "meses" e ano, do tipo integer. Assim, quando temos várias variáveis que dizem respeito à mesma coisa, as agrupamos em um registro. Também, podemos criar um tipo com registros: TYPE meses = (jan, fev, mar, abr, mai, jun, jul, ago, set, out, nov, dez); dias = 1..31; datas = RECORD dia : dias; mes : meses; ano : integer END; VAR data : datas Como sempre esse segundo enfoque é melhor, pois se precisarmos passar uma data como parâmetro de função ou procedimento ou como retorno de função fazemos PROCEDURE Q(d : datas); FUNCTION F1(l : datas) : boolean; FUNCTION F2(l : datas) : datas; uma vez que o seguinte não é permitido 89 PROCEDURE Q(d : RECORD dia : dias; mes : meses; ano : integer END;); FUNCTION F1(l : RECORD dia : dias; mes : meses; ano : integer END;) : boolean; FUNCTION F2(l : RECORD dia : dias; mes : meses; ano : integer END;) : RECORD dia : dias; mes : meses; ano : integer END; Como foi dito, os campos de um registro podem ser de qualquer tipo, inclusive outro registro. A forma geral de um registro é: RECORD campo1 : tipo1; campo2 : tipo2; campo3 : tipo1; ... campon : tipon END; Voltando à nossa data, como carregamos um valor na nossa variável? O segmento a seguir inicializa data com 26 de outubro de 2002: data.dia := 26; data.mes := out; data.ano := 2002; Ou seja, acessamos o campo de uma variável do tipo record fazendo: nome_da_variável.campo E para ler o valor de um registro? Fazemos da mesma forma que atribuímos, o conjunto nome_da_variável.campo pode ser usado como uma variável comum de tipo igual ao tipo do campo: PROGRAM P; TYPE meses = (jan, fev, mar, abr, mai, jun, jul, ago, set, out, nov, dez); dias = 1..31; datas = RECORD dia : dias; mes : meses; ano : integer END; VAR data : datas; 90 BEGIN data.dia := 26; data.mes := out; data.ano := 2002; write(data.dia,'/',Ord(data.mes)+1,'/',data.ano); IF data.ano>0 THEN writeln(' d.C.') ELSE writeln(' a.C.') END. O programa acima escreve "26/10/2003 d.C.". Viu? Podemos usar o conjunto nome_da_variável.campo como variáveis comuns. Um outro detalhe, como o campo "ano" é integer, ele é tratado como integer: PROGRAM P; TYPE meses = (jan, fev, mar, abr, mai, jun, jul, ago, set, out, nov, dez); dias = 1..31; datas = RECORD dia : dias; mes : meses; ano : integer END; VAR data : datas; FUNCTION quad(x : integer) : integer; BEGIN quad := x*x end; BEGIN data.dia := 26; data.mes := out; data.ano := 2002; write(data.dia,'/',Ord(data.mes)+1,'/',data.ano); IF data.ano>0 THEN writeln(' d.C.') ELSE writeln(' a.C.'); data.ano := quad(data.ano) {data.ano recebe 4008004 (2002 × 2002)} END. Vejamos alguns exemplos: Somar tempos: suponha que tenhamos dois tempos no formato hora, minuto e seguindo e queiramos somá-los, dando a resposta em hora, minuto e segundos, como faremos? Primeiro criamos um registro para o tempo: 91 TYPE tempo = RECORD hora : integer; min : word; seg : word END; Quem é esse word? Word é um tipo inteiro do Pascal, que vai de 0 a 65535. Agora, fazemos o programa: PROGRAM P; TYPE tempo = RECORD hora : integer; min : word; seg : word END; VAR t1 : tempo; t2 : tempo; t3 : tempo; BEGIN write('hora: '); readln(t1.hora); write('minuto: '); readln(t1.min); write('segundo: '); readln(t1.seg); write('hora: '); readln(t2.hora); write('minuto: '); readln(t2.min); write('segundo: '); readln(t2.seg); {calculo o número total de segundos} t3.seg := t1.seg + t2.seg; {calculo a soma dos minutos mais o número de segundos que virou minuto} t3.min := t1.min + t2.min + t3.seg DIV 60; {somo as horas mais o número de minutos que virou hora} t3.hora := t1.hora + t2.hora + t3.min DIV 60; {como seg não pode ser >60, guardo a parte menor, pois o resto transformei em min} t3.seg := t3.seg MOD 60; {como min não pode ser >60, guardo a parte menor, pois o resto transformei em hora} t3.min := t3.min MOD 60; {escrevo o resultado} 92 writeln(t3.hora,':',t3.min,':',t3.seg) Agora, e se quisermos fazer uma função que retorne a soma? Como definimos um tipo com o RECORD, podemos fazer tal função: PROGRAM P; TYPE tempo = RECORD hora : integer; min : word; seg : word END; VAR t1 : tempo; t2 : tempo; t3 : tempo; FUNCTION soma(t1,t2:tempo) : tempo; BEGIN {calculo o número total de segundos} soma.seg := t1.seg + t2.seg; {calculo a soma dos minutos mais o número de segundos que virou minuto} soma.min := t1.min + t2.min + soma.seg DIV 60; {somo as horas mais o número de minutos que virou hora} soma.hora := t1.hora + t2.hora + soma.min DIV 60; {como seg não pode ser >60, guardo a parte menor, pois o resto transformei em min} soma.seg := soma.seg MOD 60; {como min não pode ser >60, guardo a parte menor, pois o resto transformei em hora} soma.min := soma.min MOD 60 END; BEGIN write('hora: '); readln(t1.hora); write('minuto: '); readln(t1.min); write('segundo: '); readln(t1.seg); write('hora: '); readln(t2.hora); write('minuto: '); readln(t2.min); write('segundo: '); readln(t2.seg); t3 := soma(t1,t2); {escrevo o resultado} writeln(t3.hora,':',t3.min,':',t3.seg) 93 Vale lembrar que nem todos os compiladores aceitam tipos assim como retorno de uma função. Para esses compiladores, uma saída é definir um procedimento: PROGRAM P; TYPE tempo = RECORD hora : integer; min : word; seg : word END; VAR t1 : tempo; t2 : tempo; t3 : tempo; PROCEDURE soma(t1,t2:tempo; VAR t3:tempo); BEGIN {calculo o número total de segundos} t3.seg := t1.seg + t2.seg; {calculo a soma dos minutos mais o número de segundos quevirou minuto} t3.min := t1.min + t2.min + t3.seg DIV 60; {somo as horas mais o número de minutos que virou hora} t3.hora := t1.hora + t2.hora + t3.min DIV 60; {como seg não pode ser >60, guardo a parte menor, pois o resto transformei em min} t3.seg := t3.seg MOD 60; {como min não pode ser >60, guardo a parte menor, pois o resto transformei em hora} t3.min := t3.min MOD 60 END; BEGIN write('hora: '); readln(t1.hora); write('minuto: '); readln(t1.min); write('segundo: '); readln(t1.seg); write('hora: '); readln(t2.hora); write('minuto: '); readln(t2.min); write('segundo: '); readln(t2.seg); soma(t1,t2,t3); {escrevo o resultado} writeln(t3.hora,':',t3.min,':',t3.seg) 94 Números complexos: Sabemos que um número complexo possui sua parte real e imaginária. Assim, em (2+3i), 2 é a real e 3 a imaginária. Como podemos, então, fazer procedimentos ou funções que executem as quatro operações em complexos? Antes de mais nada, vamos definir um tipo complexo: TYPE complexo = RECORD re : real; im : real END; Agora, vejamos como são calculadas as 4 operações: (a + bi) + (c + di) = (a + c) + (b + d)i (a + bi) - (c + di) = (a - c) + (b - d)i (a + bi) × (c + di) = (ac - db) + (bc + ad)i (a + bi) ÷ (c + di) = (ac + bd)/(c² + d²) + ((cb - ad)/(c² + d²))i Vejamos como ficam a soma e a multiplicação. A subtração e a divisão ficam como exercício para você. PROGRAM P; TYPE complexo = RECORD re : real; im : real END; VAR n1, n2, n3 : complexo; FUNCTION soma(n1,n2:complexo) : complexo; BEGIN soma.re := n1.re + n2.re; soma.im := n1.im + n2.im END; FUNCTION mult(n1,n2:complexo) : complexo; BEGIN mult.re := n1.re*n2.re - n1.im*n2.im; mult.im := n1.im*n2.re + n1.re*n2.im END; BEGIN {faço n1 := 2 + 3i} n1.re := 2; n1.im := 3; {faço n2 := 4 - 2.3i} n2.re := 4; n2.im := 2.3; n3 := soma(n1,n2); writeln(n3.re,' + ',n3.im,'i'); {escreve 6 + 0.7i} 95 n3 := mult(n1,n2); writeln(n3.re,' + ',n3.im,'i') {escreve 14.9 + 7.4i} END. Observações: Comparação entre registros: No caso dos números complexos, como faço para verificar se um complexo é igual a outro? Infelizmente não há como fazer isso em Pascal: TYPE complexo = RECORD re : real; im : real END; VAR t1,t2 : complexo; BEGIN {abasteço t1 com 2-3i} t1.re := 2; t1.im := -3; {abasteço t2 com 4+2.3i} t2.re := 4; t2.im := 2.3; IF t1 = t2 THEN {faz algo} END. Como fazemos para testar registros então? Testamos campo a campo: TYPE complexo = RECORD re : real; im : real END; VAR t1,t2 : complexo; BEGIN {abasteço t1 com 2-3i} t1.re := 2; t1.im := -3; {abasteço t2 com 4+2.3i} t2.re := 4; t2.im := 2.3; IF (t1.re = t2.re) AND (t1.im = t2.im) THEN {faz algo} END. Pronto. Testado. 96 Registro dentro de registros: Vejamos agora, um exemplo de registro dentro de registro. Suponha que teremos que trabalhar com uma estrutura que deve conter uma data e um horário. O horário é algo assim: TYPE horario = RECORD hora : integer; min : word; seg : word END; Então, nossa estrutura seria algo assim: TYPE horario = RECORD hora : integer; min : word; seg : word END; TYPE data = RECORD dia : 0..31; mes : 1..12; ano : integer END; TYPE tempo = RECORD dia : data; hora : horario END; Note que posso por nomes duplicados de campos (como "dia" e "hora"), desde que pertençam a registros diferentes. Agora, como fazemos para armazenar uma data? E para ler? TYPE horario = RECORD hora : 0..23; min : 0..60; seg : 0..60 END; TYPE data = RECORD dia : 0..31; mes : 1..12; ano : integer END; TYPE tempo = RECORD dia : data; hora : horario END; VAR t : tempo; BEGIN {vou armazenar 12 de janeiro de 2002, 14h:05':30''} t.dia.dia := 12; 97 t.dia.mes := 1; t.dia.ano := 2002; t.hora.hora := 14; t.hora.min := 5; t.hora.seg := 30; {imprimo "12/1/2001 - 14:5:30"} writeln(t.dia.dia,'/',t.dia.mes,'/',t.dia.ano,' - ',t.hora.hora,':',t.hora.min,':',t.hora.seg) END. Viu como funciona? Ao fazer t.dia estou acessando o campo "dia" do registro "t". Mas esse campo, por sua vez, é um registro também, então, não posso acessá-lo diretamente, tenho que acessar seus registros. Assim, para acessar o campo "mes" do registro correspondente ao campo "dia" do registro "t" faço "t.dia.mes". 98 TIPOS ENUMERADOS Suponha que tenhamos uma variável que só possa assumir um dentre um conjunto de valores. Por exemplo, suponha que estejamos criando um registro de pessoas que conhecemos para, por exemplo, fazer um programa que nos lembre de seus aniversários. Assim, cada pessoa ou é parente, ou amigo. Então, como marcaríamos se a pessoa é um ou outro? Poderíamos associar a cada pessoa uma variável booleana que assumisse true se a pessoa é parente e false se é amigo. Isso funcionaria. Mas, e se, além desses, quisermos incluir os colegas, ou seja, a pessoa ou é parente, ou amigo, ou colega? Poderíamos associar um número a cada tipo de pessoa: 0 para parente, 1 para colega e 2 para amigo, ou seja, construímos a tabela: Código Tipo 0 Parente 1 Colega 2 Amigo Nada mnemônico, não é mesmo? Será que há como fazer melhor? Há. Podemos criar um tipo enumerado: TYPE pessoa = (parente, amigo, colega); VAR p : pessoa; Aqui criamos um tipo, chamado pessoa, que aceita apenas um dos valores da lista e, em seguida, declaramos uma variável desse tipo. Como fazemos para abastecer um valor nessa variável? BEGIN p := amigo; p := parente; p := colega; p := conhecido {erro de compilação} END. De fato, o compilador representa esses 3 valores como 0, 1 e 2. Exatamente como nossa tabela acima. A vantagem do uso de enumerações é que, além do código ficar mnemônico, ele ocupa menos espaço na memória pois, para guardar 0, 1 e 2 na memória 1 byte é suficiente, enquanto que para guardar um inteiro são necessários de 4 a 8 bytes, dependendo da máquina. Naturalmente, não podemos declarar nada com os nomes dos valores usados na lista, pois eles são tidos como identificadores. Se tentarmos declarar algo com o mesmo nome o compilador acusará um erro. Por exemplo, o programa a seguir está errado: 99 TYPE estado = (estudante, faculdade, docente); {certo} naoAdministrativo = (estudante, professor); {declaração duplicada de "estudante"} VAR faculdade : estado; {declaração duplicada de "faculdade"} BEGIN estudante := 2; {estudante não é variável, é valor} END. Veja que não precisamos, de fato, criar uma enumeração para obter o resultado, bastaríamos criar constantes. Por exemplo, o seguinte programa tem o mesmo efeito do programa inicialmente desenvolvido: CONST parente = 0; amigo = 1; colega = 2; VAR p : integer; BEGIN p := amigo; p := parente; p := colega; p := conhecido {erro de compilação} END. Mas, apesar disso, a vantagem de usar enumerações é que estou atrelando os valores ao tipo da variável. Por exemplo, em: TYPE pessoa = (parente, amigo, colega); VAR p : pessoa; sóposso por "q := parente" se "q" for do tipo "pessoa". Devido ao modo como o Pascal representa as listas enumeradas (mostrado acima), os valores nestas são ordenados. Assim, no nosso exemplo, parente < amigo < colega. Esses valores, justamente por serem ordenados, podem ser usados dentro de um FOR: FOR p:=parente TO colega DO ou FOR p:=colega DOWNTO parente DO 100 PRED E SUCC Como fazemos se quisermos determinar o predecessor e o sucessor de um tipo enumerado? Usamos Pred e Succ. Considere o exemplo: TYPE digitos = (zero, um, dois, tres, quatro, cinco, seis, sete, oito, nove); cores = (azul, vermelho, verde); VAR x : digitos; y : digitos; c : cores; z : cores; BEGIN y := dois; x := Pred(y); {x recebe "um"} y := Succ(y); {y recebe "tres"} c := Pred(verde); {c recebe "vermelho"} z := Succ(c); {z recebe "verde"} END. Essas funções não trabalham só com tipos enumerados, mas também com qualquer tipo escalar que seja ordenado, exceto real. Assim: VAR x : integer; a : boolean; BEGIN x := Pred(6); {x recebe 5} a := Succ(false) {a recebe "true"} END. Mas, e se eu estiver no limite? Ou seja, quanto valem Pred(zero) ou Succ(nove) no exemplo acima? Bom, esses valores não estão definidos, ou seja, darão erro. Agora, suponha que temos uma lista enumerada e queremos saber a posição de cada valor na lista: TYPE pessoa = (parente, amigo, colega); VAR p : pessoa; x : integer; BEGIN x := Ord(amigo); {x recebe 1} END. Isso mesmo! Ord serve também para dar a posição de determinado valor em uma lista enumerada. Assim, podemos imprimir a tabela criada pelo computador da seguinte maneira: 101 TYPE pessoa = (parente, amigo, colega); VAR p : pessoa; x : integer; BEGIN FOR p:= parente TO colega DO write(Ord(p),' ') END. Esse programa imprime: 0 1 2 102 O COMANDO CASE Suponha que, em um determinado programa, temos a seguinte interface Escolha um planeta: (M)ercúrio (V)ênus (T)erra M(a)rte (J)úpiter (S)aturno (U)rano (N)etuno (P)lutão Sua escolha: e queremos processar aentrada, ou seja, ler o caracter do usuário (que pode ser maiúsculo ou minúsculo) e abastecer uma variável com a gravidade do planeta escolhido. Como fazemos isso? Até agora teríamos que fazer: PROGRAM Planetas; VAR g : real; {a gravidade} p : char; {o planeta escolhido} {mostra a interface e devolve a letra digitada} FUNCTION interface : char; BEGIN writeln('Escolha um planeta:'); writeln(' (M)ercúrio'); writeln(' (V)ênus'); writeln(' (T)erra'); writeln(' M(a)rte'); writeln(' (J)úpiter'); writeln(' (S)aturno'); writeln(' (U)rano'); writeln(' (N)etuno'); writeln(' (P)lutão'); write('Sua escolha: '); readln(interface) END; BEGIN p := interface; IF (p='m') OR (p='M') THEN g := 1 {valor fictício} ELSE IF (p='v') OR (p='V') THEN g := 2 ELSE IF (p='t') OR (p='T') THEN g := 3 ELSE IF (p='a') OR (p='A') THEN g := 4 ELSE IF (p='j') OR (p='J') THEN g := 5 ELSE IF (p='s') OR (p='S') THEN g := 6 ELSE 103 IF (p='u') OR (p='U') THEN g := 7 ELSE IF (p='n') OR (p='N') THEN g := 8 ELSE IF (p='p') OR (p='P') THEN g := 9 ELSE writeln('planeta não existente') END. Deve haver um modo melhor de fazer isso não? E tem: PROGRAM Planetas; VAR g : real; {a gravidade} p : char; {o planeta escolhido} {mostra a interface e devolve a letra digitada} FUNCTION interface : char; BEGIN writeln('Escolha um planeta:'); writeln(' (M)ercúrio'); writeln(' (V)ênus'); writeln(' (T)erra'); writeln(' M(a)rte'); writeln(' (J)úpiter'); writeln(' (S)aturno'); writeln(' (U)rano'); writeln(' (N)etuno'); writeln(' (P)lutão'); write('Sua escolha: '); readln(interface) END; BEGIN p := interface; CASE p OF 'm' : g := 1; 'v' : g := 2; 't' : g := 3; 'a' : g := 4; 'j' : g := 5; 's' : g := 6; 'u' : g := 7; 'n' : g := 8; 'p' : g := 9 else writeln('Planeta inexistente') END END. Simples não? Assim o case permite testar se uma expressão é um de vários valores. Se nenhum dos valores for igual ao da expressão, o ELSE é ativado. Vale lembrar que, como no IF, esse ELSE é totalmente opcional, se o omitirmos e o usuário digitar uma letra não prevista, o programa simplesmente sai do CASE, passando à próxima linha de programa. Mas note que nosso programa acima aceita somente letras minúsculas. E se o usuário digitar M? O programa dará a mensagem de "Planeta inexistente".Como fazemos para 104 resolver isso? Uma solução é transformar tudo em minúscula antes de testar, outra é usar o CASE para testar as maiúsculas também: PROGRAM Planetas; VAR g : real; {a gravidade} p : char; {o planeta escolhido} {mostra a interface e devolve a letra digitada} FUNCTION interface : char; BEGIN writeln('Escolha um planeta:'); writeln(' (M)ercúrio'); writeln(' (V)ênus'); writeln(' (T)erra'); writeln(' M(a)rte'); writeln(' (J)úpiter'); writeln(' (S)aturno'); writeln(' (U)rano'); writeln(' (N)etuno'); writeln(' (P)lutão'); write('Sua escolha: '); readln(interface) END; BEGIN p := interface; CASE p OF 'm','M' : g := 1; 'v','V' : g := 2; 't','T' : g := 3; 'a','A' : g := 4; 'j','J' : g := 5; 's','S' : g := 6; 'u','U' : g := 7; 'n','N' : g := 8; 'p','P' : g := 9 else writeln('Planeta inexistente') END END. Viu como fazemos um OU dentro do CASE? Com "," separando as opções. Assim, a forma geral do CASE é: CASE expressão OF valor1 : comando1; valor2, valor3 : comando2; ... valorn : comandon else comandom END; Agora, algumas observações sobre o CASE; ? Expressão pode ser qualquer expressão, variável, ou chamada de função. 105 ? Comando é um único comando, ou chamada a um procedimento. ? Se quisermos por mais de um comando no CASE devemos fazer: CASE expressão OF valor1 : BEGIN comando1; comando2; ... comandon END; valor2, valor3 : BEGIN comandoj; ... comandol; END; ... valorn : comandom END; ? mas isso não é considerado boa programação, pois não gera um código limpo. O ideal é que criemos procedimentos: PROCEDURE P1 BEGIN comando1; comando2; ... comandon END; PROCEDURE P2; BEGIN comandoj; ... comandol END; BEGIN CASE expressão OF valor1 : P1; valor2, valor3 : P2; ... valorn : comandom END END. ? CASE não aceita coisas do tipo (c é inteiro): CASE c OF 30..40 : P1 END; ? mas aceita 106 CASE c OF 30+40 : P1 {verifica se é igual a 70} END; 107 TIPO SUBRANGE (INTERVALO) Suponha que queremos um programa em que uma variável, v, só pode ter valores inteiros de 0 a 100. Poderíamos declarar: VAR v : integer; mas isso permitiria que fizéssemos v := 1000; o que não deveria ser permitido! Como fazemos para, então, limitar os valores que podemos atribuir à nossa variável? VAR v : 0..100; O que fizemos aqui? Declaramos uma variável de um tipo especial, um intervalo, que vai de 1 a 100. O tipo intervalo (subrange) é, na verdade, um subintervalo de algum tipo do Pascal. No exemplo acima, como 0 e 100 são inteiros, é um intervalode 0 a 100 de inteiros. De fato, podemos ter intervalos de qualquer tipo padrão do Pascal, exceto real, desde que o tipo permita valores seqüenciais ('a', 'b' etc, 0, 1 etc). Então, podemos ter intervalos do tipo: VAR v : 0..100; x : 'a'..'d'; y : '5'..'9'; {note que são caracteres, não números} Assim, de uma forma geral, um intervalo é representado da seguinte maneira: limite_inferior..limite_superior note que não há espaços entre os limites e os "..". Qual a vantagem do uso de intervalo? Não cometemos erros de programação, como os abaixo: PROGRAM P; VAR x : 1..100; BEGIN x := 5; {aceito} x := 0; {erro de compilação} x := 200; {erro de compilação} 108 x := 'a'; {erro de compilação} x := 3.24 {erro de compilação} END. Assim, o uso de intervalos é mais uma técnica de auto-contenção usada pelo programador do que uma estrutura de dados mais elaborada. TYPE Suponha agora que, em vez de sempre digitarmos 1..100 queremos dar um jeito de criar um tipo que represente esse intervalo. Como faremos? PROGRAM P; TYPE intervalo = 1..100; VAR x : intervalo; BEGIN x := 10; x := x + 5*x END. Pronto! Repare na sintaxe: TYPE nome_do_tipo_criado = o tipo; Assim, o que fizemos foi criar um tipo novo, chamado intervalo, que é um intervalo de 1 a 100 de inteiros. Esse tipo pode ser usado normalmente como qualquer outro tipo do Pascal. Assim conseguimos criar tipos diferentes de real, boolean, integer e char. Por exemplo: PROGRAM Exemplo; TYPE LetraMai = 'A'..'Z'; Digito = '0'..'9'; {note que é caracter} hora = 1..24; VAR inicial : LetraMai; tempo : Hora; num : Digito Agora a pergunta é: precisamos mesmo usar TYPE? Não! Como vimos acima, podemos definir direto na declaração da variável, sem precisar de TYPE. Então, qual a vantagem de declararmos um tipo novo com TYPE? Bem, antes de mais nada, o fato de eu poder dar nome aos meus tipos deixa o programa mais legível, de mais fácil compreensão e, dependendo da estrutura de dados que o tipo criado representa, o uso de TYPE pode poupar tempo de programação. 109 Mas essa não é a principal vantagem de TYPE. A grande vantagem deste está no retorno de funções e na passagem de parâmetros para funções e procedimentos. Uma característica da linguagem Pascal é que NÃO posso definir procedimentos e funções como os abaixo: PROGRAM Exemplo; PROCEDURE P(x : 1..20); BEGIN ... END; FUNCTION F1(x : 1..20); BEGIN ... END; FUNCTION F2(y : integer) : 1..20; BEGIN ... END; BEGIN ... END. Já se eu der nome ao meu intervalo, com TYPE, o compilador ACEITA: PROGRAM Exemplo; TYPE intervalo = 1..20; PROCEDURE P(x : intervalo); BEGIN ... END; FUNCTION F1(x : intervalo) : integer; BEGIN ... END; FUNCTION F2(y : integer) : intervalo; BEGIN ... F2 := 15 {se não estiver no intervalo 1..20, o compilador reclama} END; BEGIN ... END. Mas por que ele não aceita da primeira forma e aceita da segunda se as duas são equivalentes? Porque em Pascal somente nomes de tipos podem aparecer nos parâmetros de procedimentos e funções. Assim, para fazer nosso programa rodar, temos 110 que dar nome aos tipos diferentes de Integer, Real, Char e Boolean (há outros, variações destes 4 básicos). 111 FUNÇÕES Lembra como fazíamos para retirar um valor de um procedimento? PROGRAM P; VAR c,a,b:integer; PROCEDURE Soma(a,b : integer; VAR resp : integer); BEGIN resp := a+b END; BEGIN a := 2; b := 4; Soma(a,b,c); writeln(c) {escreve 5} END. Será que não há um modo mais direto? Vamos ver, como tiramos, em Pascal, a raiz de um número y? x := Sqrt(y); E o módulo de y? x := Abs(y); Não seria interessante fazer c := Soma(a,b)? Como posso vazer isso? Fazendo uma função, cuja forma geral é: FUNCTION nome(parâmetros) : tipo de retorno; VAR variáveis locais; BEGIN corpo da função, com cálculos etc; nome := resposta {carrego a resposta a ser devolvida pela função} END; Vamos fazer um paralelo entre função e procedimento (ambos fazem a mesma coisa): VAR x; PROCEDURE soma(a,b:integer; VAR r:integer); BEGIN r := a+b END; FUNCTION soma(a,b:integer) : integer; BEGIN soma := a+b END; BEGIN x := soma(2,3); writeln(x) {escreve 5} 112 BEGIN soma(2,3,x); writeln(x) {escreve 5} END; END. Vejamos as diferenças: ? Uma função exige a palavra FUNCTION. ? Exige um tipo de retorno, que é o tipo do valor que ela devolve. ? Como último ato da função, você abastece o valor que ela deve devolver em uma variável local com o mesmo nome da função. Essa variável não pode ser declarada, pois, quando a função é criada, essa variável, com o mesmo nome e tipo igual ao tipo de retorno da função é criada automaticamente pelo compilador. ? Dessa forma, o nome da função pode ser usada como uma variável local. De fato, o resultado da função será o valor que está armazenado nessa variável quando a função termina de executar (chega ao END de seu corpo). ? A única restrição com relação a essa variável é que ela deve ficar sempre à esquerda em comandos de atribuição, ou seja, não temos como ver o que está armazenado nela dentro da função, apenas podemos atribuir valores a ela. Atenção: essa restrição não é válida em todos os compiladores, ou seja, para alguns, essa é uma variável local comum. ? Não posso chamar funções como procedimentos. Vamos ver um exemplo de erro: {retorna a soma de 1 até "limite"} FUNCTION soma(lim:integer) : integer; VAR n : integer; BEGIN soma := 0; FOR n:=1 TO lim DO soma := soma + n {erro em alguns compiladores!} END; Novamente: esse erro só existe em alguns compiladores. No free pascal, por exemplo, esse programa está correto e roda normalmente sem erro. Ou seja: antes de usar isso, verifique se o teu compilador aceita! Se aceitar, use à vontade. Caso seu compilador não aceite, você pode substituir a função acima por essa: {retorna a soma de 1 até "limite"} FUNCTION soma(lim:integer) : integer; VAR n,t : integer; BEGIN t := 0; FOR n:=1 TO lim DO t := t + n; soma := t END; 113 Como foi mencionado antes, não podemos chamar uma função como procedimento. Ela devolve um valor que deve ser usado, pois a função representa esse valor. Assim, posso até passá-la como parâmetro: BEGIN writeln('2 + 3 = ', soma(2,3)) {escreve "2 + 3 = 5"} END. ou: BEGIN a := soma(soma(2,3),6); writeln(a) {escreve "11"} END. Com relação aos parâmetros, funções são como procedimentos. Sua grande utilidade é podermos usá-las em expressões: CONST PI = 3.1416; VAR raio, lado : real; vol_esfera : real; vol_cubo : real; FUNCTION cubo(n:real):real; BEGIN cubo := n*n*n END; BEGIN raio := 3; lado := 2; vol_esfera := 4*PI*Cubo(raio)/3; vol_cubo := Cubo(lado) END. Então, como funciona a FUNCTION? ? Primeiro ela recebe os parâmetros ? Depois executa os comandos em seu corpo ? Ao final, retorna o último valor armazenado na variável local de nome igual ao da função. Mas, e se quisermos devolver dois valores? Por exemplo, suponha que queremos uma função que calcule a média de alguém e me diga se este passou. Como faríamos? PROGRAM P; VAR media : real; {n1, n2 e n3 são as notas. A função retorna true se ele passou 114 e false se não, carregando media com a média do sujeito} FUNCTION Passou(n1,n2,n3:real; VAR media:real):boolean; BEGIN media := (n1 + n2 + n3) /3; Passou := media >= 5 {truese media >=5, false se não} END; BEGIN IF Passou(5,3,7,media) THEN writeln('Passou com média ',media) ELSE writeln('Reprovou com média ',media) END. Nesse programa, a média é calculada e armazenada em "media" e, se esta for ����D� função retorna true, senão retorna false. Note que antes do retorno da função, que se dá somente no END do corpo desta, ou seja, quando ela termina, "media" já havia sido modificada. Talvez, para entender melhor, devamos notar a similaridade entre uma função matemática e uma FUNCTION. Como dizemos que y é uma função de x? Fazendo: y = f(x) Ou seja, a cada valor de x, corresponde um y dado por essa relação. Como, então, representamos essa mesma função em Pascal? y := f(x); Parecidíssimo, não? Aqui, dado um valor de x, y é abastecido com o valor dado por f(x). Notou a semelhança do modo de pensar? Não é à toa, FUNCTION foi projetada para ter essa semelhança. Mas será que o concei to de função nos é tão novo? Não, já usamos funções anteriormente, vejamos alguns exemplos: VAR ch : char; n : integer; m : real; BEGIN ch := readkey; n := ord(ch); m := Sqrt(n) END. essas funções correspondem às seguinte definições: FUNCTION readkey : char; 115 BEGIN ... END; FUNCTION ord(c : char) : integer; BEGIN ... END; FUNCTION sqrt(x:real) : real; BEGIN ... END; e já vêm pré-definidas com o Pascal. Em suma: Use procedimentos quando tiver um pedaço de código que se repete (recebendo ou não parâmetros), e use funções quando esse pedaço de código precisar retornar um valor. Obs: Com o que sabemos, FUNCTION só retorna valores simples, com nomes, como real, integer, boolean, char etc. Para saber como retornar valores mais complexos, verifique as próximas notas. 116 PASSAGEM DE PARÂMETROS POR VALOR E REFERÊNCIA Até agora, tudo que podíamos fazer com procedimentos era passar valores a eles. Mas, e como retirar um valor deles? Considere o seguinte exemplo: quero um procediemtno que, dadas 3 notas, tire sua média conforme a fórmula: m = (2 × n1 + 3 × n2 + 5 × n3 ) ÷ 10 como faço? Até o que sabemos, podemos fazer: PROCEDURE media(n1,n2,n3 : real); VAR m : real; BEGIN m := (2*n1 + 3*n2 + 5*n3) / 10; write(m) END; Mas, e se quisermos usar o valor de m depois? Podemos fazê-lo global: PROGRAM P; VAR m : real; PROCEDURE media(n1,n2,n3 : real); BEGIN m := (2*n1 + 3*n2 + 5*n3) / 10 END; BEGIN media(3.5,8,10); write(m) END. Funciona? Claro! MAs é horrível. Por que? Porque não é desejável que variáveis globais sejam modificadas dentro de procedimentos, pois, se tivermos dezenas de procedimentos em nosso programa, podemos nos confundir. Então o que faremos? Temos que ter um meio de calcular a média no procedimento e retirar o valor de lá. Como? Passando uma variável por referência: PROGRAM P; 1. VAR m : real; 4. PROCEDURE media(n1,n2,n3 : real; VAR resp : real); BEGIN 5. resp := (2*n1 + 3*n2 + 5*n3) / 10 END; BEGIN 2. media(3.5,8,10,m); 117 3. write(m) END. Notou o VAR no parâmetro de "media"? Ele indica que a variável que segue a ele é passada por referência. Como funciona um parâmetro por referência? Pense nele como uma variável local sem personalidade. Ela precisa de personalidade e, quando passamos a ela uma variável (como na linha 2. do programa acima), essa variável (no caso resp) assume a personalidade da variável passada (m). Ou seja, no nosso exemplo, tudo que fizermos com resp estaremos, na verdade, fazendo com m. Vamos ver o diagrama de execução do programa: 1: 2: 5: 3: Veja em 2 como representamos que resp recebeu m por referência: usamos uma seta indicando que qualquer mudança em resp será feita em m, o que de fato ocorre em 5, quando, ao darmos valor para resp, estamos dando para m. Essa seta póde ser vista como resp apontando para seu chefe, m. Então quando queremos algo com resp e vamos a ela (para por exemplo, ler seu val.or ou guardar algo 118 nela), resp diz: "Não é comigo, é com ela!" e aponta para m. Assim, ao lermos resp lemos m, e ao modificarmos resp, modificamos m. Vamos ver um exemplo: PROGRAM A; 1. VAR x,y : real; 2. a,b : integer; 3. PROCEDURE B(x : real; VAR y : integer); 4. VAR z : integer; BEGIN 5. z := Trunc(x / 2); 6. y := 2*z END; BEGIN 7. x := 3.1416; 8. B(x,a); 9. write(a); 10. y := 2*x; 11. B(x+y,b); 12. write(b) END. 1 e 2: 7: 8, 3 e 4: 5: 119 6: Note que modifiquei o "a". 9: 10: 11, 3 e 4: 5: 120 6: 12: Estude atentamente os quadros acima e note que: ? As variáveis podem ter o mesmo nome, não há confusão. O compilador diferencia local de global. De fato, podemos ter até: PROGRAM A; 1. VAR x : integer; 2. PROCEDURE B(VAR x : integer); BEGIN 3. x := 2 END; BEGIN 4. B(x); 5. write(x) END. 121 ? Aqui, o x de A é diferente do de B. Em 3. modificamos o de B, mas como em 4. passamos o x de A para o x de B, modificações no x de B refletirão node A. Verifique. ? Parâmetros por referência exigem variáveis! Assim, não é possível chamar B desse modo: B(2,2); B(2,x+a); etc ? Um parâmetro VAR exige que eu passe uma variável. ? Posso ter múltiplos parâmetros VAR. Por exemplo: B(x:int; VAR y: int; VAR z,a : real); ? tem 4 parâmetros, sendo 3 deles por referência, ou seja, posso modificar o que passo a eles. Então, em suma: B(x) ? Passagem por valor: passo ao procedimento uma cópia do valor que está em x, se a definição de B for B(a : tipo); ? Passagem por referência: passo a B uma referência a x, se a definição for B(VAR a : tipo);, ou seja, qualquer mudança em "a" refletirá em x. De fato, parâmetros por referência são simplesmente nomes que o procedimento dá a variáveis que estão em outro lugar. O parâmetro pode, então, ser visto como um nome local para uma variável externa ao procedimento. Observação: uma vez que posso ler de dentro do procedimento o valor que há na variável que foi passada por referência, posso fazer coisas do tipo: PROGRAM A; VAR x : integer; PROCEDURE B(VAR n : integer); BEGIN n := (n * 5) MOD 3 END; BEGIN x := 4; B(x); write(x) {x = 2} END. Em B, pego o valor anterior de x, 4, multiplico por 5, pego o resto da multiplicação dividida por 3 e guardo em x. Por fim, podemos chamar procedimentos de dentro de procedimentos, passando os parametros por referencia. Por exemplo: 122 1. PROGRAM A; 2. VAR x : integer; 3. PROCEDURE B(VAR n : integer); BEGIN 4. n := (n * 5) MOD 3 END; 5. PROCEDURE C(VAR m : integer); BEGIN 6. B(m); 7. m := m + 3 END; BEGIN 8. x := 4; 9. C(x); 10. write(x) {x = 5} END. Vamos fazer o diagrama de execuçao: 1 e 2 - o programa é definido: 8 - atualizo x: 9 e 5 - chamo C passando x por referência: 6 e 3 - chamo B passando m por referência. Note que, como m é uma encarnação de x, o que passo a B é x por referência: 123 4 - aqui é mais truculento. Primeiro devo pegar o valor de n e então fazer a conta e por em n novamente: Então vamos pegar o valor de n. Primeiro o compilador busca em n, esse diz: "não é comigo, é com o m", aí o compilador vai buscar em m o valor que ele quer, e m diz: "não é comigo, é com x". Finalmente, o compilador busca o valor em x. É muito semelhante a uma repartição pública não? Agora, após feito o cálculo, o compilador tenta colocar o resultado em n, esse diz que não é com ele, e sim com m. O compilador, então tenta por em m e essediz que não é com e le , e s im com x , a í o compilador tenta, e consegue, por o valor em x. Notou qual variável foi modificada no final das contas? x. 7 - modificamos m, modificando x: 10 - a saída é impressa: 124 VARIÁVEIS LOCAIS E GLOBAIS Se em seu programa você tem procedimentos, é possível declarar variáveis dentro de cada um deles, bem como dentro do programa em si. As constantes e variáveis declaradas após PROGRAM e fora dos procedimentos são chamadas de globais. Constantes e variáveis declaradas após PROCEDURE, ou seja, dentro do procedimento, são chamadas locais. Então, no programa abaixo: PROGRAM exemplo; CONST c1 = 3; c2 = 4; VAR v1 : integer; v2,v3 : real; PROCEDURE P; CONST c3 = 5; VAR v4,v5 : real; BEGIN ... END; BEGIN ... END. c1 e c2 são constantes globais, enquanto que c3 é local, e v1, v2 e v3 são variáveis globais, enquanto que v4 e v5 são locais. O que essa diferença significa? Variáveis globais são visíveis de todo o programa, as locais somente de dentro do procedimento onde foram declaradas. Assim, c3, v4 e v5 só são vistas em P, enquanto que c1, c2, v1, v2 e v3 podem ser acessadas de qualquer lugar no programa. Mas, o que acontece se tenho variáveis locais e globais com o mesmo nome? PROGRAM P; VAR v1,v2,v3 : integer; PROCEDURE Q; VAR v1,v2 : real; BEGIN ... END; BEGIN ... END. Dentro de Q, qualquer referência a v1 e v2 se tratará da v1 e v2 de Q. As globais não são visíveis, ou seja, as globais foram eclipsadas pelas locais de mesmo nome. 125 Quando, de dentro de Q, você chama v1, o compilador entende que é v1 de Q. Talvez seja mais fácil de entender assim: v1 é João, assim, v1 de P é João Silva, enquanto que v1 de Q é João Souza. Ambos são João, mas são diferentes. Como o compilador aceita conhecer no máximo um João, ele escolhe o mais próximo do lugar onde João foi chamado (na hierarquia). Mas isso não é tudo! Podemos ter procedimentos dentro de procedimentos: PROGRAM A; PROCEDURE B; PROCEDURE D; BEGIN ... END; {D} BEGIN ... END;{B} PROCEDURE C; PROCEDURE E; BEGIN ... END; {E} PROCEDURE F; BEGIN ... END; {F} BEGIN ... END; {C} BEGIN ... END. {A} Aqui, E e F estão dentro de C; D está dentro de B e B e C dentro de A. E agora? de onde posso chamar cada procedimento? Procedimento: Pode ser chamado por comandos em: B A, C, E, F, B C A, C D B, D E C, F, E F C, F Obs: O caso de chamar um procedimento de dentro dele mesmo será visto mais tarde no curso. Esteja seguro de ter entendido isso! Considere, se ajudar, cada procedimento como um micro-programa que, por sua vez, pode ter dentro dele novos procedimentos. 126 Note que um procedimento pode ser chamado de dentro daqueles que estão no mesmo nível, mas abaixo dele na definição. Assim, apesar de B e C estarem no mesmo nível, B é visto de C, mas C não é visto de B, pois B foi declarado antes e, quando B é lido, C não é conhecido ainda. Assim, como quando chegamos em B C não foi declarado ainda, fazer uma chamada a ele de B causará um erro. E as variáveis, como ficam? Onde posso referenciá-las? Variáveis declaradas em: Podem ser referenciadas em: A A, B, C, D, E, F B B, D C C, E, F D D E E F F Viu? As variáveis de um nível são vistas em todos os níveis abaixo. E se, agora, tivermos variáveis de mesmo nome (repare que os tipos podem ser diferentes): PROGRAM P; VAR a : tipo1; PROCEDURE Q1(a : tipo1); PROCEDURE Q2; VAR b : tipo2; PROCEDURE Q3; VAR a : tipo3; BEGIN ... END; {Q3} BEGIN ... END; {Q2} BEGIN ... END;{Q1} PROCEDURE Q4; VAR b : tipo2; PROCEDURE Q5; VAR a : tipo1; BEGIN ... END; {Q5} BEGIN ... END; {Q4} BEGIN ... END. {P} 127 Se eu fizer a:=5 dentro de cada procedimento, qual "a" modifico? Dentro de: Modifico "a" de: Q1 Q1 Q2 Q1 (não há "a" em Q2) Q3 Q3 Q4 P (não há declaracção de "a" em Q4) Q5 Q5 P P Note que, em Q1, "a" é parâmetro. Parâmetros são variáveis locais também. Então, como o compilador, ao encontrar uma referência a uma variável, acha essa variável? ? Primeiro ele olha no procedimento atual (ou programa). ? Se não estiver declarada lá, olha no procedimento (ou programa) onde o procedimento atual foi declarado; dentro de onde o atual está, o procedimento pai deste. ? Se não estiver lá, ele busca no pai do pai do atual, no avô, e assim por diante, até que encontre um procedimento (ou o próprio programa) em que a variável esteja declarada. ? Se ainda assim não achou a declaração, reporta um erro. 128 PROCEDIMENTOS Já vimos parte de procedimentos, agora vamos ver mais a fundo o que eles são e como usá-los. Mas, antes, é bom lembrarmos a principal razão de usarmos procedimentos: evitar código duplicado, tornando os programas menores e mais inteligíveis. Considere o ovo se movendo. Como fazíamos isso? Tinhamos 2 procedimentos: apaga e desenha: PROCEDURE Desenha; PROCEDURE Apaga; BEGIN BEGIN gotoxy(x+1,y); gotoxy(x+1,y); write('*'); write(' '); gotoxy(x,y+1); gotoxy(x,y+1); write('*'); write(' '); gotoxy(x,y+2); gotoxy(x,y+2); write('*'); write(' '); gotoxy(x+1,y+3); gotoxy(x+1,y+3); write('*') write(' ') END; END; São incrivelmente parecidas não? Não haveria um modo melhor de fazer isso? Claro! Note que o que fizemos foi imprimir "*" num procedimento e " " em outro. O que fazemos então? Passamos o caracter a ser impresso para dentro do procedimento. Como? Com parâmetros: PROCEDURE ovo(c : char); BEGIN gotoxy(x+1,y); write(c); gotoxy(x,y+1); write(c,' ',c); gotoxy(x,y+2); write(c,' ',c); gotoxy(x+1,y+3); write(c) END; Agora, se quisermos desenhar o ovo chamamos: ovo('*'); ou: ch := '*'; ovo(ch); Qualquer uma das duas maneiras vale. E para apagar? ovo(' '); 129 ou: ch := ' '; ovo(ch); Fácil, não? Agora você pode estar perguntando: em vez de parâmetro, c podia ser global, não? Sim, mas veja essa regrinha: evite variáveis globais. Por que? Porque em um programa grande você pode se confundir se começar a usar variáveis globais dentro de procedimentos. Use globais somente quando for usá-las no corpo do programa. Se algum procedimento as usar, passe como parâmetro. Então, vamos melhorar nosso procedimento? Como? Arrancando as variáveis globais: PROCEDURE ovo(c : char; x,y : integer); BEGIN gotoxy(x+1,y); write(c); gotoxy(x,y+1); write(c,' ',c); gotoxy(x,y+2); write(c,' ',c); gotoxy(x+1,y+3); write(c) END; Peraí, eu não mudei o código! Mudei sim!. Quando ovo acessa x e y no gotoxy, ele está acessando o x e o y dos parâmetros. Ao declarar um parâmetro com o mesmo nome de uma variável global, impeço o procedimento de enxergar essa global. Ou seja, para ovo, x e y são relativas aos parâmetros, ele não "enxerga" mais as globais x e y. Lembra de nossa discussão sobre variáveis globais e locais? Pois é, parâmetros são um tipo de variáveis locais, ou seja, são vistas somente de dentro do procedimento, deixando invisível (de dentro deste procedimento) qualquer variável global que contenha o mesmo nome de alguma local. quando o procedimento é chamado, o computador cria um espaço para ele, passando os parâmetros, e o executa. Vamos ver a animação do ovo: PROGRAM anima;1. CONST xin = 10; {x inicial} yin = 10; {y inicial} numpos = 20; {número de posições que cairá} 2. VAR cont : integer; {contador do for} PROCEDURE ovo(c : char; x,y : integer); BEGIN gotoxy(x+1,y); write(c); gotoxy(x,y+1); write(c,' ',c); gotoxy(x,y+2); 130 write(c,' ',c); gotoxy(x+1,y+3); write(c) END; BEGIN {desenho o ovo} 3. ovo('*',xin,yin); 4. delay(500); {faço cair numpos posições} 5. FOR cont := yin TO (yin + numpos - 1) DO BEGIN {apago o ovo} 6. ovo(' ',xin,cont); {desenho uma posição abaixo} 7. ovo('*',xin,cont+1); 8. delay(500) END END. Note que, ao passar valores aos parâmetros de ovo, posso dar o valor, como '*', dar uma variável, como xin, ou uma expressão, como cont+1. O que acontece é que o valor de cada um destes ('*', valor de xin e resultado de cont+1) é copiado para cada uma das variáveis dos parâmetros de ovo, em ordem, ou seja, na primeira chamada a ovo, as locais de ovo c, x e y recebem, respectivamente, '*', o valor de xin e o valor de yin. Note também que delay nada mais é que um procedimento com um parâmetro. Atenção! Se você fizer isso: PROCEDURE ovo(c : char; x,y : integer); BEGIN ... c := 'a'; ... END; você mudou o valor da variável local c, ou seja, o que foi passado no parâmetro foi perdido. DIAGRAMAS DE EXECUÇÃO Agora vamos fazer um diagrama de execução para nosso programa do ovo. Um o que? O diagrama de execução mostra o estado das variáveis do programa. Veja o exemplo com o ovo. Primeiro crio um diagrama para o programa, com variáveis e constantes (nesse caso, fiz numpos=2, para ficar mais rápido): 131 Começo a execução do programa e, como na linha 3 chamamos um procedimento, crio novo diagrama para ele, dando valor aos parâmetros: agora executamos ovo, marcando sua saída. Note que nenhum parâmetro foi mudado. na linha 4, o delay não é um procedimento meu, então não represento. Na linha 5 atualizo o contador: na linha 6, chamo novamente o procedimento: nas linhas 7 e 8 desenho novamente o ovo, uma posição abaixo e espero um momento: 132 voltando ao FOR na linha 5: passando novamente pela linha 6: linhas 7 e 8: 133 incrementa o contador e faz o teste do FOR, como passou do limite, sai do FOR, saindo do programa: Isso é um diagrama de execução. Vejamos com outro exemplo: PROGRAM A; VAR x,y : real; PROCEDURE B(z : real); VAR h : real; BEGIN h := z * 5; 134 writeln('B: ',h) END; BEGIN 0. y := 3; 1. x := y + 2; 2. y := x * y; 3. B(y); 4. B(x) END. Vamos executar: crio um diagrama com o nome do programa e suas variáveis (e constatnes se houver). Começo a rodar do BEGIN (linha 0): linha 1: linha 2: linha 3: mudo o valor de h, no procedimento: linha 4: 135 mudo o valor de h, no procedimento: Naturalmente, você deve fazer todos os desenhos num só. As figuras mostram apenas a evolução temporal do diagrama. Para ver mais sobre diagramas consulte as notas sobre passagem de parâmetros. ENTRADA E SAÍDA Já vimos como escrever, com write e writeln, e como ler uma entrada, com read e readln. Mas, como lemos mais de uma entrada por vez, sem usarmos vários reads ou readlns? PROGRAM P; VAR i1,i2:integer; BEGIN read(i1, i2); {lê dois inteiros} END. Só que para que isso funcione, o usuário deve separar os dois inteiros com um ou mais espaços. Veja algumas entradas válidas: 2 3 2 -3 -2 2 -2 -2 etc 136 Da mesma forma, podemos ler outros tipos: PROGRAM P; VAR r1,r2:real; b1,b2:boolean; BEGIN read(r1, r2); {lê dois reais} read(b1, b2); {lê dois booleanos} END. Algumas entradas válidas são: 2.3e22 2.345E2 true false -2.3e22 2.45E-2 false true 0.4 -3.980 false false 0.498e-2 12.3 true true etc Em suma, o read lê de forma diferente os diferentes tipos de entrada (tipo este determinado pela variável que é passada como parâmetro para o read, que receberá a entrada): ? Inteiros: todos os espaços e caracteres de nova linha iniciais são pulados até que se encontre um caracter diferente destes. Se este caracter não for um dígito, ou '+' ou '-', um erro é acusado. Se for dígito, o read vai lendo cada caracter até encontrar um não-dígito, quando o read pára e transforma o monte de dígitos lidos em um inteiro. ? Reais: novamente o read pula espaços e caracteres de nova linha até encontrar um caracter diferente. Esse caracter deve ser '+', '-' ou número, senão um erro é reportado. Então o read vai lendo os dígitos até que encontre um caracter diferente de número, '.', 'e', 'E', 'e+', 'E+', 'e-' ou 'E-', ou seja, as entradas permitidas são do tipo 2, -2, +2, 2.0, +2.0E10, -2.05e-10 etc. ? Booleanos: da mesma forma, o read pula espaços e nova linha até encontrar as palavras 'true' ou 'false seguidas de ' ' ou algum outro delimitador('.', ',' etc). ? Char : o read lê o próximo caracter, sem pular nada. CARACTERES Em Pascal, caracteres são guardados em um tipo de variável especial, declarada assim: 137 VAR ch : char; Aqui, ch é do tipo caracter, o que significa que pode guardar qualquer caracter (mas apenas um), quer seja, letra, número, sinais gráficos etc. Mas, e como armazenamos caracteres nessas variáveis? ch := 'a'; ch := 'A'; ch := ' '; {espaço} ch := '"'; {aspas duplas} Note que 'a' é diferente de 'A', ou seja, há distinção entre caracteres maiúsculos e minúsculos. Um outro ponto interessante é que caracteres são ordenados e seqüenciais: 'A' < 'B' < 'C' < ... < 'Z' 'a' < 'b' < 'c' < ... < 'z' '0' < '1' < '2' < ... < '9' Não confunda 9 com '9', o primeiro é um número e o segundo um caracter. Então podemos fazer: FOR ch:='A' TO 'Z' DO write(ch); e teremos o alfabeto. LENDO E ESCREVENDO CARACTERES Para ler um único caracter, fazemos: read(ch); onde ch é do tipo char. Já para escrever: write(ch); Isso é muito útil na interação com o usuário, por exemplo: ch := 's'; WHILE (ch<>'n') AND (ch<>'N') DO BEGIN 138 faz algo write('quer continuar? (s/n) '); readln(ch) END; ou REPEAT faz algo write('quer continuar? (s/n) '); readln(ch) UNTIL (ch='n') OR (ch='N'); Novamente, como o usuário pode entrar tanto com 'n' quanto com 'N', e como 'n' � 1 �� devo testar os dois. Agora suponha que temos a seguinte entrada: Esta é uma frase . e queremos gerar a saída: Esta é uma frase Ou seja, pegamos uma frase que termina com '.' e separamos as palavras dela. Como faremos? enquanto for diferente de '.' (senão acabou a frase) enquanto for diferente de ' ' (senão ainda é a palavra) leio uma letra escrevo essa letra dou nova linha ou repito enquanto for diferente de ' ' leio uma letra escrevo a letra lida dou nova linha até que encontre um '.' 139 mas isso tem um problema, ' ' será lido e escrito. Quero testar antes de escrever, então ler tem que ser a última coisa a fazer: leio uma letra enquanto for diferente de '.' (senão acabou a frase) enquanto for diferente de ' ' (senão ainda é a palavra) escrevo a letra lida leio uma letra dou nova linha mas isso também tem um problema: repare que, se lemos um ' ', entraremos num laço infinito (confira!). Então, para corrigir, devemos fazer: leio uma letra enquanto for diferente de '.' (senão acabou a frase) enquanto for diferente de ' ' (senão ainda é a palavra) escrevo a letra lida leio uma letra dou nova linha leio uma letra Pronto!Como fica isso em Pascal? read(ch); WHILE ch<>'.' DO BEGIN WHILE ch<>' ' DO BEGIN write(ch); read(ch) END writeln; read(ch) END; Mas, em vez de 2 WHILEs, não poderíamos por tudo em um só? Claro, mas teríamos que mudar o código, se fizermos somente: enquanto for direrente de '.' e diferente de ' ' faça o programa pararia no primeiro espaço, o que não queremos. Então, como fazemos? read(ch); WHILE ch<>'.' DO IF ch<>' ' THEN BEGIN write(ch); read(ch) END ELSE BEGIN {é = ' '} 140 read(ch); writeln END END; Agora, qual o maior problema com esse código? O read exige . Não seria legal o usuário digitar e nós lermos no mesmo isntante, sem precisar do ? Como faríamos isso? Com: ch := readkey; readkey espera que o usuário digite algo e, assim que ele digitar, pega o caracter que ele digitou. Então não preciso do . Legal, não? Mas como nem tudo é uma maravilha, há um detalhe sobre o readkey: como o nome diz, ele lê uma tecla somente. Você não consegue ler integer, real ou palavras com ele, somente char. Outra coisa, enquanto que com read você escreve, vê o que escreveu na tela e então dá enter, com readkey isso não acontece. Ao você digitar algo, readkey lê diretametne a tecla, sem mostrar o que você digitou na tela. Se você quiser que mostre, terá que ecoar o caracter lido, ou seja, escrevê-lo na tela, com write. Então o programa fica: ch := readkey; WHILE ch<>'.' DO BEGIN WHILE ch<>' ' DO BEGIN write(ch); ch := readkey END writeln; ch := readkey END; CARACTERES E A TABELA ASCII Até agora vimos como usar caracteres, mas como será que o computador entende caracteres? Bem, ele não entende. Quando você digita um caracter, o Sistema Operacional se encarrega de traduzí-lo para um código numérico que ele possa manipular mais tarde. Assim, os caracteres nada mais são do que números. Mas como ele faz essa tradução? Para fazer a tradução de caracteres em números, o Sistema Operacional usa uma tabela, chamada ASCII (veja no próximo tópico). Existem vários tipos de tabela, dependendo do computador que é usado e do país em que é usado, apesar disso, ela não muda dos números 0 ao 127. 141 Mas você não precisa decorar essa tabela para brincar com caracteres. O Pascal tem duas funções para lidar com caracteres: VAR n : integer; c : char; BEGIN n := Ord('a'); {n recebe o código ascii de 'a'} c := 'z'; n := Ord(c); {n recebe o código ascii do caracter armazenado em c, ou seja, 'z'} c := Chr(120); {c recebe 'x' (veja tabela abaixo)} c := Chr(Ord('z')); {c recebe 'z'} END. Assim, Ord recebe um caracter e retorna o código ascii deste, e Chr recebe um código ascii e retorna o caracter correspondente a este. Mas para que podemos usar isso? Vejamos alguns exemplos: Exemplo 1 : Uma codificação simples. Suponha que queremos codificar uma determinada frase de 10 letras digitada, para isso, lemos cada caracter e somamos 5 ao seu ascii, resultando num caracter diferente: PROGRAM codifica; VAR n : integer; {ascii de c1} c1 : char; {caracter digitado} c2 : char; {caracter codificado} i : integer; {contador do FOR} BEGIN FOR i:=1 TO 10 DO BEGIN c1 := readkey; {leio o caracter digitado} n := Ord(c1); {pego o ascii do caracter digitado} n := n + 5; {incrementei o código em 5} c2 := Chr(n); {pego o caracter desse novo código} write(c2) {escrevo o caracter codificado} END END. Exemplo 2: Transformar minúsculas em maiúsculas e vice-versa. Nesse caso, o Pascal já tem uma função pré-definida ch1 := Uppercase(ch2); para transformar minúsculas (ch2) em maiúsculas (ch1), apesar disso, não tem uma função para transformar maiúsculas em minúsculas. Então vamos construir a nossa função Para tal, precisamos de algumas informações: 142 ? Tanto as letras maiúsculas quanto as minúsculas são seqüenciais na tabela ascii, ou seja, se 'C' está ná segunda posição após 'A', 'c' estará na segunda posição após 'a' também. ? Dessa forma, na tabela ascii, 'C' - 'A' = 'c' - 'a'. ? Assim, dado um caracter x (não confunda com 'x'), minúsculo, sei que X - 'A' = x - 'a', ou seja, X = x - 'a' + 'A'. Assim, tenho como calcular a maiúscula de x, a partir da minúscula, x. ? Da mesma forma, dado um caracter maiúsculo X, sei que X - 'A' = x - 'a', ou seja, x = X - 'A' + 'a'. Tenho, então, a minúscula a partir da maiúscula. Agora, sim, vamos fazer procedimentos que transformem minha minúscula em maiúscula e vice-versa: PROGRAM transforma; VAR l : char; {a letra a ser transformada} {recebe c por referência, e o substitui pela sua maiúscula} PROCEDURE Maiuscula(VAR c : char); VAR mai : integer; {guarda o ascii da maiúscula} BEGIN IF (c>='a') AND (c<='z') THEN {c é minúscula} BEGIN mai := Ord(c) - Ord('a') + Ord('A'); c := Chr(mai) END {se não for minúscula (ou mesmo não for letra), deixa como está} END; {recebe c por referência, e o substitui pela sua minúscula} PROCEDURE Minuscula(VAR c : char); VAR mai : integer; {guarda o ascii da maiúscula} BEGIN IF (c>='A') AND (c<='Z') THEN {c é maiúscula} BEGIN mai := Ord(c) - Ord('A') + Ord('a'); c := Chr(mai) END {se não for maiúscula (ou mesmo não for letra), deixa como está} END; BEGIN l := 'd'; Minuscula(l); writeln(l); {escreve 'd', não alterou} Maiuscula(l); writeln(l); {escreve 'D', a maiúscula} l := 'P'; Maiuscula(l); writeln(l); {escreve 'P', não alterou} Minuscula(l); writeln(l) {escreve 'p', a minúscula} END. note que o procedimento "Maiuscula" poderia ser tão somente: 143 {recebe c por referência, e o substitui pela maiúscula dela} PROCEDURE Maiuscula(VAR c : char); BEGIN IF (c>='a') AND (c<='z') THEN {c é minúscula} c := Chr(Ord(c) - Ord('a') + Ord('A')) {se não for minúscula (ou mesmo não for letra), deixa como está} END; mas fizemos mais longo para ficar mais legível a operação. TABELA ASCII Essa é a tabela usada em linux (ISO 8859-1): Dec Char Dec Char Dec Char Dec Char -------------------------------------------------------------------- ----- 0 NUL '\0' 32 SPACE 64 @ 96 ` 1 SOH 33 ! 65 A 97 a 2 STX 34 " 66 B 98 b 3 ETX 35 # 67 C 99 c 4 EOT 36 $ 68 D 100 d 5 ENQ 37 % 69 E 101 e 6 ACK 38 & 70 F 102 f 7 BEL '\a' 39 ' 71 G 103 g 8 BS '\b' 40 ( 72 H 104 h 9 HT '\t' 41 ) 73 I 105 i 10 LF '\n' 42 * 74 J 106 j 11 VT '\v' 43 + 75 K 107 k 12 FF '\f' 44 , 76 L 108 l 13 CR '\r' 45 - 77 M 109 m 14 SO 46 . 78 N 110 n 15 SI 47 / 79 O 111 o 16 DLE 48 0 80 P 112 p 17 DC1 49 1 81 Q 113 q 18 DC2 50 2 82 R 114 r 19 DC351 3 83 S 115 144 s 20 DC4 52 4 84 T 116 t 21 NAK 53 5 85 U 117 u 22 SYN 54 6 86 V 118 v 23 ETB 55 7 87 W 119 w 24 CAN 56 8 88 X 120 x 25 EM 57 9 89 Y 121 y 26 SUB 58 : 90 Z 122 z 27 ESC 59 ; 91 [ 123 { 28 FS 60 < 92 \ '\\' 124 | 29 GS 61 = 93 ] 125 } 30 RS 62 > 94 ^ 126 ~ 31 US 63 ? 95 _ 127 DEL Códigos ASCII extendidos: 145 O COMANDO REPEAT Como vimos, o WHILE testa a condição ANTES de executar o laço. Mas, e se quiséssemos testar DEPOIS da execução? Ou seja, se quiséssemos fazer algo pelo menos uma vez e então testar? REPEAT comando1; comando2; ... comandon UNTIL condição; O que esse comando diz? Repita os comandos a seguir até que a condição seja verdadeira. Ou seja, primeiro ele executa os comandos comando1, comando2, ..., comandon para depois testar a condição. Se a condição for falsa, ele executa o laço novamente e testa de novo, e assim por diante. Se a condição for verdadeira, ele sai do laço e segui o programa. Veja bem a diferença entre REPEAT e WHILE: ? WHILE: enquanto a condição for verdadeira, executa os comandos; ? REPEAT: executa os comandos até que a condição seja verdadeira. Ou seja, enquanto que o WHILE pára se a condição for falsa, o REPEAT continua se ela for falsa, ou seja, pára se ela for verdadeira. Outra coisa, notou a ausência de BEGIN e END? No REPEAT não precisa. Não é errado por, mas não precisa. Assim, as duas formas a seguir são redundantes: REPEAT REPEAT comando1; BEGIN comando2; comando1; ... comando2; comandon ... UNTIL condição; comandon END UNTIL condição Note também que não preciso por ";" antes do UNTIL. Ou seja: ? Antes de END e UNTIL não precisa pro ";" ? Antes de ELSE não pode haver ";" Vejamos uma aplicação: lembra do nosso programa anterior com WHILE para achar a média? PROGRAM media; VAR soma, nota : real; cont : integer; 146 BEGIN soma := 0; cont := 0; nota := 0; write('nota: '); readln(nota) WHILE nota <> -1 DO BEGIN soma := soma + nota; cont := cont + 1; write('nota: '); readln(nota) END; IF cont>0 THEN writeln('A média é ',soma/cont) END. Como escreveríamos isso com REPEAT? PROGRAM media; VAR soma, nota : real; cont : integer; BEGIN soma := 0; cont := -1; nota := 0; REPEAT soma := soma + nota; cont := cont + 1; write('nota: '); readln(nota) UNTIL nota = -1; IF cont>0 THEN writeln('A média é ',soma/cont) END. Então, em suma: ? Se quisermos primeiro testar para depois fazer o laço, usamos WHILE; ? Se quisermos fazer o laço pelo menos uma vez e então testar, usamos REPEAT. Meio nebulosa a diferença não? Não é para menos, REPEAT e WHILE são completamente equivalentes, ou seja, você pode escrever um em função do outro. Por exemplo, abaixo vemos, à esquerda, um REPEAT escrito com WHILE (ou seja, o código escrito com WHILE age como se fosse um REPEAT), e à direita temos um WHILE escrito com REPEAT (ou seja, o código escrito com REPEAT age como se fosse um WHILE): REPEAT com WHILE: WHILE com REPEAT: f := true; REPEAT WHILE f DO BEGIN IF condição THEN BEGIN comando1; comando1; comando2; comando2; ... ... 147 comandon; comandon f := NOT condição END END; UNTIL NOT condição; Quando você usa um ou outro na prática? Vai do gosto do freguês. 148 O COMANDO WHILE Lembra nosso programa de chute? Nós dávamos 10 chances ao usuário e, então saíamos. O que acontece se quiséssemos dar infinitas chances? Não poderíamos usar o FOR. Iríamos querer algo assim: enquanto o usuário não acertar peça novo chute Como fica isso em Pascal? PROGRAM chute; CONST num = 5; VAR nao_acertou : boolean; chute : integer; BEGIN nao_acertou := true; WHILE nao_acertou DO BEGIN write('seu chute: '); readln(chute); IF chute = num THEN BEGIN writeln('acertou!'); nao_acertou := false END ELSE IF chute > num THEN writeln('é >') ELSE writeln('é <') END {while} END. Então, em termos gerais: WHILE condição DO comando; A condição é como no IF, podendo ter AND, OR e NOT Como o WHILE funciona? Se condição for verdadeira, ele executa o comando (ou grupo de comandos). Aí, ao final, ele testa novamente a condição e, se ela continuar verdadeira, executa comando novamente. Faz isso até que condição seja falsa. Ou seja, faz exatamente o que diz: "Enquanto condição faça comando". Um lembrete: sempre inicialize as variáveis da condição. Se a condição for falsa antes do WHILE, ele nem será executado: 149 continua := false; WHILE continua DO write('nunca será executado'); Com isso vemos que não precisamos mais do coxambre horrível do FOR. Aliás, um FOR pode ser feito com WHILE, veja abaixo: cont := 1; FOR cont:=1 TO N DO WHILE cont<=N DO comando; BEGIN comando; cont := cont + 1 END; Use sempre WHILE quandovocê precisar sair do laço a qualquer momento, como no exemplo do programa onde o usuário adivinhava o número. Considere o programa abaixo: n := 1; WHILE n <> 10 DO BEGIN write(n); n := n+2 END; Quando esse programa pára? Nunca! Pois n nunca é 10. Ele imprime os ímpares ad infinitum. Se quiséssemos imprimir os 5 primeiros ímpares deveríamos fazer: n := 1; WHILE n < 10 DO BEGIN write(n); n := n+2 END; Nunca use <> quando < ou > bastam. Considere agora esse fragmento: x := 5; WHILE x > 0 DO BEGIN x := x - 1; write(10/x) END; Qual o erro deste? O erro é que antes subtraímos, daí escrevemos, para depois testar. Assim, quando x=1, faço x:=x-1 => x=0 e divido 10 por 0 => erro! Como conserto isso? O modo mais direto seria: 150 x := 5; WHILE x > 0 DO BEGIN x := x - 1; IF x>0 THEN write(10/x) END; Mas isso não parece legal, pois estamos fazendo duas vezes o mesmo teste. Tem como melhorar? Tem: x := 4; WHILE x > 0 DO BEGIN write(10/x); x := x - 1 END; Agora garanto que ao decrementar x, testo se ele é maior que zero. Assim, é bom seguir essa receita com laços WHILE: gero ou leio o primeiro valor WHILE há outros valores processo o valor gero ou leio o próximo Dessa forma garantimos que só valores testados são processados. Como foi visto no IF, a condição pode ser qualquer expressão, variável ou valor booleano. Assim, o seguinte programa nunca pára: WHILE true DO writeln('não pára!'); pois a condição é sempre verdadeira. Além disso, operações como AND, OR e NOT são permitidas. Em suma: o laço WHILE testa a condição e, se esta for verdadeira, então executa seu corpo (o que está entre BEGIN e END, ou o comando que segui o DO, snão não houverBEGIN e END). Após executar o corpo, ele testa novamente a condição, executando novamente o corpo se esta for verdadeira. Assim ele seguie até que a condição se torne falsa, quando ele "pula" o corpo e o programa passa ao comando seguinte ao WHILE. Como exemplo, façamos um programa que tire a média de n notas do usuário (n desconhecido). Se o usuário entrar -1 ele sai do programa: PROGRAM media; VAR soma, nota : real; cont : integer; BEGIN 151 soma := 0; cont := 0; nota := 0; WHILE nota <> -1 DO BEGIN write('nota: '); readln(nota); soma := soma + nota; cont := cont + 1 END; writeln('A média é ',soma/cont) END. Que erros temos aí? Dois grandes! O primeiro é que, quando o usuário entra com -1, ele é somado. Então temos que deixar o read para o final (para deposi da soma): PROGRAM media; VAR soma, nota : real; cont : integer; BEGIN soma := 0; cont := -1; {se 0 conta um a mais, verifique} nota := 0; WHILE nota <> -1 DO BEGIN soma := soma + nota; cont := cont + 1; write('nota: '); readln(nota) END; writeln('A média é ',soma/cont) END. Mas ainda dá para melhorar! Notou que da primeira vez, soma=0, nota=0 e faço soma := soma+nota? Posso evitar essa soma desnecessária assim: PROGRAM media; VAR soma, nota : real; cont : integer; BEGIN soma := 0; cont := 0; nota := 0; write('nota: '); readln(nota) WHILE nota <> -1 DO BEGIN soma := soma + nota; cont := cont + 1; write('nota: '); readln(nota) END; writeln('A média é ',soma/cont) END. 152 Agora sim... o -1 não entra na soma nem faço somas desnecessárias. E qual era o segundo erro? E se da primeira vez o usuário puser de cara um -1? Aí soma=0, cont=0 e soma/cont=0/0 = erro! Então temos que por um teste no final: PROGRAM media; VAR soma, nota : real; cont : integer; BEGIN soma := 0; cont := 0; nota := 0; write('nota: '); readln(nota) WHILE nota <> -1 DO BEGIN soma := soma + nota; cont := cont + 1; write('nota: '); readln(nota) END; IF cont>0 THEN writeln('A média é ',soma/cont) END. Pronto! 153 TIPO BOOLEAN Variáveis booleanas são variáveis capazes de conter apenas 1 de 2 valores: verdadeiro ou falso. Como as condições no IF retornam verdadeiro ou falso são chamadas booleanas. Note que, em x > 2, x não é booleano, mas o resultado da expressão é. Como defino uma variável booleana? VAR v : boolean; E como abasteço um valor nessa variável? Assim, a forma geral do comando IF é: PROGRAM abastece; var b1,b2 : boolean; a,b,c : integer; BEGIN a := 2; b := 3; c := 1; b1 := true; b2 := false; b2 := a > b; {aqui b2 é "false", pois a é menor que b} b1 := (a>b) AND (b>c); {b1 recebeu false, confira} b2 := (a>b) OR NOT (b>c); etc END. Opa! Quem é esse NOT? É a negação! NOT true = false NOT false = true Vejamos um exemplo: suponha que queremos que b1 seja verdadeira se b2 for falsa e vice-versa. Como farei? Um modo horrendo é: IF b2 THEN b1 := false ELSE b1 := true; Mas isso pode, e DEVE, ser substituído por: b1 := NOT b2; O efeito é o mesmo, confira, e o programa fica menor. 154 Note também que não fiz: IF b2 = true THEN b1 := false ELSE b1 := true; Por que? Porque é redundante. Quando o IF acima será executado? Quando o resultado de (b2 = true) for verdadeiro, ou seja, true. E, para que isso ocorra, que valor b2 deve ter? true. Então b2 = true equivale a simplesmente ler o valor de b2. Mas e se quiséssemos que algo fosse executado se uma variável for false? Abaixo você vê as duas maneiras, a segunda é preferível: modo 1: IF b1=false THEN faz algo; modo 2: IF NOT b1 THEN faz algo Agora confira e veja que as duas formas agem da mesma maneira: se a condição for verdadeira, faz e, para a condição ser verdadeira, ou b1 é false ou NOT b1 é verdadeiro => b1 é false. OPERADORES BOOLEANOS AND, OR e NOT sao operadores booleanos, porque agem em expressoes ou variaveis booleanas. Suponha que temos duas variaveis booleanas a e b, a seguir temos uma tabela com seus valores e os resultados das operaçoes: a b a AND b a OR b NOT a NOT b True True True True False False True False False True False True False True False True True False False False False False True True Ou seja: AND True False True True False False False False OR True False True True True False True False NOT True False False True Essas tabelas sao chamadas de tabelas verdade. 155 Lembra do nosso programa de chute? Suponha que, ao final do programa queiramos dizer que acabaram-se as chances do usuario, mas queremos dizer isso somente se ele errou todas as vezes. Vomo fazemos? Veja abaixo: PROGRAM chute; CONST num = 5; {valor a ser adivinhado} VAR n : integer; {o chute do usuário} c : integer; {contador do for} acertou : boolean; {indica se o usuario acertou} BEGIN writeln('Você tem 10 chances'); acertou := false; FOR c := 1 TO 10 DO BEGIN write('seu chute: '); readln(n); IF n = valor THEN BEGIN writeln('acertou"'); acertou := true; c := 10 END; if n > valor then writeln('seu palpite foi maior'); if n < valor then writeln('seu palpite foi menor') END; IF NOT acertou THEN writeln('suas chances acabaram'); END. Por fim, lembre que as únicas operações permitidas em booleanos são AND, OR e NOT. +, - etc não funcionam porque não tem lógica, afinal, quanto é true + 1? E true + false? TIPO REAL As mesmas operações permitidas com integer podem ser usadas com real. Vale lembrar que "-" pode indicar subtração de dois valores ou indicar que o valor é negativo: x := 2.5 - 1.43; x := -3.12; Agora vamos ver uma aplicação: vamos calcular quanto tempo passa, pela relatividade, em uma nave se movendo a uma velocidade v, enquanto que na Terra passaram n horas. A fórmula é: Tn = TT ¥�1 - v²/c²) onde Tn é o tempo na nave, TT é o tempo na Terra e c é a velocidade da luz no vácuo. 156 Mas, peraí! Como calculo o quadrado e a raíz? x := sqr(y); x recebe y² x := sqrt(y); x recebe lembre-se de que x deve ser real. Naturalmente, x := y*y; também funciona. Então o programa fica: PROGRAM relat; CONST c = 300000; {km/s} VAR tt : real; {tempo na terra (s)} tn : real; {tempo na nave (s)} v : real; {velocidade da nave (km/s)} BEGIN write('vel (km/s): ); readln(v); tt := 60; {1 minuto} tn := tt * sqrt(1 - (sqr(v) / sqr(c))); writeln('t na nave é: ',tn:4:2,' s, t na terra é ',tt:4:2,' s') END. CONVERSÃO DE TIPOS Como fazemos para converter entre real e integer? Para isso, pascal tem as seguintes regras: ? Expressões integer são automaticamente convertidas para real. ? Expressões real devem ser convertidas explicitamente para integer. Então, o seguinte vale: VAR a : integer; b : real; BEGIN b := a; b := a/2; etc END. Mas como convertemos de real para integer? Depende do que você quer que seja feito com o número. Podemos: 157 ? arredondar: por exemplo, de 4.5 para 5; de 4.7 para 5; de 4.3 para 4. ? truncar: de 4.5 para 4; de 4.7 para 4; de 4.3 para 4. Ou seja, ou arredondo, ou trunco. No caso do último, só a parte inteira é considerada. Se quisermos truncar: x := trunc(4.7); x terá 4 Se quisermos arredondar: x := round(4.7); x terá 5 A forma geral das funções é: Trunc(x : real) : longint Round(x : real): longint o que significa que trunc e round recebem um valor real edevolvem um longint. Mas, e se o número for negativo? Nada muda: Trunc(-expressão) = - Trunc(expressão) Round(-expressão) = - Round(expressão) Naturalmente, qualquer expressão real pode ser usada dentro de trunc ou round. Vejamos alguns exemplos: Valor Trunc(valor) Round(valor) 5.0 5 5 4.5 4 5 4.99999 4 5 4.49999 4 4 4.2 4 4 4.7 4 5 -5.0 -5 -5 -4.5 -4 -5 -4.2 -4 -4 -4.7 -4 -5 158 O COMANDO IF Suponha que queremos um programa onde o usuário adivinha um número que definimos. Como fazemos isso? 1. Pedimos o número ao usuário. 2. Se ele adivinhou, aviso. Como eu faço a segunda parte? Para ele ter adivinhado, o número que o usuário deu deve ser igual ao número que definimos. Suponha que este número pré-definido seja 5. Então a parte 2 fica: 1. Pedimos o número n ao usuário. 2. Se n = 5 então digo que ele acertou. E como faço isso em Pascal? PROGRAM adivinha; CONST valor = 5; VAR n : integer; {o chute do usuário} BEGIN write('seu chute: '); readln(n); IF n = 5 THEN write('acertou!') END. note que é n = 5 e não n := 5. Assim, a forma geral do comando IF é: IF condição THEN comando; e, da mesma forma que o FOR: IF condição THEN BEGIN comando1; comando2; ... comandon END; Esse comando diz que se a condição for verdadeira, então o comando (ou grupo de comandos entre BEGIN e END) que vem após o THEN ser'executado. Se não for verdadeira, ele não será executado. 159 CONDIÇÕES Mas, afinal, que tipo de condições podemos usar no IF? Qualquer tipo de comparação envolvendo constantes, variáveis, valores ou expressões: Condição Significado Exemplo expressão1 = expressão2 igual 5 = 6 é falso expressão1 < expressão2 menor 5 < 6 é verdadeiro expressão1 <= expressão2 menor ou igual 5 <= 6 é verdadeiro expressão1 > expressão2 maior 5 > 6 é falso expressão1 >= expressão2 maior ou igual 5 >= 6 é falso expressão1 <> expressão2 diferente 5 <> 6 é verdadeiro Vamos melhorar nosso exemplo de adivinhar números. Agora ele compara os números e, se o usuário adivinhou, pára o FOR. Ele dá também dicas ao usuário se o chute é maior ou menor e 10 chances, no máximo, para acertar: PROGRAM adivinha; CONST valor = 5; VAR n : integer; {o chute do usuário} c : integer; {contador do for} BEGIN writeln('Você tem 10 chances'); FOR c := 1 TO 10 DO BEGIN write('seu chute: '); readln(n); IF n = valor THEN BEGIN writeln('acertou"'); c := 10 END; if n > valor then writeln('seu palpite foi maior'); if n < valor then writeln('seu palpite foi menor') END END. Viu como fizemos para sair do FOR? Enganamos a máquina, fazendo ela achar que esta era a iteração de número 10 e, portanto, a última. Atenção! E s s e m o d o d e s a i r d o F O R é H O R R E N D O e e x t r e m a m e n t e DESADONSELHÁVEL. Além disso, NÃO se pode garantir que o compilador que você usa irá fazer isso mesmo. Em Free Pascal isso funciona. Mas, então, por que está sendo apresentado? Mais adiante você verá comandos que interrompem o ciclo quando uma 160 determinada condição for satisfeita. No momento, essa "interrupção" terrível do FOR pode servir para mostrar a você como o FOR funciona e qual a importância de seu contador, sua variável de controle. Por isso vale lembrar... NUNCA FAÇA ISSO! Mas esse programa não está bem escrito. Veremos agora por quê. ELSE Suponha agora que queremos dizer que o usuário errou ou acertou. Ou seja, queremos tomar a seguinte decisão: Se o usuário acertou, escrevo "acertou!" Senão escrevo "errou" Como faço isso em Pascal? IF n = 5 THEN writeln('acertou!') ELSE writeln('errou!'); Notou a falta do ";" após o writeln? Antes do ELSE NUNCA bote ";". Se puser, o compilador reclamará. Então, de um modo geral, o IF fica: IF condição THEN comando1 ELSE comando2; e, nesse caso, se a condição for verdadeira, então comando1 é executado. Se não for verdadeira, o comando2 é executado. Ou seja, se condição for verdadeira, o que vier depois do THEN é executado e o ELSE é pulado. Se for falsa, o que vem após o THEN é pulado e o ELSE é executado. Da mesma forma que antes, em vez de um comando podemos ter blocos de comandos: IF condição THEN BEGIN comando1; ... comandon END { --> note a falta do ";" } ELSE BEGIN comandok; ... comandoj END; Vamos, agora, reescrever nosso programa anterior: 161 PROGRAM adivinha; CONST valor = 5; VAR n : integer; {o chute do usuário} c : integer; {contador do for} BEGIN writeln('Você tem 10 chances'); FOR c := 1 TO 10 DO BEGIN write('seu chute: '); readln(n); IF n = valor THEN BEGIN writeln('acertou"'); c := 10 END ELSE {n <> valor} IF n > valor THEN writeln('seu palpite foi maior') ELSE { n < valor } writeln('seu palpite foi menor') END END. Viu como sumimos com uma comparação? Do modo modo como o programa estava, quando o usuário tinha acertado, ainda assim a máquina comparava para ver se era > e para ver se era <, uma perda de tempo. Agora, se for =, a parte do ELSE não é executada. Da mesma forma, antes, se era >, ainda assim fazia o teste para <, que daria falso. Agora esse teste não é feito. Além desses ganhos, ganhamos com a lógica. Por que o terceiro if sumiu? Porque era desnecessário, veja: ? inicialmente, o palpite poderia ser qualquer coisa. ? se for = acerto, então não faço mais testes ? senão, já tenho uma informação, é diferente. ? ao fazer o segundo IF, sei que é diferente, pois o IF está dentro do ELSE do IF que testou se era igual. ? se for maior, então o IF é executado e pula o ELSE ? senão, tenho duas informações: não é igual nem maior, logo, é menor. Por isso não preciso nem testar. Mas há um problema com o IF. Há uma ambigüidade que surge quando usamos IFs aninhados. Por exemplo: IF condição1 THEN IF condição2 THEN comando1 ELSE comando2; 162 Quando comando2 será executado? Ou seja, o ELSE é de qual IF? Não deixe a edentação errada te enganar! Nesse caso, o computador casa o ELSE sempre com o último IF que não esteja impedido. Assim, comando2 será executado se condição1 for verdadeira e condição2 for falsa; e comando1 se ambas as condições forem verdadeiras. E se fizermos assim: IF condição1 THEN BEGIN IF condição2 THEN comando1 END ELSE comando2; Nesse caso, o ELSE é do primeiro IF, pois isolamos o segundo dentro do corpo do primeiro, impedindo o segundo IF. Então comando2 é executado somente se condição1 for falsa. No código seguinte: IF condição1 THEN IF condição2 THEN comando1 ELSE comando2 ELSE comando3; comando1 é executado se condição1 e condição2 forem verdadeiras, comando2 se condição1 for verdadeira e condição2 for falsa, e comando3 se condição1 for falsa. Note como a edentação deixa realmente o código mais legível. AND Suponha que queremos executar um comando somente quando duas condições, C1 e C2, forem verdadeiras. Queremos algo como: se C1 e C2 forem verdadeiras então faça comando Como fazemos isso em Pascal? IF (C1) AND (C2) THEN comando; Note os parênteses em volta das condições. Eles precisam estar aí. OR 163 Suponha agora que queremos executar um comando quando uma de duas condições (ou ambas) forem verdadeiras. Então queremos algo assim: se C1 ou C2 forem verdadeiras, então faça o comando Em Pascal isso é: IF (C1) OR (C2) THEN comando; note novamenteos parênteses. 164 ANIMAÇÃO BÁSICA O que o programa abaixo faz? BEGIN FOR i:=1 to 20 do write('*'); writeln END. Escreve 20 '*' certo? E como você vê a saída? ******************** Como o computador escreveu? * ** *** ... ******************** E por que você não viu isso? Porque foi muito rápido! Agora considere o programa: FOR c:=1 TO 20 DO BEGIN write('*'); atrasa END; writeln; se "atrasa" for suficientemente longo, então veremos a animação, que corresponde a uma barra de * que cresce da esquerda para a direita. Como fazemos o atraso? Uma maneira horrível pode ser: PROCEDURE atrasa; VAR a,b,c : integer {contadores} BEGIN FOR a:=1 TO 1000 DO FOR b:=1 to 10000 DO FOR c:=1 TO 10000 DO {nada}; END; Isso vai ficar contando até 10¹². É um atraso. 165 Mas tem que haver um meio mais inteligente de gerar esse atraso. E tem! Em pascal existe o comando delay(t), que gera um atraso de t milissegundos. Para que delay funcione, não esqueça de incluir "USES crt;" logo abaixo da cláusula "PROGRAM". Então nosso programa pode ser: FOR c:=1 TO 20 DO BEGIN write('*'); delay(500) END; Mas isso ainda é limitado, não? Afinal, a tela não é só uma linha. Ela tem altura e largura, e poderíamos querer por um '*' em algum lugar pré-definido dela. Como faríamos então? Antes de mais nada, temos que considerar a tela como uma grande matriz de n linhas por m colunas. Um grande quadriculado. Então a tela é: 0 1 2 ... m 0 1 2 ... n onde n e m dependem do tamanho do monitor. Note que o (0,0) é no canto superior esquerdo. Como fazemos então para por um '*' na posição (2,3) -> linha 3, coluna 2? Usando gotoxy: BEGIN gotoxy(2,3); write('*') END. Assim, gotoxy posiciona o cursor na coordenada (x,y) = (2,3) e write escreve la´um '*'. Fácil não? Novamente, não esqueça de incluir "USES crt;". Agora, como fazermos para fazer com que um '*' ande das coordenadas (2,2) até (20,2), ou seja, que ande 18 posições na linha 2? Um algoritmo para isso seria: desenho um * na posição pos espero para pos de 2 a 20 faça: apago o * em pos desenho um * em pos+1 espero 166 E como apago o '*'? Simples, desenho um espaço em branco em cima dele. Ou seja, pinto na mesma cor da tela. Então o programa é: gotoxy(2,2); write('*'); delay(500); FOR x := 2 TO 19 DO {19 porque a última linha irá escrever no 20} BEGIN gotoxy(x,2); write(' '); gotoxy(x+1,2); write('*'); delay(500) END; Você vai ver o '*' passar de x:=2 a 20. Peraí. 20? Então por que no FOR tem um 19? Porque o último write será posto em x+1 e, se x for 19, esse último '*' irá em x = 20. Agora, para finalisar esse pequeno desvio que fizemos, vamos derrubar um ovo. Vamos desenhar algo que, ainda que vagamente, lembre um ovo e fazê-lo cair 20 posições a partir de uma posição inicial constante. Que algoritmo usamos? o mesmo: desenho o ovo espero para y = yinicial até yinicial + 20 apago o ovo desenho o ovo em y+1 espero E como vamos desenhar o ovo? Bom, com o que sabemos até agora podemos criar um procedimento para desenhar o ovo e um para apaga. E como saberemos onde desenhar? É só fazer com que os procedimento leiam a posição inicial do ovo em variáveis globais. Lembre-se: há um modo melhor, mais inteligente e mais seguro de fazer isso, mas só veremos mais tarde. Então vamos lá: PROGRAM queda; USES crt; CONST xin = 10; {o x inicial} yin = 2; {o y inicial} VAR x,y : integer; {coordenadas} c : integer; {contador do for} PROCEDURE desenha_ovo; BEGIN gotoxy(x+1,y); write('*'); gotoxy(x,y+1); write('* *'); gotoxy(x,y+2); write('* *'); 167 gotoxy(x+1,y+3); write('*') END; PROCEDURE apaga_ovo; BEGIN gotoxy(x+1,y); write(' '); gotoxy(x,y+1); write(' '); gotoxy(x,y+2); write(' '); gotoxy(x+1,y+3); write(' ') END; BEGIN x := xin; {inicializo a coordenada} y := yin; {inicializo a coordenada} desenha_ovo; delay(500); FOR c := yin TO (yin+20) DO BEGIN apaga_ovo; y := y+1; {coordenada onde desenho o próximo ovo, x é o mesmo} desenha_ovo; delay(500) END END. O que "desenha_ovo" faz? Desenha um ovo na posição x,y (veja abaixo): x x+1 x+2 y * y+1 * * y+2 * * y+3 * E "apaga_ovo"? Desenha o mesmo ovo, só que com espaços em vez de *. O corpo do programa faz, então, a animação. Obs: Notou a falta de ";" no último write dos procedimentos, no delay do FOR e no END do FOR? Isso funciona porque antes de um END o ";" não é necessário. Tudo funciona se você colocar, mas não é necessário. Pronto. Estamos derrubando o ovo. Mas note uma coisa: a velocidade de queda é constante. O tempo entre cada desenhar do ovo é constante, ou seja, se o ovo cai 20 posições, o fará em tempo 20 × t (tempo do atraso), a uma velocidade de 1 pos/t. Mas, e se quisermos incluir uma aceleração? Basta variarmos o atraso a um passo constante. Por exemplo: Passo Atraso 168 1 0.5 2 0.4 3 0.3 4 0.2 O programa leva cada vez menos tempo para percorrer a mesma distância de um passo, ou seja, a velocidade está aumentando. Como a redução do tempo se dá em um passo constante, a velocidade aumenta em um passo constante também, ou seja, a aceleração é constante. Então, se quisermos incluir uma gravidade em nossa queda, teríamos que fazer algo assim: desenha_ovo; delay(60 * g); {600 para g = 10} FOR c := yin TO (yin+20) DO BEGIN apaga_ovo; y := y+1; {coordenada onde desenho o próximo ovo, x é o mesmo} desenha_ovo; delay((60 - c) * g) {reduz o tempo de g em g} END Obs: Lembre-se de que delay aceita somente valores inteiros positivos. ENTRADA Como fazemos se quisermos que o usuário nos dê uma informação? Por exemplo, se quisermos calcular a média de um aluno, como fizemos até hoje? Bom, até hoje, modificávamos o programa, abastecendo, para cada aluno, as notas, compilando e executando o programa. Bastante besta não? O que gostaríamos de fazer é escrever o programa uma só vez e então executá-lo e deixar que ele peça as notas. Como fazemos isso? Com os comandos read e readln. Considere o programa: PROGRAM teste_read; VAR n : real; BEGIN write('digite um número: '); readln(n); writeln('você digitou ',n:3:2) END. Saída: 169 digite um número: 5 você digitou 5.00 E se trocarmos readln por read? PROGRAM teste_read; VAR n : real; BEGIN write('digite um número: '); read(n); writeln('você digitou ',n:3:2) END. Saída: digite um número: 5você digitou 5.00 Viu a diferença? Então: ? read lê a entrada e posiciona o cursor após o final desta. ? readln lê a entrada e dá uma nova linha, ou seja, posiciona o cursor na linha de baixo. Assim como o writeln, você pode usar "readln;" se quiser apenas uma resposta, mas não armazená-la, assim, o seguinte programa: PROGRAM p; BEGIN readln END. não terminará até que "enter" seja pressionado, aí o sistema dá uma nova linha e sai. Se usarmos "read;" em vez de "readln;", a nova linha não será dada. Cabe a você decidir quando usar cada um. Então, basicamente: read(n) e readln(n) lêem uma entrada que o usuário digitou e, após ele dar "enter" o comando guarda essa entrada em n. Naturalmente, se o tipo de entrada dada for diferente do tipo de n, o programa gerará um erro. Por exemplo, se n for integer e o usuário digitar "3.5", na horaque o read for ler dará erro. Tem como evitar esse erro, mas é assunto para um curso mais avançado. Vamos, então, escrever o programa de notas: PROGRAM notas; 170 VAR p1,p2,p3 : real; {notas das provas} m : real; {média das provas} BEGIN write('nota p1: '); readln(p1); write('nota p2: '); readln(p2); write('nota p3: '); readln(p3); m := (2*p1 + 3*p2 + 5*p3) / 10; writeln('a média é ',m:4:2) END. Nesse caso, queremos somente 3 dados. Mas, e se quisermos usar um número de dados que não é conhecido quando escrevemos o programa? Por exemplo, suponha que queremos achar a média aritmética simples de n dados, onde n é uma entrada do usuário. Considere o programa: PROGRAM notas; VAR n : integer; {número de dados} num : real; {número digitado} media : real; {média dos dados} cont : integer; {contador do for} soma : real; {soma dos dados} BEGIN write('Quantos dados serão? '); readln(n); soma := 0; FOR cont := 1 TO n DO BEGIN write('entre um dado: '); readln(num); soma := soma + num END; media := soma / n; writeln('a média é ',media:4:2) END. Percebeu o que aconteceu? O usuário disse quantos números ía dar e depois entrou cada um dos números. O oprograma, então, somava-os à medida que estes eram dados pelo usuário e, no final, apresentava a média. Ou seja, o que o programa fez foi calcular media = (di) /n n onde n e cada di foi dado pelo usuário quando da execução do programa. 171 CONSTANTES Suponha que queremos que uma determinada variável tenha sempre o mesmo valor no programa, sem que possamos modificar esse valor. Mas então não queremos uma variável, e sim uma constante. Como defino uma constante em Pascal? Quase como uma variável: PROGRAM nome; CONST cte1 = valor1; cte2 = valor2; VAR v1 : tipo1; v2 : tipo2; PROCEDURE ... BEGIN ... END. Assim, por exemplo, se faço CONST l = 3.5;, l terá sempre esse valor no programa, não podendo ser modificada, ou seja, se eu fizer l:=2; em algum lugar do programa, um erro será dado. Mas afinal, por que usar uma constante em vez de por diretamente seu valor no programa? Ou seja, por que fazer CONST cte = 10; e não por 10 em todo lugar que precisar no programa? Suponha que usamos esse valor 50 vezes no programa. Agora suponha que precisamos rodar esse programa mas com um valor diferente. Temos que fazer 50 substituições. Se tivéssemos usado a constante, só precisaríamos substituir o valor na linha CONST. De resto, uma constante pode ser usada como usaríamos um valor qualquer. Vamos ver agora um exemplo: Fatorial Vamos calcular o fatorial de um número dado pelo usuário. Como faremos? ? leio o número, n ? faço 1 × 2 × 3 × ... × n E como fica isso em Pascal? PROGRAM fatorial; VAR n : integer; {o número cujo fatorial é calculado} c : integer; {contador para o for} r : integer; {resultado do fatorial} BEGIN 172 write('número: '); readln(n); r := 1; {inicializo r} {calculo o fatorial de n} FOR c := 1 TO n DO r := r * c; writeln('resultado: ',r) END. O que esse programa fez, foi n! = n × (n-1)!, onde o (n-1)! era guardado em r a cada iteração. De fato, para calcular 5!, calculamos os 5 primeiros fatoriais. Mas, e se n = 0? Nesse caso o laço FOR não será executado. E qual o valor de r? 1, exatamente 0!.