Baixe o app para aproveitar ainda mais
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...............................................................................................................................162 OR ..................................................................................................................................162ANIMAÇÃ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 maiusculas e 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 escreverna 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 desenheum 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óriaapontada 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ço q 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 quequeremos 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 fila comum. 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; {eliminop} 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 arquivo que 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,
Compartilhar