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!.