Algoritmos e CLP: Guia Completo
Algoritmos e CLP: Guia Completo
Data: Dezembro/2018
Sumário
Algoritmos................................................................................................................................. 3
1.1 Formas de Representação de um Algoritmo ........................................................... 4
1.2 Execução de Algoritmos de Pseudocódigos ............................................................. 7
1.3 Estruturas de Controle............................................................................................ 20
1.4 Variáveis Indexadas ................................................................................................ 31
1.5 Vetores Dinâmicos .................................................................................................. 34
1.6 Modularização ........................................................................................................ 35
1.7 Recursividade.......................................................................................................... 38
1.8 Caderno de Exercícios ............................................................................................. 39
Algoritmos
Nota de Curiosidade:
É um dispositivo imaginário que formou a estrutura para fundamentar a ciência da
computação moderna. Seu inventor, o matemático Alan Mathison Turing, mostrou que
a computação das operações de leitura, escrita e exclusão de símbolos binários
poderiam ser satisfeitas por uma máquina que continha uma fita de comprimento
ilimitado, com quadrados de tamanho definido sobre ela e um dispositivo com um
número finito de estados, que realizava as operações na fita.
Em 1936 foi formalizado o termo algoritmo: um conjunto finito de instruções simples e
precisas, que são descritas com um número finito de símbolos.
“Qualquer processo aceito por nós homens como um algoritmo é precisamente o que
uma máquina de Turing pode fazer” (Alonzo Church, matemático).
O Procedimento de execução de um algoritmo, envolve o processamento dos dados a
partir da leitura de uma fonte de entrada que após ser processada conforme a lógica
contida nos passos do algoritmo, retorna um valor em uma saída com o resultado do
processamento, sendo geralmente este processamento realizado com o auxilio de uma
ou mais estrtura de dados.
a) Através de uma língua (português, inglês, etc.): forma utilizada nos manuais de
instruções, nas receitas culinárias, bulas de medicamentos, etc.;
b) Através de uma linguagem de programação (Pascal, C, Delphi, Java, SCL, C#,
etc.): Esta é a forma utilizada geralmente por programadores experientes, que pulam a
etapa de desenvolvimento de um algoritmo formal antes de iniciar sua programação;
c) Através de representações gráficas: são muito recomendáveis, já que a
ilustração gráfica do problema, muitas vezes substitui várias palavras e torna a
assimilação mais fácil, principalmente para os programadores iniciantes.
Neste caso vamos falar um pouco mais sobre algumas formas de representação de
algoritmos:
Desvantagens:
• Fluxogramas detalhados podem obscurecer a estrutura do programa.
O detalhamento através de textos dentro da estrutura gráfica causa confusão visual,
obscurecendo a estrutura principal do programa.
Decisões
O Pseudocódigo será abordado de maneira mais profunda nesse material, por ser a
base para a programação em linguagens não gráficas, a qual pertence a S7-SCL,
Schneider ST (Structured Text ST), Rockwell Structured Text, Beckhoff Structured Text,
etc.
//bloco de instruções
instrução 1
instrução 2
instrução 3
Um mesmo algoritmo pode conter diversos blocos seriados ou blocos dentro de outros
blocos. Se um bloco está dentro de outro dizemos que há aninhamento de blocos. O
aninhamento é construído por indentação, ou seja, colocando-se recuos no texto-
código para produzir uma hierarquia. Veja o exemplo esquemático:
//bloco 1
instrução 1-1
instrução 1-2
// bloco 2 indentado
instrução 2-1
instrução 2-2
instrução 2-3
instrução 1-3
Neste exemplo o bloco 2 é parte integrante do bloco 1, mas tem significado lógico
independente dentro deste bloco e desta forma é indentado.
Vários níveis de indentação são permitidos num mesmo pseudocódigo.
O par de barras //, utilizado nos exemplos esquemáticos anteriores, marca a linha como
comentário de forma que a mensagem que ele precede possui apenas valor informativo.
Executar um algoritmo significa executar sequencialmente cada uma das linhas que
formam o bloco principal onde pode haver dezenas ou mesmo centenas de linhas. Não
necessariamente todas as linhas serão processadas.
Poderão ocorrer as seguintes possibilidades:
• Algumas linhas poderão ser saltadas;
• Um grupo de linhas poderá ser diversas vezes repetido;
• Um comando ou módulo poderá ser chamado principiando o processamento de
linhas de outro bloco processamento;
• Execução é encerrada porque a última linha foi executada ou porque foi suspenso
explicitamente.
instrução 1
instrução 2
pare
instrução 3
instrução 4
Entrada e Saída
Dispositivo E/S
Entrada Padrão
leia x
Saída Padrão
A sintaxe que evoca o envio de dados para a saída padrão é construída aqui com a
palavra reservada, escreva, como no exemplo:
Este exemplo ilustra o uso mais simples de escreva: a impressão de mensagens. Uma
mensagem é simplesmente um agregado de caracteres entre aspas simples. Uma
mensagem aparecerá na saída padrão tal como foi escrita no pseudocódigo.
Variáveis
Quando falamos de CLPs não devemos nos esquecer de que os acessos a suas entradas
e saídas é através de memórias e não entradas e saídas físicas, pois no seu ciclo de
funcionamento a primeira coisa que o CLP faz é atualizar suas tabelas de entradas que
são nada mais do que áreas de memórias especiais onde o CLP armazena o estado atual
das entradas no início do scan (ciclo de execução) e no final atualiza o estados das saídas
físicas, baseado na tabela de saídas da área especial da memória destinada a ela,
falaremos mais sobre essas características em outro capítulo desse material.
Os tipos de variáveis
Por que há finitos inteiros e reais nos tipos numéricos dos computadores?
A resposta é simples: cada célula de memória possui uma quantidade finita de bits.
Assim cada número em base decimal é representado em circuito por um layout de um
grupamento de bits (base binária) previamente vinculado a uma variável. De fato, o que
se faz é pré-estabelecer uma quantidade fixa de bytes para variáveis de um dado tipo e
verificar que faixa de valores pode ser representada. Por exemplo, com 1-byte podem-
se montar até no máximo 256 combinações de zeros e uns que poderão tanto
representar números inteiros entre -128 e 127 (denominados inteiros de 1-byte com
sinal) quanto de 0 a 255 (inteiros de 1-byte sem sinal).
Tipo Caractere
Estes são mantidos por tabelas existentes em várias categorias de softwares como
editores de textos ou mesmo o próprio sistema operacional, no caso do podem ser pelo
sistema operacional ou somente dentro do software de desenvolvimento que irá fazer
a compilação para envio ao CLP para execução, irá depender da geração e tecnologia de
cada CLP e opção de seus respectivos fabricantes. Nestas tabelas há duas colunas
relacionadas: na primeira encontram-se números normalmente inteiros sem sinal; na
segunda um conjunto de grafemas de uma ou mais línguas naturais: são os caracteres.
Nestas tabelas existe um único inteiro para cada caractere e vice-versa. As tabelas de
caracteres podem ter tamanhos variados de acordo com a quantidade de bits dedicados
a representação dos valores inteiros da primeira coluna. Com 1-byte, por exemplo, tem-
se 8-bits e logo números entre 0 e 255: assim com 1-byte 256 caracteres distintos podem
ser representados.
Uma das mais comuns tabelas de caracteres utiliza 7-bits e denomina-se ASCII (American
Standard Code for Information Interchange, que em português significa "Código Padrão
Americano para o Intercâmbio de Informação”). Este padrão tem uso difundido em todo
o mundo e é aceito praticamente por todos os softwares de edição de texto. Neste texto
os algoritmos tomarão seus caracteres do padrão ASCII. A representação em
pseudocódigo ora aparecerá como número (que neste caso está na faixa 0 a 27-1, ou
seja, 0 a 127) ora aparecerá como o caractere propriamente dito (grafema). Neste
último caso os grafemas correspondentes serão escritos sempre entre aspas simples (‘ ’).
A seguir ilustramos parcialmente a tabela ASCII fazendo pares do valor numérico com o
caractere (entre aspas simples) equivalente:
Cada variável declarada como caractere num programa armazena de fato um número
inteiro. Na maioria das implementações de linguagens de programação, as células de
memória vinculadas são de 1-byte.
Tipo Lógico
Uma variável é lógica quando ela possui apenas dois valores possíveis: genericamente
Verdadeiro e Falso, ou ainda 0 e 1. Tais notações provêm da álgebra booleana e por
essa razão este tipo de dados é comumente denominado também tipo booleano.
A declaração de variáveis
declare
i: inteiro
x, y: real
b: lógico
c: caractere
//instruções do bloco de código
Este pseudocódigo ilustra como funciona a declaração de cinco variáveis: uma inteira,
chamada i, duas reais, chamadas x e y; uma lógica, chamada b; e uma de tipo caractere
chamada c. A sessão de declaração de variáveis possui a seguinte sintaxe fixa: a palavra-
chave declare marcando o início da sessão e logo abaixo a lista de variáveis endentada
em um nível para representar o bloco de variáveis. Cada linha desta lista é constituída
por um subgrupo de uma ou mais variáveis de mesmo tipo; caso haja mais de uma
variável por subgrupo, elas serão separadas por vírgulas (como ocorreu a x e y). Cada
subgrupo de variáveis encerrará em dois pontos (:) seguido pelo tipo base do subgrupo.
Os tipos primitivos base terão os nomes inteiro, real, caractere e lógico (cada qual
referindo-se aos tipos já apresentados).
O nome das variáveis normalmente aparece como um único caractere: entretanto isso
não é obrigatório. Nomes completos são legais e as regras para criá-los são as seguintes:
não são usadas palavras reservadas (como declare, pare, etc.); não são usados
caracteres especiais na grafia (como *, # ou &); não são usados espaços em branco em
um mesmo nome; números são permitidos desde que não estejam no começo do nome
(exemplo, x88 é válido, mas 5w não é); uso de underline (_) é legal. Exemplos:
declare
alpha, beta: inteiro
dia _ de _ sol: lógico
Depois de declaradas cada variável funcionará como uma “caixa” onde se podem colocar
valores e de onde também se podem lê-los. É importante observar que tais caixas
suportam um valor de cada vez e que desta forma a ideia de empilhamento de mais de
um valor naquele mesmo espaço é inconcebível.
Outra ideia inconcebível é a de “caixa vazia”: sempre haverá uma informação disponível
numa variável, mesmo logo após sua declaração. Normalmente esta informação não
tem sentido algum no contexto da codificação sendo meramente projeção do conteúdo
digital presente naquela célula de memória no momento da declaração.
A entrada de variáveis é feita com o comando leia ao passo que a saída pelo comando
escreva. Vejamos o exemplo a seguir:
declare
x: inteiro
leia x
escreva ‘valor fornecido =’, x
Neste exemplo leia provoca uma parada provisória da execução, aguardando a entrada
de informação seguida de uma tecla de confirmação (ENTER), uma vez que a informação
é confirmada é processada e enviada para ser armazenada no espaço (célula) de
memória reservada para a variável “x”.
A próxima instrução executada é uma chamada a escreva ele é responsável por enviar
a saída padrão o conteúdo textual contrário, envia à saída padrão (neste caso a tela)
conteúdo textual. Neste exemplo a informação escrita tem duas partes separadas por
uma vírgula: a primeira é uma mensagem de texto que será enviada para a tela tal como
foi escrita; a segunda é uma referência a célula x e neste caso a CPU necessita recorrer
à memória principal, buscar pelo conteúdo numérico em x, transformar este conteúdo
em texto e por fim enviá-lo à saída padrão.
De fato, apenas uma mensagem de texto é enviada à saída padrão: se duas ou mais
partes separadas por vírgulas estão presentes o processamento em série individual será
feito, várias componentes textuais geradas e por fim concatenadas numa única a qual
será enviada. O termo concatenação surge comumente em computação representando
a fusão de informação numa só, normalmente textual.
(Para a programação de CLPs sempre que falamos em concatenação nos referimos a
caracteres).
As vírgulas possuem papel importante tanto em leia como em escreva. Para o
comando leia a vírgula permite leitura de vários valores em uma única chamada do
comando. Veja o exemplo a seguir:
declare
x, y: inteiro
leia x, y
escreva ‘A soma vale =’, x+y
Uma observação, valores lógicos não podem ser lidos ou escritos diretamente com leia
e escreva.
Se o comando leia aguarda por valores numéricos e, por exemplo, texto é digitado,
então uma conversão apropriada não poderá ser feita pela CPU e ocorrerá um erro em
tempo de execução. Nesta abordagem sobre algoritmos, quando um erro ocorre
necessariamente a execução é suspensa.
Operadores
Variáveis são vinculações com a memória real do computador e funcionam como meros
armazenadores temporários de informação digital.
Entretanto, para a construção de algoritmos funcionais, deve ser possível também
operar as variáveis entre si. Nesse contexto significa tanto alterar o conteúdo de uma
variável quanto associar duas ou mais destas em uma expressão válida para se obter um
novo valor.
Unários:
Binários:
São os que atuam em mais de uma variável como os operadores aritméticos de adição
(+) e subtração (-).
Operadores Aritméticos
Operadores de Caracteres
Operadores Relacionais
Respectivamente: igual, menor que, maior que, menor ou igual a, maior ou igual a,
=, <, >,
diferente de. São utilizados em expressões lógicas para se testar a relação entre dois
<=, >=,
valores do mesmo tipo. Exemplos: 3 = 3 ( 3 é igual a 3?) resulta em VERDADEIRO ; "A"
<>
> "B" ("A" está depois de "B" na ordem alfabética?) resulta em FALSO.
Importante: para quem for utilizar o software para o estudo de algoritmos VisuAlg, as
comparações entre strings não diferenciam as letras maiúsculas das minúsculas.
Assim, "ABC" é igual a "abc". Valores lógicos obedecem à seguinte ordem: FALSO <
VERDADEIRO.
Operadores Lógicos
Operadores lógicos são aqueles que operam valores lógicos e retornam valores lógicos.
Utilizando a notação mais usual da álgebra booleana, montamos a listagem dos
operadores lógicos, juntamente com seus nomes conforme tabela a seguir.
Não Operador unário de negação. nao VERDADEIRO = FALSO, e nao FALSO =
¬ Negação VERDADEIRO. Tem a maior precedência entre os operadores lógicos.
Ou Operador que resulta VERDADEIRO quando um dos seus operandos lógicos
∨ Disjunção for verdadeiro.
E Operador que resulta VERDADEIRO somente se seus dois operandos
∧ Conjunção lógicos forem verdadeiros. Equivale ao AND do Pascal.
Xou
Operador que resulta VERDADEIRO se seus dois operandos lógicos forem
⊕ Disjunção
diferentes, e FALSO se forem iguais. Equivale ao XOR do Pascal.
Exclusiva
Sabendo que cada variável só pode assumir os valores Falso ou Verdadeiro, é possível
fazer uma listagem das possibilidades, essas listagens são chamadas tabelas da verdade.
Expressões:
Tabelas da Verdade
Usualmente a maioria dos operadores binários é usada infixamente e dos unários pré-
fixamente. Há situações em que operadores tradicionalmente infixos são utilizados
como pré ou pós-fixação. Em notação polonesa inversa (PEREIRA;2006), por exemplo, a
expressão, AB+, é legal e denota a soma das variáveis A e B com operador de adição pós-
fixado. Esta notação é conveniente aos computadores no que diz respeito a avaliação
de expressões (úteis no projeto de compiladores, por exemplo).
Operadores são funções, ou seja, ao invés da notação apresentada até então uma dada
operação pode ser expressa utilizando uma função nomeada com seus operandos entre
parênteses e separados por vírgula. Assim, por exemplo, a expressão, SOMA(A, B), tem
exatamente o mesmo efeito de, A+B. Na notação funcional o símbolo + é substituído
pela função SOMA(). Em outras sintaxes propostas para algoritmos, ou mesmo em
linguagens de programação, operadores são introduzidos apenas pela notação funcional.
Se um operador
não possui símbolo que permita prefixação, in-fixação ou pós-fixação, então a notação
funcional é única opção. Exemplos: logaritmo natural LN(), função exponencial EXP(),
funções trigonométricas (SEN(), COS() e etc.) entre outras.
A Atribuição
declare
a: inteiro
x, y: real
c: caractere
a←120
x←3.253
y←a+x+23
c←’w’
escreva b, y, c
declare
a, b: lógico
a←V
b←F
De uma forma geral o lado direito de uma atribuição é primeiramente resolvido para
então o resultado encontrado ser redirecionado para à célula de memória vinculada a
variável do lado esquerdo desta atribuição. Esta forma de processamento permite que
a seguinte expressão seja legal:
x←x+y
Em casos como este a variável que aparece em ambos os lados da atribuição possui um
valor antes e outro depois da atribuição e comumente chamados de valor antigo e valor
novo da variável respectivamente. A Arquitetura de von Neuman (de John von Neuman),
é uma arquitetura de computador que se caracteriza pela possibilidade de uma máquina
digital armazenar seus programas no mesmo espaço de memória que os dados,
podendo assim manipular tais programas. A máquina proposta por Von Neumann reúne
os seguintes componentes:(1) uma memória, (2) uma unidade aritmética e lógica (ULA),
(3) uma unidade central de processamento (UCP), composta por diversos registradores,
e (4) uma Unidade de Controle (UC).
declare
x, y: inteiro
leia x
y←x+10
escreva ‘Resposta: ’, y
Neste pseudocódigo a variável x tem atribuição implícita ao passo que y tem atribuição
explícita, ou seja, o conteúdo na célula vinculada a x muda por ação de leia ao passo que
aquela vinculada a y muda por ação do operador ← após resolução da expressão, x+10.
Funcionalmente a execução deste algoritmo implica na leitura de um valor dado pelo
usuário e sua impressão acrescentada de 10. Veja o esquema de execução a seguir:
34 <ENTER>
Resposta: 44
Expressões
Expressões podem ser constituídas pela associação de diversas variáveis de tipos não
necessariamente compatíveis, mas que se arranjem de forma lógica e avaliável. Uma vez
definidas tais expressões, a avaliação implica num processo sistemático que finaliza num
valor final cujo tipo dependerá da expressão.
Precedência de Operadores
Nas primeiras lições sobre matemática ensina-se que numa expressão numérica
primeiramente devem-se fazer as divisões e multiplicações para pôr fim fazer as adições
e subtrações. Ensina-se também que se existem componentes entre parênteses elas são
prioritárias na ordem de resolução.
Sem tais regras uma expressão como, 2+4*7, seria ambígua, ou seja, com duas
possibilidades de resposta dependo de qual operação fosse realizada em primeiro lugar.
Esta predefinição de quem opera primeiramente numa família de operadores é
comumente chamada de precedência de operadores.
(maior precedência)
abs
+, – (unários)
*, /, mod, div
(menor precedência)
+, - (binários)
(maior precedência)
¬
∧
∨
(menor precedência)
⊕
Associatividade de Operadores
A+B-C
<número>+<número>-<número>
<número>+<número>
<número>
Cada redução na resolução acima transforma um par de números num novo número
que entra na etapa seguinte da avaliação. De uma forma geral, operadores aritméticos
são sempre associáveis. O mesmo não acontece na expressão, A>B>C, onde A, B e C são
inteiros. O processo de avaliação desta expressão, da esquerda para direita, é
interrompido por uma inconsistência, como se vê a seguir:
A>B>C
<número> > <número> > <número>
<número> > <lógico> //inconsistência! Não pode continuar
Expressões Booleanas
Se existem parênteses nas expressões então o conteúdo entre eles deve ser avaliado
primeiro obedecendo naturalmente, neste escopo, a sequência anterior. Considere o
exemplo a seguir:
Caso a disjunção exclusiva deva ser avaliada antes da conjunção, então o uso
apropriado de parênteses transforma a expressão em:
Estrutura Sequencial
É o conjunto de ações primitivas que serão executadas numa sequência linear, de cima
para baixo e da esquerda para a direita.
Estruturas de Seleção
Seleção Simples
se condição A então
comando 1;
comando 2;
comando 3;
fim se;
Seleção composta
se condição A então
comando A1;
comando A2;
comando A3;
senão
comando B1;
comando B2;
comando B3;
fim se;
Decisão múltipla
...
<rótulo n>: <bloco n>
Cada rótulo da estrutura acima possui o próprio bloco e é separado dele pela marca,
(dois pontos). Todos os rótulos são dispostos um nível de indentação acima no nível
onde está a palavra-chave seja. Blocos dispõem-se um nível de indentação acima
daquele onde estão os rótulos.
Em síntese, após a avaliação da expressão (ou variável) de controle um valor inteiro é
obtido, o rótulo com este valor selecionado e por fim o bloco executado. Quando a
execução encerra, ocorre um desvio para fora da estrutura para que outros blocos não
sejam executados.
O exemplo a seguir ilustra a estrutura caso...seja:
declare
n: inteiro
leia n
caso (n) seja
1: escreva ‘Olá mundo!’
2: escreva ‘Hello world!’
3: escreva ‘Hallo Welt!’
A execução deste pseudocódigo implica impressão de uma entre três mensagens (caso
o valor em n seja 1, 2 ou 3) ou em finalização sem impressão alguma (caso outros
valores sejam fornecidos). Nunca duas mensagens são simultaneamente impressas.
declare
ch: caractere
raio, base, altura, lado: inteiro
escreva ‘pressione: C para cículo, T para triângulo, Q para
quadrado’
ch ← tecla
caso ch seja
‘C’: escreva ‘Valor do raio:’
Leia raio
escreva ‘área = ’, 3.14159235*raio*raio
‘T’: escreva ‘Valores da base e da altura: ’
leia base, altura
escreva ‘área = ’, base*altura/2
‘Q’: escreva ‘Valor do lado: ’
leia lado
escreva ‘área = ’, lado*lado
senão
escreva ‘Opção inválida!!’
Este algoritmo efetua cálculo da área de três categorias de figuras geométricas: círculo,
triângulo ou quadrado. De acordo com a tecla pressionada a execução é direcionada
para um rótulo apropriado. Cada bloco possui mensagem de entrada, leitura de
variáveis e apresentação de resultados (área da figura). A variável de controle é de tipo
caractere e como caracteres são tipos especiais de inteiros então podem ser usados em
estruturas caso...seja.
O uso de uma decisão com se..então..senão em um bloco de caso...seja é perfeitamente
legal e bastante usual. No exemplo seguinte simula-se uma calculadora cujos operandos
são fornecidos primeiro e a operação (soma, subtração, multiplicação ou divisão) depois
com um pressionar de tecla:
declare
op: caractere
x, y: inteiro
escreva ‘fornecer valores: ’
leia x, y
escreva ‘forcecer opção : + - * /’
op ← tecla
caso op seja
‘+’: escreva ‘Soma: ’, x+y
‘-’: escreva ‘Subtração: ’, x-y
‘*’: escreva ‘Multiplicação: ’, x*y
‘/’: se (b≠0) então
escreva x/y
senão
escreva ‘divisão por zero!’
senão escreva ‘Opção inválida!!’
Se a tecla com o caractere ‘/’ for pressionada então a operação de divisão é selecionada.
Isso inicia um processo de decisão com se..então..então que trata a possibilidade de
divisão por zero. A mensagem ‘divisão por zero!’ é impressa quando o valor em b é nulo.
A mensagem ‘Opção inválida!!’ é impressa quando uma tecla operacional inválida é
pressionada.
Aninhamento de Decisões
se (...) então
// bloco
se (...) então
// bloco
senão
se (...) então
// bloco
senão
se (...) então
// bloco
senão
// bloco
Repetição
Um laço é controlado por contador quando uma variável auxiliar, chamada contador,
usualmente de tipo inteiro, é responsável pela contabilização das iterações. A estrutura
de controle para laço com contador é, para...até...faça, e possui a seguinte sintaxe geral:
declare
i: inteiro
para i ← 1 até 100 faça
escreva i
declare
i, x: inteiro
para i ← 1 até 50 faça
x ← 2*i
escreva x
A execução deste algoritmo imprime na saída padrão os números pares entre 2 e 100.
Em cada iteração o valor em x é recalculado em função do valor do contador i.
Uma alternativa sintática ao laço com contador, e que melhora o desempenho de muitos
algoritmos, é o uso do passo. Por definição o passo é um valor inteiro que deve ser
somado ao contador quando encerrar uma iteração de laço e iniciar outra. O passo nos
laços mostrados anteriormente vale 1. A sintática básica do laço com contador e passo
é a seguinte:
para <contador> ← <limite inferior>, <limite superior>, <passo> faça
<bloco de pseudocódigo>
declare
i: inteiro
para i ← 2, 100, 2 faça
escreva i
Neste algoritmo o contador i inicia com valor 2 e em cada iteração é incrementado de 2
(passo) até atingir 100.
Note que dependendo do passo o contador pode passar por cima do limite superior.
Quando isso acontece o laço encerra naturalmente quando o contador atinge o primeiro
valor acima do limite superior, mas sem processá-lo.
Exemplo:
declare
i: inteiro
para i ← 7, 30, 3 faça
escreva i, ‘ ’
Quando o valor do passo é negativo é possível montar laços com contador decrescente
porque neste caso ocorre sucessivamente a subtração ao invés da adição. Exemplo:
declare
i: inteiro
para i ← 20, 0, -1 faça
escreva i, ‘ ’
declare
n, i, fat: inteiro
leia n
fat ← 1
para i ← 1 até n faça
fat ← fat*i
escreva fat
Cada iteração de um laço está associada a um teste que será responsável pela finalização
ou continuação deste. Nos laços com contador este teste está embutido na própria
declare
x: inteiro
x←0
enquanto (x<20) faça
escreva x, ‘ ’
x←x+2
0 2 4 6 8 10 12 14 16 18
O valor 20 não é impresso porque não satisfaz a expressão booleana (20 é igual a 20 e
não menor).
Observe que, ao contrário do que ocorre nos laços por contador, é possível mudar o
local onde a variável de controle se modifica. Se, por exemplo, as duas linhas do bloco
encapsuladas por enquanto no algoritmo anterior fossem trocadas de posição o código
ainda seria válido, mas a saída impressa se modificaria para:
2 4 6 8 10 12 14 16 18 20
declare
x: inteiro
x←0
repita
escreva x, ‘ ’
x←x+2
até (x>20)
0 2 4 6 8 10 12 14 16 18 20
Aninhamento de Laços
declare
i, j: inteiro
para i ← 1 até 10 faça
para j ← 1 até 10 faça
escreva i+j
Os dois laços no algoritmo anterior são construídos para executar 10 vezes cada. O
primeiro tem contador em i e o segundo contador em j. Entretanto o primeiro lado
encapsula o segundo laço (posto um nível de endentação acima). Isso faz com que cada
iteração do primeiro laço execute todas as 10 iterações do segundo laço e
consequentemente execute 100 vezes a instrução escreva (posta um nível de
endentação acima do segundo laço).
Os limites dos contadores dos laços podem ser expressos como função de qualquer
variável inteira do programa (exceto o próprio contador). Isso inclui também os
contadores de laços hierarquicamente acima em um dado aninhamento. Veja o
exemplo:
declare
i, j, cnt: inteiro
cnt ← 0
Valor de i 1 2 3 4 5 6 7 8 9 10
Número de iterações do segundo laço 10 9 8 7 6 5 4 3 2 1
O algoritmo utiliza acumulação por soma para guardar na variável cnt o total de
iterações. Este valor também pode ser calculado pela somatória dos valores na segunda
linha da tabela anterior, ou seja, 10+9+8+7...+1 = 55. Este será o valor impresso na saída
padrão.
Um laço controlado por teste é denominado laço infinito quando a expressão booleana
de controle é sempre avaliada como Verdadeira (no caso de enquanto...faça) ou sempre
como Falsa (no caso de repita...até). Laços infinitos são aplicados em situações onde o
critério de parada não se encaixa na entrada (ou na saída) do laço. Quando instruções
internas ao bloco de laço promovem sua suspensão forçada (sem uso da expressão
booleana de controle) diz-se que houve quebra de laço. Uma quebra de laço é
construída utilizando o comando pare. Veja o exemplo:
declare
ch: caractere
enquanto (V) faça
ch ← tecla
caso (ch) seja
‘x’: escreva ‘olá mundo!!’
‘y’: escreva ‘hello world!’
‘z’: escreva ‘hallo Welt!!’
senão pare
das três então o pare do bloco rotulado senão é executado suspendendo o laço. As
regras de funcionamento do comando param são as seguintes:
• Se pare for executado fora de laços, mesmo encapsulado por alguma estrutura de
decisão, ocorrerá suspensão da execução do algoritmo como um todo.
• Se pare for executado dentro de um laço sem aninhamento com outros laços, mesmo
que esteja encapsulado por alguma estrutura de decisão, apenas tal laço será suspenso,
mas o algoritmo não necessariamente.
• Se pare for executado em um aninhamento de laços, mesmo havendo aninhamento
de estruturas de decisão, apenas o laço hierarquicamente mais próximo a este comando
será suspenso, mas o algoritmo não necessariamente.
Estudo de Caso – Números Primos: Um número primo é aquele que é divisível apenas
por um e por ele mesmo. Como um número não é divisível pelos que o sucedem é
razoável acreditar que um dado inteiro n, que se quer testar se é primo ou não, deva ser
dividido pelos números no intervalo fechado [2, n-1] sendo primo se todas estas divisões
obtiverem resto não nulo e não primo se pelo menos uma for nula (encontrada uma
divisão com resto nulo o número não é mais primo e as demais divisões, se ainda
restarem, tornam-se desnecessárias). Matematicamente, entretanto, tantas divisões
não são necessárias um intervalo suficiente de testes pode ser reduzido para [2, d] tal
que d2 ≤ n.
Com base no critério anunciado acima foi construído um algoritmo capaz de escrever,
na saída padrão, todos os números primos menores que cinco mil. Segue:
declare
n, d: inteiro
b: lógico
para n ← 2 até 5000 faça
b←V
d←2
enquanto (d*d ≤ n) faça
se (n mod d = 0) então
b←F
pare
d ← d+1
se (b) então
escreva n, ‘ ’
O laço para, neste algoritmo submete em cada iteração o valor de seu contador n a um
teste para verificar se ele é ou não valor primo. O teste possui quatro etapas:
(II) A variável d recebe valor 2 indicando o início do intervalo de valores pelos quais
o valor em n deve ser dividido.
Neste último exemplo, utilizando-se limite inferior igual a 3 e passo igual a 2 no laço
para, testam-se apenas valores ímpares (o único primo par é 2) aumentando-se a
velocidade do algoritmo.
Matrizes
Uma variável escalar é uma entidade abstrata que se refere a uma célula de memória e
que possui um tipo relacionado, uma faixa restrita de representação e a capacidade de
armazenar um único valor. Há, entretanto, a possibilidade de definir variáveis com
capacidade de guardar mais de um valor ao mesmo tempo. Tais variáveis são
denominadas matrizes.
Todas essas variáveis constituintes são do mesmo tipo de forma que podemos ter, por
exemplo, matrizes de inteiros, lógicos ou de reais, mas nunca a mistura deles.
Denomina-se tipo base o tipo de dados comum a todos os aglomerados da matriz.
Uma variável matriz, assim como todas as demais, possui apenas um nome. Então como
manipular cada uma das variáveis escalares (células) que a constitui? O mecanismo para
se referenciar cada uma das células de uma matriz é denominado indexação. Assim, por
exemplo, se M é uma matriz dos primeiros dez ímpares então M[1] = 1, M[2] = 3, M[3]
= 5 e assim por diante: aqui os colchetes retratam hospedeiros dos índices que neste
caso vão de um a dez acessando independentemente cada valor em M. O vetor M ainda
pode ser descrito desta forma: M[k]=2k-1, com o índice k no intervalo {1..10}.
Matrizes podem contar com um ou mais índices. Cada índice de uma matriz está
associado a uma dimensão da matriz. Assim matrizes unidimensionais precisam de um
índice, bidimensionais de dois e assim por diante. As matrizes na matemática se
confundem com as matrizes bidimensionais aqui descritas.
pelo tamanho em bytes do tipo base. Assim, por exemplo, se H é um vetor formado de
5 inteiros de 2-bytes, então o comprimento de H é 5, seu tipo base é inteiro, e seu
tamanho vale 10-bytes.
Comprimento = 5
Inteiro – 2 bytes Inteiro – 2 bytes Inteiro – 2 bytes Inteiro – 2 bytes Inteiro – 2 bytes
Matrizes na Memória
O acesso a células individuais via vetores é o mais eficaz se comparada a outras formas
de armazenamento linear (como listas encadeadas).
declare
M[10], i, imax: inteiro
Este algoritmo solicita dez valores do usuário e imprime como saída o maior entre eles.
O primeiro laço é o responsável pela entrada de dados. Em cada iteração o comando
leia modifica uma célula distinta do vetor M de modo a preenchê-lo convenientemente.
O restante do código é um processo de busca. Tal busca começa com a hipótese deque
o maior valor está na primeira posição (assim imax recebe 1). As demais posições são
testadas progressivamente pelo laço para: cada vez que uma célula verificada possui
valor maior que aquele de índice imax, então imax recebe o valor do contador. Quando
o laço encerra imax possui a posição contendo o maior valor e assim M[imax] é impresso.
Não há aninhamento de laços neste exemplo e por essa razão os dois laços estão no
mesmo nível de indentação. Apesar de não estar disponível na maioria das linguagens
de programação introduzimos nessa abordagem uma opção de inicialização de vetores
diretamente do código. A sintaxe geral é a seguinte:
declare
<variável_1>[c1]: <tipo>
//...
<variável_1> ¬ {<valo1_1>, <valo1_2>, <valo1_3>,..., <valo1_c1>}
//...
Um exemplo de aplicação:
declare
M[10], i, imax: inteiro
M ¬ {12, -9, 33, 8, -23, 17, 6, -5, 31, 2}
imax 1
para i ¬2 até 10 faça
se (M[i] > M[imax]) então
imax i
escreva M[imax]
Perceba, que não é necessário utilizar totalmente o tamanho do vetor, porém caso a
mensagem seja maior que o vetor quaisquer caracteres que excedam o tamanho do
vetor, serão desconsiderados, outro detalhe é que o espaço utilizado para separar as
palavras também conta como parte do vetor e ocupam espaços na memória.
declare
S[10]: caractere
S ‘a casa’
precisa ser definido no código pelo programador. O efeito direto disso é a inflexibilidade,
ou seja, se mais memória for necessária não será possível expandir o vetor durante a
execução. Para resolver esta limitação, introduzimos os comandos alocar e limpar. Eles
interagem diretamente com o gerenciador de memória requisitando e devolvendo
respectivamente quantidades de memória. Cada solicitação de memória com alocar
está associada a uma devolução com limpar. Memória não devolvida mantém o status
de ocupada não podendo ser usada por outros processos. Em máquinas reais, a
ocorrência desse quadro causa queda de desempenho ou mesmo parada do sistema
(crash).
declare
<vetor>[]: <tipo base>
Exemplos:
declare
X[]: inteiro
VET[]: inteiro
alocar <vetor>[<comprimento>]
...
limpar <vetor>
declare
X[], n, i: inteiro
leia n
alocar X[n]
para i 1 até n faça
X[i] 2*i-1
limpar X
No exemplo acima o vetor dinâmico X tem seu comprimento alocado com um valor de
comprimento, n, fornecido via entrada padrão. Após a alocação o vetor é carregado com
a máxima quantidade de números ímpares que suporta e finalmente desalocado ao final.
Um detalhe para CLPs, é que a os mesmos não permitem alocação dinâmica de memória,
nesse caso não é possível utilizar vetores dinâmicos, porém são uma grande ferramenta
de programação para computadores.
1.6 Modularização
Conforme uma tarefa cresce e se torna mais complexa, surge uma série de situações a
serem resolvidas para que este problema possa ser solucionado. Podemos dizer que
passamos a ter dentro deste problema uma série de pequenos problemas.
Muitas vezes, a grande quantidade de pequenos problemas afeta a legibilidade (clareza)
fazendo com que uma consulta ou manutenção futura desta lógica seja uma tarefa difícil
de se realizar. Através da modularização é possível evitar esta "confusão da lógica".
Modularizar é quebrar um problema em pequenas partes, sendo que cada uma dessas
partes será responsável pela realização de uma etapa do problema.
Se um grupo de argumentos de entrada tiver mesmo tipo eles podem ser declarados
com a seguinte sintaxe:
Quando uma aplicação inicia sua execução diz-se que a função principal recebe o
controle de fluxo ou simplesmente que recebe o controle. Se a função principal chama
uma função secundária então esta função receberá o controle do fluxo enquanto estiver
executando e o devolverá a função principal quando sua execução se encerrar.
Funções podem retornar valores. O retorno (ou saída) de uma função pode ser, por
exemplo, um número, um caractere ou um booleano. A ação de retorno de uma função
é sumária, ou seja, quando invocada a função automaticamente se encerra devolvendo
o controle à função chamadora. Para provocar o retorno usa-se a palavra-chave retorne
seguida do valor que se deve retornar. Veja o exemplo a seguir:
declare
s: inteiro
sx+y
retorne s
principal()
declare
a, b, c: inteiro
leia a, b
c soma(a,b)
escreva c
Neste exemplo há duas funções: a principal (que sempre denotaremos pelo nome
principal) e a função secundária soma(). A função secundária possui dois parâmetros, x
e y e sua chamada é feita na função principal passado como argumentos os valores nas
variáveis a e b. O uso da atribuição, c soma(a, b), entra em concordância com o
retorne da função soma(), ou seja, só é possível atribuir a c algum valor porque este
valor existe e é o retorno da função em questão. As variáveis x e y são denominados
argumentos formais da função soma() ao passo que a e b são denominadas de
argumentos reais da mesma função.
Procedimentos, tem como diferença para as funções o fato de não retornarem valores
para a função principal.
// Seção de Comandos
fimprocedimento
O escopo de uma variável é definido como o lugar no algoritmo onde esta variável pode
ser manipulada.
Variáveis Globais
Tem a sua declaração, antes do cabeçalho da função principal, como o nome diz ela
pode ser acessada por qualquer função
Variáveis locais
São declaradas dentro da seção de declaração de variáveis dentro das funções e são
acessadas somente dentro dessa função específica.
1.7 Recursividade
Recursividade é a técnica que permite a uma função fazer, no corpo de seu código, uma
chamada a si própria. Para ilustrar a recursividade apresentamos a seguir duas versões
de funções para cálculo de fatorial: uma não-recursiva (iterativa) e outra recursiva.
//versão iterativa
fat(n: inteiro)
declare
res: inteiro
enquanto (n>0) faça
res res * n
nn-1
retorne res
// versão recursiva
fat(n: inteiro)
se (n<2) então
retorne 1
senão
retorne n*fat(n-1)
A versão não recursiva não traz novidades, mas ilustra a potencialidade dos laços para
resolver problemas como o cálculo de fatoriais. Na versão recursiva o laço desaparece,
pelo menos aparentemente. O processamento da versão recursiva da função fatorial é
descrito a seguir. Quando uma chamada explícita a fat() é feita seu argumento formal
recebe o valor do qual se deseja calcular o fatorial. Caso este seja menor que 2 a função
se encerra retornando 1. Caso não seja, ela tenta retornar o produto, n*fat(n-1). Como
a expressão de retorno contém a própria função, então uma nova chamada a fat() é
realizada. Enquanto esta nova chamada não for resolvida a chamada anterior não
poderá retornar. Assim a primeira chamada fica a espera do retorno da segunda
chamada. Essa por sua vez pode desencadear o mesmo processo e ter-se-á um primeiro
aguardando por um segundo e esse por sua vez por um terceiro. O processo só não se
torna infinito porque existe a decisão bidirecional, se, que desvia para retorno 1 sempre
que valores menores que 2 forem repassados.
Em suma a recursão cria vários níveis de espera que são resolvidos de trás para frente
no momento em que uma delas não recorre mais a recursão. A presença de um
mecanismo de parada como o descrito no exemplo do fatorial (uso de se) impede
naturalmente o problema de laço recursivo infinito.
Recomendações:
• Não analise a resposta dos exercícios logo de cara, tente elaborar o algoritmo.
• Pense no algoritmo como receita de bolo;
• Na dúvida refaça a leitura sobre o tema da apostila;
• Continuou sem entender??? Google!! ... tem várias outras na internet explicando
de forma diferente, talvez ajude;
• Coloque no papel tudo o que entendeu;
• Mesmo assim não conseguiu? Mostre até onde chegou a quem entende e busque
ajuda, explicando com clareza o que está “travando”você;
Todos os algoritmos das respostas serão gerados no VisuAlg, então teste seus algoritmos
lá também.
Terminou todos os exercícios?? Parabéns ... hora de dar um Google e procurar por
mais! – Quanto mais pratica, melhor fica!
Exercícios:
Var
x, y: inteiro
inicio
// Seção de Comandos
escreval("Digite o primeiro número: ")
leia(x)
escreval("Digite o segundo número: ")
leia(y)
escreval("A soma dos números é: ",x+y)
2- Função : Faça um algoritmo que receba dois números e ao final mostre a soma,
subtração, multiplicação e a divisão dos números lidos.
Resposta:
var
x, y: real
inicio
// Seção de Comandos
escreva("Digite o primeiro número: ")
leia(x)
escreva("Digite o segundo número: ")
leia(y)
escreval("A soma é: ",x+y)
escreval("A subtração é: ",x-y)
escreval("A multiplicação é: ",x*y)
escreval("A divisão é: ",x/y)
fimalgoritmo
3- Função : Escrever um algoritmo que leia o nome de um vendedor, o seu salário fixo
e o total de vendas efetuadas por ele no mês (em dinheiro). Sabendo que este
vendedor ganha 15% de comissão sobre suas vendas efetuadas, informar o seu
nome, o salário fixo e salário no final do mês
Resposta:
var
nome: caractere
salario: real
vendas: real
comissao: real
salarioFinal: real
inicio
// Seção de Comandos
escreval("<><><><><> Sistema de gestão de vendedores <><><><><>")
escreva(">>> Digite o nome do vendedor: ")
leia(nome)
escreva(">>> Digite o salário: ")
leia(salario)
escreva(">>> Informe a quantidade de vendas deste no mês: ")
leia(vendas)
// Cálculo da comissão e salário final
comissao >>>>>>>>> RESUMO <<<<<<<<<<")
escreval("-- Nome: ",nome)
escreval("-- Salário: ",salario)
escreval("-- Salário Final (salário + comissão): ",salarioFinal)
escreval(">>>>>>>>>><><><><><<<<<<<<<<")
inicio
escreval("Insira um número inteiro: ")
leia(n)
se(n mod 2 = 0) entao
escreval("O número: ",n," é par")
senao
escreval("O número: ",n," é impar")
fimse
fimalgoritmo
algoritmo "Financiamento"
var
sala, financ: real
inicio
escreval("Digite o valor do salário: ")
leia(sala)
escreval("Digite o valor do financiamento pretendido: ")
leia(financ)
se(financ<= 5 * sala) entao
escreval("Financiamento concedido, obrigado por nos consultar")
senao
escreval("Financiamento negado, obrigadopor nos consultar")
fimse
fimalgoritmo
10- criar um algoritmo para uma calculadora, o usuário digita o primeiro número, a
operação que deseja executar e o segundo número. Dependendo do que o usuário
informar como operador, o algoritmo executará um cálculo diferente (soma,
subtração, multiplicação ou divisão
Resposta:
algoritmo "CalculadoraBasicaComSE"
var
numero1 : REAL
numero2 : REAL
operacao : CARACTERE
resultado : REAL
inicio
Outra versão:
algoritmo "CalculadoraBasicaComSE"
var
numero1 : REAL
numero2 : REAL
operacao : CARACTERE
resultado : REAL
inicio
ESCOLHA operacao
CASO "+"
resultado := numero1 + numero2
CASO "-"
resultado := numero1 - numero2
CASO "*"
resultado := numero1 * numero2
CASO "/"
resultado := numero1 / numero2
FIMESCOLHA
// Seção de Declarações
var
i:inteiro
vetA:vetor[1..5] de inteiro
vetB:vetor[1..5] de inteiro
inicio
// Seção de Comandos
// VETOR A
para i de 1 ate 5 faca
escreval("VETOR A")
escreva("Informe o valor da posicao: ",i,": ")
leia(vetA[i])
fimpara
limpatela
//APRESENTANDO VETOR A
escreval("VETOR A")
escreval("")
para i de 1 ate 5 faca
escreval(vetA[i])
fimpara
//APRESENTANDO VETOR B
escreval("")
escreval("VETOR B")
escreval("")
para i de 1 ate 5 faca
escreval(vetB[i])
fimpara
fimalgoritmo
12- Faça um algoritmo que leia um vetor de 20 posições a apresente o maior e o menor
valor e as posições que eles se encontram.
Resposta:
// Seção de Declarações
var
i,maior,menor,posicaomaior,posicaomenor:inteiro
numeros:vetor[1..20] de inteiro
inicio
// Seção de Comandos
se numeros[i]>maior entao
maior<-numeros[i]
posicaomaior<-i
fimse
se numeros[i]<menor entao
menor<-numeros[i]
posicaomenor<-i
fimse
fimpara
escreval("")
escreval("")
fimalgoritmo
// Seção de Declarações
var
l,c:inteiro
matrizA:vetor[1..3,1..3] de inteiro
matrizB:vetor[1..3,1..3] de inteiro
matrizC:vetor[1..3,1..3] de inteiro
inicio
// Seção de Comandos
// MATRIZ A
para l de 1 ate 3 faca
para c de 1 ate 3 faca
escreval("MATRIZ A")
escreva("Informe o valor da posicao: ",l,"-",c,": ")
leia(matrizA[l,c])
limpatela
fimpara
fimpara
limpatela
// MATRIZ B
para l de 1 ate 3 faca
para c de 1 ate 3 faca
escreval("MATRIZ B")
escreva("Informe o valor da posicao: ",l,"-",c,": ")
leia(matrizB[l,c])
limpatela
fimpara
fimpara
limpatela
// MATRIZ C
para l de 1 ate 3 faca
para c de 1 ate 3 faca
matrizC[l,c]<-matrizA[l,c]+matrizB[l,c]
fimpara
fimpara
escreval("MATRIZ A")
escreval("")
escreval("")
escreval("MATRIZ B")
escreval("")
escreval("")
escreval("A SOMA DAS MATRIZES A e B")
escreval("")
escreval("")
fimalgoritmo
14- Dado uma matriz de 4x5 elementos, escreva um algoritmo para calcular a soma de
cada uma das linhas.
Resposta:
var
l,c,total:inteiro
numeros:vetor[1..4,1..5] de inteiro
inicio
// Seção de Comandos
limpatela
escreval("")
escreval("")
fimalgoritmo
15- Escreva um algoritmo para ler dois números e exibir o menor dos dois. A verificação
de qual deles é o menor deve ser realizada por uma função.
Resposta:
algoritmo "Calcula_Quadrado"
// módulo para cálculo do quadrado de um número
leia (val)
fimprocedimento
16- Fazer um algoritmo para o usuário informar uma frase e o número de vezes que o
programa deve repetir essa frase na tela.
Resposta:
Algoritmo “soma”
Var
string:caractere
repetir:inteiro
contador:inteiro
Inicio
escreval(“Digite uma frase:”)
leia(string)
escreval(“Digite a quantidade de vezes para repetir:”)
leia(repetir)
escreval (“resultado:”)
enquanto contador < repetir faca
escreval(string)
contador <- contador+1
fimenquanto
fimalgoritmo
Bibliografia:
https://www.google.com/search?q=algoritmo+cadeia+de+caracteres&rlz=1C1SQJL_pt-
BRUS825US825&oq=algoritmos+cadeia+de+ca&aqs=chrome.1.69i57j0.6303j0j4&sourc
eid=chrome&ie=UTF-8
https://www.dca.ufrn.br/~affonso/DCA800/pdf/algoritmos_parte1.pdf
http://joinville.ifsc.edu.br/~edsonh/Repositorio/Microcontrolador/Livro%20de%20Pro
grama%C3%A7%C3%A3o/Cap7%20-%20Modularizando%20Algoritmos_v7.pdf