Escolar Documentos
Profissional Documentos
Cultura Documentos
ALGORITMO E LÓGICA
DE PROGRAMAÇÃO
MISSÃO
VISÃO
EDITORIAL
FACULDADE CAPIXABA DA SERRA • MULTIVIX
Algoritimo e lógica da programação / Bruno Perche Pinto, Juliane Escola (revisora). – Serra : Multivix, 2017.
Seja bem-vindo!
SUMÁRIO
2
2 CONCEITOS FUNDAMENTAIS PARA CONSTRUÇÃO
UNIDADE
DE ALGORITMOS ESTRUTURADOS 26
2.1 APRESENTAÇÃO DA UNIDADE 26
2.2 ALGORITMO COM ESTRUTURA SEQUENCIAL 26
2.3 ALGORITMO COM ESTRUTURA CONDICIONAL 28
2.3.1 ESTRUTURA DE CONDIÇÃO SIMPLES: SE - ENTÃO (IF - THEN) 28
2.3.2 ESTRUTURA DE CONDIÇÃO COMPOSTA:
SE - ENTÃO - SENÃO (IF - THEN - ELSE) 29
2.3.3 ESTRUTURA DE CONDIÇÃO CASO SEJA 31
2.4 ALGORITMO COM ESTRUTURA DE REPETIÇÃO 32
2.4.1 ENQUANTO-FAÇA 33
2.4.2 FAÇA-ENQUANTO 33
2.4.3 FAÇA-PARA 34
REFERÊNCIAS 133
ICONOGRAFIA
ATENÇÃO ATIVIDADES DE
APRENDIZAGEM
PARA SABER
SAIBA MAIS
ONDE PESQUISAR CURIOSIDADES
LEITURA COMPLEMENTAR
DICAS
GLOSSÁRIO QUESTÕES
MÍDIAS
ÁUDIOS
INTEGRADAS
ANOTAÇÕES CITAÇÕES
EXEMPLOS DOWNLOADS
UNIDADE 1
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
> Compreender os
conceitos básicos
da Administração
de Pessoal.
APRESENTAÇÃO
Olá, acadêmico (a).
Para que seu estudo se torne proveitoso e prazeroso, esta disciplina foi organizada
em 8 unidades, com temas e subtemas que, por sua vez, são subdivididos em seções
(tópicos), atendendo aos objetivos do processo de ensino-aprendizagem.
Para tanto, fique atento (a) à leitura e acompanhamento das novas tecnologias que
vem surgindo.
Antes de iniciar a leitura, gostaria que você parasse um instante para refletir sobre o
desenvolvimento de programas de computadores, parece ser complexo, não é? Não
se preocupe, até o final da disciplina, você terá respostas e, também, outras pergun-
tas formuladas.
UNIDADE 1
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
> Compreender os
conceitos da lógica
de programação e de
algoritmos;
1 NOÇÕES DE LÓGICA DE
PROGRAMAÇÃO.
Nesta unidade iremos conhecer o conceito de lógica de programação, essa será sua
primeira etapa no aprendizado da programação de computadores. Sendo assim, ela
é a base para o curso, pois é nela que você aprenderá os princípios básicos que lhe
darão suporte para todas as demais disciplinas desta área.
Nesta disciplina, iniciaremos nossos estudos sobre Lógica de Programação. Mas, afi-
nal, qual o significado da palavra “Lógica”?
Segundo Farrer (1999), a lógica pode ser vista como a arte de pensar corretamente.
A lógica visa a colocar ordem no pensamento.
Aplicamos a lógica no dia-a-dia muitas vezes até mesmo sem percebermos. Por
exemplo:
Posso concluir então que para entrar no mar terei que suportar a água gelada.
Posso concluir então que para ganhar na Mega Sena, tenho que realizar a aposta.
Agora que entendemos o conceito de lógica e instruções, podemos definir que a lógi-
ca de programação é a técnica de encadear pensamentos para atingir determinado
objetivo.
1.3 ALGORITMO
Podemos pensar no algoritmo como uma receita de um bolo, uma sequência de ins-
truções que com uma meta específica. Estas etapas devem ser claras e precisas, não
podem ser redundantes nem subjetivas na sua definição.
Segundo Forbellone (2005), o algoritmo trata-se de uma sequência de passos que de-
vem ser seguidos para atingir um determinado objetivo. O algoritmo não é a solução
do problema e sim o caminho para a solução do mesmo. O conceito de algoritmo
não se aplica apenas na área da computação, ele define os passos necessários para
realizar uma tarefa ou solucionar um problema, independente da área de atuação.
Passo 6 – Se o saldo for maior que a quantia desejada, sacar; caso contrário, mostrar
mensagem de impossibilidade do saque;
Esta solução é apenas uma das muitas soluções possíveis para o problema apresenta-
do. Assim, ao criarmos um algoritmo, indicamos uma dentre várias possíveis sequên-
cias de passos para solucionar o problema.
Por exemplo, o problema acima poderia ser resolvido mesmo se alterássemos a se-
quência de passos para:
Passo 10 – Se o saldo for maior que a quantia desejada, sacar; caso contrário, mostrar
mensagem de impossibilidade do saque;
Agora considere o seguinte problema. Suponha que você dispõe de duas vasilhas de
nove e quatro litros respectivamente. Como elas não possuem marcação, não é possí-
vel ter medidas intermediárias sobre o volume ocupado. O problema consiste, então,
em elaborar uma sequência de passos, por meio da utilização das vasilhas de nove e
quatro litros, a fim de encher uma terceira vasilha com seis litros de água. A figura 1
abaixo, ilustra dois possíveis passos de um algoritmo para resolver o problema.
Figura 1. Ilustra dois passos possíveis envolvendo as operações de encher e esvaziar as vasilhas. Em (a) apenas a primeira vasilha está
cheia. Já em (b) os nove litros da primeira vasilha são colocados nas outras duas.
Uma solução para o problema pode ser alcançada a partir do seguinte algoritmo:
3. Coloque a quantidade que sobrou (cinco litros) na terceira vasilha (v3 = 5).
8. Usando a sobra da de nove litros (cinco litros), encha novamente a de quatro litros.
9. Coloque a sobra da de nove litros (agora um litro) na terceira vasilha (v3=5+1= 6).
- Depois da identificação dos dados de entrada, agora é definir como será o pro-
cessamento da multiplicação de num1 com num2;
• Testarmos o algoritmo para possíveis correções que possam vir a ser necessá-
rias na lógica proposta;
• Imaginar que você está desenvolvendo um algoritmo para pessoas que não
trabalham com informática;
• Ser objetivo;
Todo algoritmo, seja ele computacional ou não, recebe uma entrada, processa-a e
gera uma saída segundo seu conjunto de passos. Todos eles possuem as seguintes
características:
Os três tipos mais utilizados para representar algoritmos são: descrição narrativa, flu-
xograma e pseudocódigo ou portugol, que descrevemos a seguir.
conceito novo, pois uma língua natural, neste ponto, já é bem conhecida. Os exem-
plos de algoritmos mostrados anteriormente refletem esta forma de representação.
A sintaxe do pseudocódigo não precisa ser seguida tão rigorosamente quanto a sin-
taxe de uma linguagem de programação, já que o algoritmo não será executado
como um programa.
Esta é a linguagem mais utilizada por ser mais formal do que a descrição narrativa
e mais fácil de manter do que um fluxograma. Além disso, essa linguagem é menos
rígida do que uma linguagem de programação, dando assim liberdade ao progra-
mador para “rascunhar” seus futuros programas sem ficar engessado na rigidez da
linguagem de programação escolhida por ele. Geralmente, essa forma de represen-
tação de algoritmos é uma versão reduzida de linguagens de alto nível como C e
Pascal. A Figura 3 apresenta um exemplo de algoritmo na forma de representação
de pseudocódigo.
Com base nos três tipos de algoritmos citados anteriormente, os exemplos a seguir mos-
tram os algoritmos para retornar o resultado da multiplicação de dois números inteiros.
Algoritmo em pseudocódigo:
ALGORITMO
LEIA N1, N2
M ← N1 * N2
ESCREVA “Multiplicação = “, M
FIM ALGORITMO.
UNIDADE 2
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
> Compreender
a diferenciação
existente entre
as estruturas de
controle do fluxo de
execução;
> Estrutura de
repetição;
2 CONCEITOS
FUNDAMENTAIS
PARA CONSTRUÇÃO
DE ALGORITMOS
ESTRUTURADOS
2.1 APRESENTAÇÃO DA UNIDADE
Os algoritmos dos exemplos que vimos até agora apresentam uma sequência de
passos que devem ser seguidos para atingir um objetivo final. Observe que, para o
atingimento do objetivo, todos os passos dos algoritmos devem ser executados sem
nenhuma alteração no seu fluxo, a não ser, claro, que exista alguma instrução explíci-
ta para a mudança do fluxo.
Por exemplo, considere que precisamos escrever um algoritmo que calcule um au-
mento salarial de 10% para todos os funcionários que ganham até R$ 2.500,00 reais.
Para esse problema sabemos que precisamos avaliar o salário de cada funcionário.
Neste caso, para um intervalo de valores de até R$ 2.500,00 o algoritmo executa um
conjunto de ações e para outro intervalo executa um outro conjunto de ações. Para
este tipo de situação, em que um determinado valor é avaliado para executar alguma
determinada ação, utilizamos as estruturas de condição.
Podemos ter três tipos de estrutura de condição: condição simples, condição com-
posta e condição múltipla. Vamos ver adiante estes três tipos.
se <expressão-lógica> então:
<bloco de comandos>
fim-se
Uma expressão (<expressão-lógica>) deve ter como resultado apenas dois valores pos-
síveis: verdadeiro ou falso. A instrução (<bloco de comandos>) só será executada se a
condição tiver o valor verdadeiro. Caso seja falso, a execução do programa ignora o
bloco de comando e continua na linha seguinte à estrutura de condição.
Imagine um algoritmo que determine se o aluno estará aprovado se sua média for
maior ou igual a 5.0 pontos, veja no exemplo de algoritmo como ficaria.
Aluno Aprovado
se <expressão-lógica> então:
senão:
fim-se
Aluno Aprovado
Senão
Aluno Reprovado
No exemplo acima está sendo executada uma condição que, se for verdadeira, exe-
cuta o comando “APROVADO”, caso contrário executa o segundo comando “REPRO-
VADO”. Podemos também dentro de uma mesma condição testar outras condições.
Como no exemplo abaixo, na figura 10.
<bloco de comandos>
fim-se
Este tipo de estrutura condicional tem a mesma função do se-então, com a diferença
que a estrutura de condição caso oferece a opção chamada padrão. O bloco de co-
mandos dentro da opção padrão será executado caso nenhuma dos casos informados
sejam atendidos. A sua utilização é demonstrada na Figura 11, onde o algoritmo rece-
be o número inteiro correspondente ao dia da semana e mostre por extenso este dia.
2.4.1 ENQUANTO-FAÇA
Neste caso, o bloco de operações será executado enquanto a expressão lógica (x)
for verdadeira, ou seja, antes de realizar a estrutura de repetição, uma condição x é
avaliada e caso o resultado da mesma for verdadeiro, os comandos que estão dentro
da estrutura serão executados. O teste da condição será sempre realizado antes de
qualquer operação e caso o resultado do teste de condição seja falso, o algoritmo sai
da estrutura de repetição e segue para a próxima linha.
2.4.2 FAÇA-ENQUANTO
2.4.3 FAÇA-PARA
Esta estrutura também serve para criar um laço de repetição e geralmente é utilizado
quando conhecemos a quantidade de vezes que queremos que as instruções sejam
repetidas. Esta estrutura contém um controle para determinar quando o laço será
finalizado. A estrutura é mostrada na Figura 14 e para ser utilizada precisa das infor-
mações básicas: valores de início, fim e incremento.
UNIDADE 3
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
> Compreender os
conceitos de tipos
primitivos
> Expressões
Comandos de
entrada e saída
3 ITENS FUNDAMENTAIS.
3.2 CONSTANTES
Exemplo:
b. pode estar ou não seguida de um ponto decimal (.) e outra sequência de dígitos;
Exemplo:
c. pode terminar ou não pela letra E seguida de outra sequência de dígitos. Entre
a letra E e esta outra sequência de dígitos pode existir ou não sinal positivo (+)
ou negativo (-).
Exemplo:
1. Observe que:
2. não pode haver espaço em branco entre os caracteres usados para representar
uma constante numérica;
4. se existir ponto decimal numa constante numérica, pelo menos um dígito deve
preceder o ponto decimal e pelo menos outro dígito deve sucedê-lo, sendo in-
válido escrever constantes como no exemplo a seguir:
Exemplo:
Exemplo:
Constantes inteiras são os números positivos ou negativos e que não possuem par-
te decimal compreendidos num intervalo. Esse tipo de dado, quando armazenado
na memória do computador, ocupa 2 bytes, por isso temos 28 x 28 = 2 * 16 = 65 536
possibilidades de representação dos números inteiros. O intervalo de valores inteiros
possíveis é de -32768 até + 32767.
Constantes reais podem ser positivos ou negativos e possuem parte decimal. Esse
tipo de dados, quando armazenado na memória do computador, ocupa 4 bytes, por
isso temos, 28 x 28 x 28 x 28 = 232 possibilidades de representação dos números reais. A
faixa de valores reais possíveis é muito maior de 6 a 11 dígitos significativos com sinal.
Nos números reais a parte decimal é separada da parte inteira por um .(ponto) e não
por uma , (vírgula).
Exemplos:
As duas constantes lógicas são representadas pelas palavras true (verdadeiro) e false
(falso) e são denominadas constantes booleanas. Esse tipo de dado, quando armaze-
nado na memória do computador, ocupa 1 byte, pois possui apenas duas possibili-
dades de representação.
São dados formados por uma cadeia de caracteres ou por apenas um único carac-
tere. Esses caracteres podem ser os números, os caracteres especiais (&, #, @, ?, +), as
letras maiúsculas e as letras minúsculas. Esses tipos de dado, ocupa um byte para
cada caractere quando armazenado na memória do computador.
As constantes literais predefinidas na linguagem PASCAL são formadas por uma se-
quência de caracteres aceitos pela implementação da linguagem, incluindo as 26
letras latinas
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,
os dígitos
0 1 2 3 4 5 6 7 8 9,
Exemplo:
3.3 VARIÁVEIS
Segundo Farrer (1999), a memória é utilizada para armazenar tanto as instruções dos
programas quando os dados utilizados pelos mesmos. Qualquer programa, para ser
executado, tem de estar na memória.
Uma variável pode assumir vários valores diferentes ao longo da execução do progra-
ma, mas, em um determinado momento, possui apenas um valor.
onde:
Exemplo:
a) Identificador permitidos:
A X5
Nota A32B
Matrícula F1G3H5
5B X-Y
E(13) Nota[1]
A:B B*D
Km/H Terca-feira
begin for of to
do in program while
Portanto, quando declaramos uma variável, devemos ter em mente quais valores se-
rão armazenados naquele espaço de memória. Uma variável pode ser de um dos
seguintes tipos:
• Tipo inteiro: Qualquer número inteiro, negativo, nulo ou positivo. Por exemplo,
se precisarmos de uma variável para armazenar a idade dos funcionários, o
tipo ideal para essa variável seria inteiro.
• Tipo real: Qualquer número real, ou seja, valores com ponto decimal. Esse seria
o tipo ideal para armazenar o salário dos funcionários.
• Tipo caractere: Variável do tipo literal caractere para armazenar um único ca-
ractere, que pode ser uma letra ou um símbolo. Por exemplo, para identificar
o sexo do funcionário.
• Tipo cadeia: Variável do tipo literal cadeia para armazenar uma sequencia de
caracteres, ou seja, uma palavra. Por exemplo, para armazenar o nome do fun-
cionário.
• Tipo Lógico: Variável para armazenar valores lógicos. Será sempre verdadeiro
ou falso.
Sua sintaxe é:
var
nome_variavel2 : tipo_variavel3
3.4 COMENTÁRIOS
À medida que um programa cresce, ele vai ficando cada vez mais difícil de ser lido
e consequentemente de ser entendido. Para tornar o algoritmo legível e claro, deve-
mos colocar textos que expliquem o raciocínio adotado durante seu desenvolvimen-
to para que outras pessoas, ou o próprio desenvolvedor, ao ler o programa mais tarde,
não tenhamos dificuldades em entender sua lógica. Esses textos são chamados de
comentários.
integer;
c: {conceito final}
string;
Os comentários de bloco são iniciados por /* e finalizados por */. Tudo o que estiver
entre esses dois símbolos são considerados comentários. Os comentários de bloco
podem ocupar várias linhas.
• O sinal de multiplicação é indicado com asterisco (*) e nunca deve ser suben-
tendido;
2ª -> +-
• Div e mod são operadores que só podem ser aplicados com operandos intei-
ros dando, respectivamente, o quociente inteiro (obtido por divisão e trunca-
mento, sem arredondamento) e o resto inteiro da divisão entre operandos.
11 div 4 dá resultado 2
11 mod 4 da resultado 3
O resultado obtido de uma relação é sempre um valor lógico. Por exemplo, analisan-
do a relação numérica A + B = C, o resultado será verdade ou falsidade à medida que
o valor da expressão aritmética A + B seja igual ou diferente do conteúdo da variável
C, respectivamente.
A <> B
Nome == “Bruno”
X=1
Com as declarações:
var x, y, z: integer;
VARIÁVEIS RELAÇÕES
x y z Cor Nome X*X+Y>Z Cor = 'Azul' Nome <> 'Jose'
1 2 5 'azul' 'Paulo' false true true
4 3 1 'verde' 'Jose' true false false
1 1 2 'branco' 'Pedro' false false true
1 2 1 'azul' 'Jose' true true false
A álgebra das preposições define três conectivos usados na formação de novas pre-
posições a partir de outras já conhecidas. Esses conectivos são os operadores nas
expressões lógicas:
or para a disjunção
3.6.2 PRIORIDADE
Como foi mostrado anteriormente, pode-se ter mais de um operado lógico na mes-
ma expressão, além dos operadores de relação e dos operadores aritméticos. Em
PASCAL, a prioridade das operações está dada na tabela 3.
PRIORIDADE OPERADORES
1ª not
2ª *, /, div, mod, and
3ª +, - , or
4ª =, <>, <, <=, >=, >, in
Onde:
No exemplo a seguir:
lógico: A, B
inteiro: X;
A <- B;
X <- 4 + 6 div 2;
B <- 5 = 3;
X <- 2;
Nos comandos em que o valor a ser atribuído à variável é representado por uma ex-
pressão aritmética ou lógica, estas devem ser resolvidas em primeiro lugar, para que
depois o resultado possa ser armazenado na variável.
Notemos também que uma variável pode ser utilizada diversas vezes. Ao atribuirmos
um segundo valor, o primeiro valor armazenado será descartado, sendo substituído
pelo segundo.
Para que o algoritmo possa receber os dados de que necessita, adotaremos o coman-
do entrada de dados denominado leia, em PASCAL read e readln, cuja finalidade é
atribuir o dado a ser referenciado à variável identificada.
No PASCAL, a diferença entre read e readln é que neste último, após a leitura dos va-
lores correspondentes à lista-de-identificadores, provoca uma mudança de linha na
unidade de entrada utilizada.
nota: real;
01 60.05 02 88.6
03 84.04 04 54.00
cada linha dispondo de quatro valores, então quatro execuções repetidas do coman-
do readln (num, nota);
01 03
60.05 84.04
01 02 03 04
Para que o algoritmo possa mostrar os dados que calculou, como resposta ao proble-
ma que resolveu, adotaremos um comando de saída de dados denominado escreva,
cuja finalidade será apresentar o valor da variável identificada. No PACAL, os coman-
dos de saída possuem a forma write e writeln a diferença básica entre os comandos
é que no writeln após a leitura dos valores correspondentes à lista-de-identificadores,
provoca uma mudança de linha na unidade de saída utilizada.
Exemplos:
UNIDADE 4
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
4 CONSTRUÇÃO DE
ALGORITMOS POR
REFINAMENTOS
SUCESSIVOS.
4.2 SUB-ROTINAS
Em geral, não existem regras específicas para a criação das sub-rotinas, porém, al-
guns critérios devem ser analisados:
Uma sub-rotina não pode ser executado diretamente como um programa. Ele pre-
cisa ser “invocado” ou “chamado” de algum ponto do código do programa como um
todo do qual faz parte. Se durante a execução do fluxo de instruções atingir um pon-
to de invocação, ou seja, quando em um determinado ponto do algoritmo contém
o mesmo identificador utilizado na definição da sub-rotina, o fluxo de controle da
execução passa para a sub-rotina, e, ao ser concluída a execução deste subprograma,
o fluxo de controle é retomado imediatamente após o ponto de invocação do algo-
ritmo de origem.
Como pode ser observado no fluxo de execução do algoritmo na Figura 16, assim
que for encontrado um identificador contendo o mesmo nome da sub-rotina, neste
exemplo, Primeiro e Segundo, o fluxo é direcionado para as respectivas sub-rotinas e
no final de cada trecho, retorna-se para o algoritmo principal.
O comando “determine o salário” pode ser refinado como a seguir (FIG. 18):
Observe que no refinamento realizado acima, não foi detalhado como seria a lógica
dos cálculos de vantagens e das deduções. Estas ações serão definidas adiante em
sub-rotinas específicas, conforme a Figura 19.
Quando trabalhamos com várias sub-rotinas, nos deparamos com questões relativas
à visibilidade das variáveis em diferentes partes do programa. Ou seja, se uma variável
é visível ou acessível em certas partes de um programa.
Para entendermos como funciona a visibilidade das variáveis, precisamos falar sobre
o escopo. O escopo ou abrangência de uma variável é a parte do programa na qual
ela é visível e pode ser acessada. A visibilidade refere-se a hierarquia, ou seja, uma va-
riável é global quando e visível e acessada por todas as sub-rotinas inferiores, e local,
quando é visível apenas em seu contexto e não nas sub-rotinas superiores.
Em outra situação temos a variável X, que é local ao módulo 1, sendo visível também
no módulo 2, podendo ser definida relativamente como global ao módulo 2.
4.4 PROCEDIMENTOS
Imagine se essa empresa possuir mais de 30 setores e decida realizar esse cálculo?
A repetição de trechos de código em um programa é considerada uma péssima prá-
tica. Imagine que após a replicação deste mesmo trecho de código para os 30 se-
tores, é identificado um erro no cálculo. A resposta para a eliminar a repetição de
código chama-se: procedimentos e funções.
O identificador é o nome dado ao procedimento, este deverá ser único e não confli-
tante com as demais declarações (como variáveis e tipos). A lista de parâmetros, que
pode ser nula ou até mesmo vazia, lista os parâmetros formais do procedimento (de-
talhados nas próximas seções). As declarações de variáveis internas correspondem a
quaisquer declarações, seja de tipos, variáveis, sub-rotinas e etc.
Dentro do procedimento podem ser utilizadas tanto variáveis locais quanto variáveis
globais. Todas as variáveis locais aos procedimentos são alocadas somente quando
o procedimento entra em execução, mas são liberadas quando o procedimento ter-
mina, perdendo assim, seus conteúdos. Caso seja necessário o aproveitamento dos
dados manipulados, o procedimento deverá utilizar as variáveis globais (FIG.28)
4.5 FUNÇÕES
Outro tipo de sub-rotina são as funções. Uma função possui o mesmo objetivo de
um procedimento, ou seja, desviar a execução do algoritmo principal para realizar
uma tarefa específica, com uma única diferença, uma function sempre retorna um
valor.
Diferente dos procedimentos, uma função não deverá simplesmente ser chamada,
mas deverá ser atribuída à alguma variável. Uma função deve ter um nome e um tipo,
e sua sintaxe é mostrada abaixo (FIG. 29)
O nome da função é quem deve assumir o valor da função, como se fosse uma variá-
vel. O tipo corresponde ao tipo do valor que será retornado. NOME é o próprio nome
dado à função. Já a lista-de-parâmetros-formais, corresponde à lista dos objetos que
serão substituídos por outros objetos, fornecidos quando da chamada da função.
Assim como nos procedimentos, uma função deve ser definida dentro do espaço
de declaração de variáveis do programa. Para que a função possa retornar um valor,
este deverá ser explicitamente atribuído ao nome da função, dentro da rotina da fun-
ção.
Agora criamos uma função que calcula a média das notas com base nos parâmetros
recebidos, e devolve o valor da média ao invés de fazer a impressão. A função deve
ser definida usando o tipo de retorno que ela irá fornecer (no caso do exemplo, valor
de tipo real) além dos parâmetros que são opcionais. Dentro do bloco da função,
devemos usar uma instrução que indique o valor que será retornado (nas linguagens
geralmente a palavra é return para retornar um valor). Em pseudocódigo, a represen-
tação usada é de que o nome da função recebe o valor, isso quer dizer que quando
chamarmos essa função podemos atribuí-la a uma variável de tipo real, pois ela irá
conter um valor de resposta.
Na passagem de parâmetros por valor não existe ligação entre o parâmetro e a variá-
vel usada na chamada. Esta variável poderá ser utilizada e alterada dentro da função
sem afetar a variável da qual ela foi gerada, pois esta possui sua própria área de me-
mória para armazenamento.
N! = N x (N -1) x … x 2 x 1
N! = N x (N -1)!
Seguindo essa ideia, podemos escrever uma função recursiva que calcula o valor do
fatorial de um número. Como a função chama a si mesma, é muito importante saber
quando a cadeia de chamadas da função a ela mesma termina. No caso do fatorial,
quando queremos calcular o fatorial do número 1 ou do número 0, podemos respon-
der diretamente: 1! = 0! = 1, sem usar a recursividade. Esse é o chamado caso-base
da recursão. Sem isso, a função nunca pararia de chamar a si mesmo, e o programa
seria finalizado com algum erro dependente do sistema operacional e do compilador
utilizados (FIG.36).
UNIDADE 5
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
> Compreender um
conjunto amplo
de algoritmos para
realizar a mesma
tarefa, cada um deles
com uma vantagem
particular sobre os
outros, dependendo da
aplicação.
5 CONSTRUÇÃO DE
ALGORITMOS BÁSICOS:
ORDENAÇÃO INTERNA.
5.2 ORDENAÇÃO
Quando no processo de ordenação a ordem relativa dos itens com chaves iguais
mantém-se inalterada pelo processo de ordenação, é dito um método de ordenação
estável. Por exemplo, se uma lista alfabética de nomes de alunos de turma é ordena-
da pelo campo nota, então um método estável produz uma lista em que os alunos
com a mesma nota aparecem em ordem alfabética. Alguns dos métodos de ordena-
ção mais eficientes não são estáveis.
5.2.1 MEMÓRIA
Existem vários tipos de memórias para que o computador tenha o seu funcionamen-
to adequado onde estas apresentam diferentes características devido às diferentes
tecnologias utilizadas em suas fabricações. Nos próximos tópicos são apresentados os
dois tipos mais comuns de memórias: a principal e a secundária.
Fávero (2011), afirma que a memória principal é denominada memória RAM (Ran-
dom Access Memory), corresponde a um tipo de memória volátil, ou seja, seu conteú-
do fica armazenado enquanto o computador estiver ligado (energizado); ao desligar
a corrente elétrica, o conteúdo da memória RAM é apagado. Esse é o motivo pelo
qual muitas pessoas perdem arquivos que estão utilizando quando ocorrem fatos
como, por exemplo, alguém esbarrar no cabo ligado à tomada de energia elétrica ou
mesmo cessar o fornecimento de energia.
De acordo com Monteiro (2007), a memória secundária pode ser constituída por di-
ferentes tipos dispositivos, alguns diretamente ligados ao sistema para acesso ime-
diato (ex.: discos rígidos) e outros que podem ser conectados quando desejado (ex.:
pen-drive, CD/DVD).
Willrich et al. (2010) afirmam que muitas vezes os programas precisam manipular
uma quantidade de dados tão grande que não cabem na memória principal. Nesse
caso, esses dados são armazenados em arquivos que são lidos da memória secundá-
ria e processados por partes. Muitas vezes esses dados podem até caber na memória
principal, mas por uma questão de organização ficam armazenados em arquivos.
Os métodos simples são mais eficientes para pequenos arquivos, uma vez que, ape-
sar de os métodos mais sofisticados usarem menos comparações, estas comparações
são mais complexas nos detalhes.
primeira posição do vetor. Repita essas duas operações com os n-1 itens restantes,
depois com os n-2 itens, até que reste apenas um elemento. O método é ilustrado
abaixo na Figura 37 e o algoritmo na Figura 38, os números sublinhados representam
a sequência destino:
A ordenação por Inserção é quase tão simples quanto o algoritmo de ordenação por
Seleção, e além disso é um método estável, pois deixa os registros com chaves iguais
na mesma posição relativa.
O método consiste em cada passo, a partir de i=2, o i-ésimo item da sequência origi-
nal é selecionado e transferido para a sequência destino, sendo colocado na posição
correta. O método é ilustrado utilizando a mesma sequência utilizada anteriormente,
os números sublinhados representam a sequência de destino (FIG.39)
A razão pela qual este método é eficiente ainda não foi determinada, porque é difícil
analisar o algoritmo. A sua análise contém alguns problemas matemáticos muito di-
fíceis, a começar pela própria sequência de incrementos, mas cada incremento não
deve ser múltiplo do incremento anterior.
Shellsort é uma ótima opção para arquivos de tamanho moderado (da ordem de
5000 registros), mesmo porque sua implementação é simples e requer um conjunto
de códigos pequeno. O tempo de execução do algoritmo é sensível à ordem inicial
do arquivo, além do que o método não é estável, pois ele nem sempre deixa os regis-
tros com chaves iguais na mesma posição relativa.
Segundo Ziviani (2005), este é o método de ordenação interna mais rápido que se co-
nhece para uma ampla variedade de situações, sendo provavelmente mais utilizado
do que qualquer outro algoritmo. É um método de ordenação por troca, onde a ideia
básica é dividir o problema de ordenar um conjunto com n itens em dois problemas
menores. Na sequência, os problemas menores são ordenados independentemente
e depois os resultados são combinados para produzir a solução do problema maior.
A parte mais delicada deste método é relativa ao procedimento Partição, o qual tem
que rearranjar o vetor A através da escolha arbitrária de um item x do vetor denomi-
nado pivô, de tal forma que ao final o vetor A está particionada em uma parte es-
querda com os elementos menores ou iguais a x e uma parte direita com elementos
maiores ou iguais a x.
2. percorra o vetor a partir da esquerda até que um elemento A[i] >= x é encontra-
do; da mesma forma percorra o vetor a partir da direita até que um elemento
A[j] <= x é encontrado;
3. como os dois elementos A[i] e A[j] estão fora do lugar no vetor final, então
troque-os de lugar;
Após obter os dois pedaços do vetor por meio do procedimento Partição, cada pe-
daço é ordenado recursivamente. O refinamento final do procedimento Quicksort é
mostrado no algoritmo da Figura 45. O procedimento Ordena é recursivo, e o vetor A
é global aos procedimentos Partição e Ordena.
A Figura 46, ilustra o que acontece com o vetor exemplo em cada chamada recursiva
do procedimento Ordena. Cada linha mostra o resultado do procedimento Partição,
em que o pivô é mostrado em negrito.
• Repita estas duas operações com os (n-1) itens restantes, depois com os (n-2)
itens e assim sucessivamente.
O custo para encontrar o menor (ou o maior) item entre os n itens custa (n-1) compa-
rações. Este custo pode ser reduzido através da utilização de uma estrutura de dados
denominada fila de prioridades, esse assunto será tratado em outra unidade.
As operações mais comuns são: Adicionar um novo item ao conjunto e extrair o item
do conjunto que contenha o maior (ou o menor) valor.
Um tipo abstrato de dados fila de prioridades, contendo registros com chaves numé-
ricas (prioridades), deve suportar as operações:
4. Substitui o maior item por um novo item, a não ser que o novo item seja maior;
Uma representação para a fila de prioridades é uma lista linear ordenada. Neste caso,
a operação Constrói leva o tempo O(n log n), Insere é O(n) e Retira é O(1). Outra repre-
sentação é através de uma lista linear não ordenada, na qual a operação Constrói tem
custo linear e é de O(1), Retira é O(n) e Ajunta é O(1) para implementações através
de apontadores e O(n) para implementações através de arranjos, onde n representa
o tamanho da menor fila de prioridades.
5.2.2.7 HEAPS
Uma estrutura de dados eficiente para suportar as operações Constrói, Insere, Retira,
Substitui e Altera um heap. É definido como uma sequência de itens com chaves:
tal que
Esta ordem é visualizada se a sequência de chaves for desenhada em uma árvore bi-
nária, onde as linhas que saem de uma chave levam a duas chaves menores no nível
inferior conforme ilustra a Figura 47.
Esta estrutura é conhecida como uma árvore binária completa: o primeiro nodo é
chamado raiz, os nodos abaixo de cada nodo são chamados nodos filhos e o nodo
acima é chamado de nodo pai. Uma árvore binária completa pode ser representada
por um vetor.
5.2.2.8 HEAPSORT
Um método elegante e que não necessita de memória auxiliar foi apresentado por
Floyd (1964) dado um vetor A[1], A[2], ..., A[n], os itens A[n/2+1], A[n/2+2], ..., A[n] for-
mam um heap, porque neste intervalo do vetor não existem dois índices i e j tais que
j=2i ou j=(2i+1). A construção do heap:
1 2 3 4 5 6 7
O R D E N A S
b) estendendo o heap para a esquerda (Esq=3), englobando o item A[3], pai dos itens
A[6] e A[7], aqui a condição do heap é violada e os itens A[3] e A[7] são trocados;
Esq = 3
1 2 3 4 5 6 7
O R S E N A D
Esq = 2
1 2 3 4 5 6 7
O R S E N A D
d) finalmente, estendendo o heap a esquerda (Esq= 1), englobando o item A[1], pai
dos itens A[2] e A[3], a condição é violada e os itens A[1] e A[3] são trocados, encer-
rando o processo.
Esq = 1
1 2 3 4 5 6 7
S R O E N A D
Inicialmente, o algoritmo não parece eficiente, pois as chaves são movimentadas vá-
rias vezes. Entretanto, o procedimento Refaz gasta cerca de (log n) operações no pior
caso. Portanto, Heapsort gasta um tempo de execução proporcional a (n log n) no
pior caso.
Heapsort não é recomendado para arquivos com poucos registros, por causa do tem-
po necessário para construir o heap, bem como porque o anel interno do algoritmo
é bastante complexo, se comparado com o anel interno do Quicksort. O quicksort é,
em média, duas vezes mais rápido que o Heapsort. Entretanto, o Heapsort é melhor
que o Shellsort para grandes arquivos. Deve-se observar que o comportamento do
Heapsort é O(n log n) qualquer que seja a entrada. Aplicações que não podem tolerar
um caso desfavorável devem usar o Heapsort. Um aspecto negativo é que o método
não é estável.
A ordenação interna é usada quando todos os registros do arquivo (ou lista ou vetor)
cabem na memória principal. Usando dois métodos simples (seleção e inserção) que
requerem O(n2) comparações e três métodos eficientes (shellsort, quicksort e heap-
sort) que requerem O(n log n) comparações em seus casos médios para uma lista
com n elementos.
9. O Shellsort é o método escolhido para a maioria das aplicações por ser muito
eficiente para um conjunto de elementos de até 10000 elementos. Sua imple-
mentação é simples e fácil de colocar funcionando corretamente e geralmente
resulta em um programa pequeno;
10. O Quicksort é o algoritmo mais eficiente que existe para uma grande variedade
de situações. Entretanto, deve-se procurar uma implementação estável. O algorit-
mo é recursivo, o que demanda uma pequena quantidade de memória adicional;
Quando os registros do arquivo são muito grandes é desejável que o método de or-
denação realize o menor número de movimentação com os registros, podendo ser
usada uma ordenação indireta.
UNIDADE 6
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
6 CONSTRUÇÃO DE
ALGORITMOS BÁSICOS:
INTERCALAÇÃO,
MANIPULAÇÃO DE
CARACTERES E ARRAYS
6.1 APRESENTAÇÃO DA UNIDADE
Considere um arquivo contendo n registros (neste caso cada registro contém apenas
uma palavra) e uma memória interna de m palavras. A passada inicial sobre o arqui-
vo produz n/m blocos ordenados (se cada registro contiver k palavras, k > 1, então
teríamos n/m/k blocos ordenados.) Seja P uma função de complexidade tal que P(n)
é o número de passadas para a fase de intercalação dos blocos ordenados, e seja f o
número de fitas utilizadas em cada passada. Para uma intercalação-de- f -caminhos
o número de passadas é
P(n)= logf n
m
P(n)= log3 22 = 2
3
Para uma intercalação-de- f -caminhos foram utilizadas 2f fitas nos exemplos acima.
Para usar apenas f + 1 fitas basta encaminhar todos os blocos para uma única fita e,
com mais uma passada, redistribuir estes blocos entre as fitas de onde eles foram lidos.
No caso do exemplo de 22 registros apenas 4 fitas seriam suficientes: a intercalação
dos blocos a partir das fitas 1, 2 e 3 seria toda dirigida para a fita 4; ao final, o segundo
e o terceiro blocos ordenados de 9 registros seriam transferidos, de volta para as fitas 1
e 2, e assim por diante. O custo envolvido é uma passada a mais em cada intercalação.
char cidade[30];
char empresa[20];
char nome[100];
Uma string em C tem um caractere especial que determina o seu fim. Esse caracte-
re nulo (\0), e ele é inserido automaticamente pelo compilador no último elemento
do vetor de elementos do tipo char. Com isso, deve-se levar isso em consideração na
hora de declarar suas strings. Assim sendo, se você deseja que uma string armazene
N caracteres, você deve declará-la com um tamanho N+1, pois a última posição con-
terá caractere nulo. Esse caractere não precisa ser declarado manualmente, isso feito
de forma automática pelo compilador.
F U T E B O L \0
0 1 2 3 4 5 6 7
A declaração deste vetor será: char Palavra[8]. Observe que na última posição do ve-
tor é inserido o caractere nulo. A variável Palavra, quando é declarada, pode ocu-
par qualquer espaço livre na memória, entretanto, todas as posições deste vetor ocu-
pam posições de memória adjacentes, onde, cada caractere ocupa 1 byte.
Assim como as variáveis e vetores, uma string pode ser inicializada no momento da
sua criação ou após a sua criação, veja os exemplos a seguir.
str1=
'A' 'L' 'G' 'O' 'R' 'I' 'T' 'M' 'O' '\0' '\0'
0 1 2 3 4 5 6 7 8 9 10
Em C, para capturar o valor de um caractere fornecido pelo usuário via teclado, usa-
mos a função scanf, com o especificador de formato %c.
char nome;
...
scanf(“%c”, &nome);
...
Diante disso, se o usuário digitar a letra b, por exemplo, o código associado à letra b
será armazenado na variável nome. Vale ressaltar que, diferente dos especificadores
%f e %d, o especificador %c não pula os caracteres brancos, um caractere branco
pode ser: um espaço (‘ ‘), um caractere de tabulação (‘\t’) ou um caractere de nova
linha (‘\n’). Portanto, se o usuário digitar um espaço antes da letra b, o código do es-
paço será capturado e a letra b será capturada apenas numa próxima chamada da
função scanf. Se desejarmos pular todas as ocorrências de caracteres brancos que
porventura antecedem o caractere que queremos capturar, basta incluir um espaço
em branco no formato, antes do especificador.
char nome;
...
scanf(“ %c”, %nome); /* o branco no formato pula brancos da entrada */
...
Para que nomes compostos possam ser lidos (nome de cidades, pessoas, endereços
para correspondência, etc.), o especificador no formato %[...] poderá ser utilizado, no
qual todos os caracteres aceitos na leitura deverão estar listados entre colchetes. As-
sim, o formato “%[aeiou]” lê sequências de vogais, isto é, a leitura prossegue até que
se encontre um caractere que não seja uma vogal. Se o primeiro caractere entre col-
chetes for o acento circunflexo (^), teremos o efeito inverso (negação). Assim, com o
formato “%[^aeiou]” a leitura prossegue enquanto uma vogal não for encontrada. Esta
construção permite a captura de nomes compostos. Consideremos o código abaixo:
char nome[51];
...
scanf(“ %[^\n]”, nome);
...
No exemplo acima, a função scanf lê uma sequência de caracteres até que seja encon-
trado o caractere de mudança de linha (‘\n’), ou seja, até que o usuário tecle “Enter”.
A inclusão do espaço no formato (antes do sinal %) garante que eventuais caracteres
brancos que precedam o nome serão ignorados. Como o vetor foi dimensionado com
51 elementos, o trecho de código acima produzirá um erro se o usuário fornecer uma
linha que tenha mais de 50 caracteres, pois utilizará um espaço de memória que não
foi reservado.
Para eliminar esta possível invasão, podemos limitar o número máximo de caracteres
que serão capturados.
char nome[51];
...
scanf(“ %50[^\n]”, nome); /* lê no máximo 50 caracteres */
…
• E etc.
Esta função tem como objetivo receber uma string via parâmetro e retornar a quan-
tidade de caracteres da string ou seja o tamanho da string excluindo o caracter ‘\0’.
Exemplo:
Essa função permite realizar a cópia do conteúdo da string s2 para a string s1.
Exemplo:
Exemplo:
Essa função permite comparar duas strings. A função devolve um inteiro negativo se
str1 for menor alfabeticamente em relação a str2. A função devolve 0 (zero) se str1
for igual a str2. A função devolve um inteiro positivo se str1 for maior alfabeticamente
em relação a str2.
Exemplo:
int numero;
Na linha 4, declaramos duas strings: var1 e var2. Nas linhas 6 e 7, utilizamos o coman-
do gets() para que o usuário informe as duas strings que serão armazenadas nas va-
riáveis mencionadas. As linhas 9 e 10 imprimem os tamanhos das strings var1 e var2,
enquanto que a linha 12 é responsável por concatenar as strings var2 e var1, ou seja,
var1 passa a ter o conteúdo anterior com a adição do conteúdo de var2. Por fim, a
linha 13 exibe o tamanho de var1 após a sua concatenação com var2.
6.4 ARRAYS
Até agora vimos que uma variável armazena apenas um único valor por vez.
Por exemplo, em um programa para ler o salário de vários colaboradores de uma
empresa, cada salário é armazenado em uma variável, e assim que os salários de
um novo colaborador são lidos, os salários do colaborador anterior são perdidos. Em
algumas situações, é bem comum a necessidade de armazenar um conjunto de da-
dos onde os valores lidos não podem ser esquecidos ou perdidos à medida que o
processamento for ocorrendo. Diante desta situação, seria inviável declarar uma va-
riável distinta para armazenar cada valor, quando a quantidade de valores a serem
manipulados for relativamente grande. Para situações como essas utilizamos arranjos
(arrays), que consistem em estruturas de dados (ou variáveis compostas homogêneas)
capazes de agrupar em uma única variável vários elementos de um mesmo tipo e
permitir facilmente o acesso a cada item em particular.
Os arranjos podem ter diferente dimensões, onde um tipo especial de arranjo com
apenas uma dimensão é chamado de vetor. Um arranjo é uma única variável, a qual é
formada por um conjunto homogêneo (isto é, do mesmo tipo) de valores. Para iden-
tificar cada valor é usado um número inteiro, que corresponde à posição do valor em
relação aos demais. Este número, apropriadamente, é chamado de índice.
tipo_vetor nome_vetor[quantidade_elementos];
onde:
int cpf[11];
char placa[8];
float valor[30];
Um vetor ao ser criado, como qualquer outra variável, contém em suas várias posições
valores indefinidos (“lixo”). Semelhante a outras variáveis, a utilização do seu conteúdo
faz sentido apenas se já houver sido feita uma leitura ou uma atribuição àquela posição.
int lista[5]
lista[i] ← lista[i – 1] * 3 + 5
fim-para
Tabela 6. Ilustração passo a passo dos valores de um arranjo para atribuições sucessivas.
Um vetor é como uma coleção de caixas numeradas. Cada caixa é capaz de armaze-
nar um valor e tem o seu “endereço”. O “endereço” de cada caixa é conhecido como
índice e serve para identificar qual posição do vetor queremos acessar. Por exemplo:
se precisarmos armazenar a idade de 10 crianças podemos declarar um vetor de 10
posições do tipo int. A declaração do nosso vetor ficaria assim: int idade[10]. Na se-
quência, a Figura 53 representa esse vetor de idade:
No vetor acima temos, por exemplo, armazenado do índice “4” o valor “24”. Observe,
que declaramos um vetor de 10 posições, os índices variam de 0 a 9.
nome_do_vetor[índice]
Desta forma, para armazenar o valor 34 na segunda posição do vetor, seria utilizado
o comando: idade[1] = 34.
Figura 55. Algoritmo para identificar o menor e o maior número dos informados.
A=
20 100 0 30 -60 10 0 0
0 1 2 3 4 5 6 7
i Menor Maior
20 20
7 0 20
6 0 20
5 0 20
4 -60 20
3 -60 30
2 -60 30
1 -60 100
0 -60 100
-1
Menor = -60
Maior = 100
6.5 MATRIZES
Como já foi dito anteriormente, os arranjos podem ter várias dimensões. Quando pos-
suem mais de uma dimensão, eles são chamados de matrizes, também conhecido
como conjuntos bidimensionais. O conceito de matrizes em programação é bem se-
melhante ao da matemática. A Figura 55 a seguir apresenta o formato de uma matriz
m x n, onde m é representa o número de linhas e n o número de colunas.
As matrizes possuem:
int matriz[4][3];
char nomes[4][5];
UNIDADE 7
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
7 ARQUIVOS SEQUENCIAIS E
DIRETOS
7.1 APRESENTAÇÃO DA UNIDADE
7.2 ARQUIVOS
Uma forma de contorno este tipo de problema é trabalhar com uma nova estrutura:
os arquivos, que tem como objetivo principal realizar o armazenamento de grandes
quantidades de informação por um grande período de tempo. Este, é também uma
alternativa para estruturas de dados que não podem ou não necessitam estar aloca-
das no ambiente do algoritmo. Esta estrutura de dados geralmente fica alocada fisi-
camente em um meio secundário, sendo mais comum o uso de meios magnéticos
como fitas, discos ou disquetes.
Utilizaremos como exemplo a carteira de identidade de uma pessoa. Como pode ser
visto na Figura 57. Nela encontram-se alguns dados básicos do cidadão: nome, filia-
ção, naturalidade, data de nascimento e etc. O conjunto de informações apresentado
por estes dados formam um registro:
A relação lógica entre estas informações é a sua associação a uma pessoa. Quando
reunimos várias destas carteiras de identidades dos membros de uma família, poderá
formar por exemplo, um arquivo.
Como o arquivo pode ser armazenado em uma memória secundária o torna inde-
pendente de qualquer algoritmo. Isto é, um arquivo pode ser criado, consultado, pro-
cessado e eventualmente removido por algoritmos distintos.
As operações mais comuns que podem ser feitas em um arquivo através de algorit-
mo são: obtenção de um registro do arquivo, inserção de um novo registro, modifica-
ção do registro ou exclusão de um registro.
7.4 DECLARAÇÃO
Como o arquivo é um conjunto de registros, é necessário definir o registro que com-
põe o arquivo primeiro, para somente então definir o arquivo. De acordo com o exem-
plo da identidade citado anteriormente, cada identidade é formada por um registro:
onde:
fim registro;
identidade: ident;
arqIdent: fami;
em que:
Na vida real, não se pode obter alguma informação contida em uma gaveta sem an-
tes abri-la, isso é feita através do seguinte comando:
abra ( IdArquivo ) ;
em que:
Exemplo:
abra (FAMI)
abra (AGENDA)
Não devemos manter uma gaveta aberta depois de usá-la, isso deixaria o seu con-
teúdo exposto em que podem danificá-lo. Por isso, convém sempre fechar o arquivo
após sua utilização com o comando:
fecha ( IdArquivo ) ;
em que:
Exemplo:
feche (FAMI)
feche (AGENDA)
Um arquivo geralmente não deve ser consumido, e sim consultado. Para tal, precisa-
mos copiar o conteúdo que nos interessa em algum lugar. Utilizamos então:
em que:
Exemplo:
A cópia é realizada de algum lugar para outro. Neste comando, copiam-se as infor-
mações da posição do arquivo para o registro especificado no comando, o qual pos-
sui um formato idêntico ao do registro que compõe o arquivo.
Podemos observar neste fluxo que sempre parte da variável de arquivo para a variável
de registro, ou seja, é a variável de registro que recebe o resultado da operação.
Da mesma maneira que os registros são lidos de um arquivo, também devemos gra-
var registros em um arquivo. Para armazenar um registro no arquivo, faz-se necessário
que ele possua estruturação de campos idênticos à dos registros já armazenados.
Para tal, temos:
em que:
Exemplo:
guarde(FAMI, AUX);
elimine( IdArquivo ) ;
em que:
Exemplo:
elimine(FAMI);
avance ( IdArquivo ) ;
em que:
fda( IdArquivo ) ;
em que:
Esse comando retorna verdadeiro quando a posição corrente é o fda (Fim do Arqui-
vo), caso contrário, retorna falso. O algoritmo para armazenar um novo telefone na
agenda do usuário utilizando esses comandos pode ser observado na Figura 58:
No algoritmo da Figura 60, exibe trecho de código onde se faz necessário atualizar o
telefone de um contato já salvo na agenda. Neste caso, o registro do usuário tem que
ser localizado no arquivo, o seu conteúdo tem que ser copiado para um registro auxi-
liar, e em seguida, o campo fone deverá ser atualizado para o novo número. Com este
registro auxiliar atualizado, basta gravá-lo na mesma posição em que se encontrava
antes, substituir o registro antigo do arquivo pelo registro atualizado (auxiliar).
Como pode ser visto na linha 22 do algoritmo acima, ao realizar a exclusão de registro
de arquivo, é recomendável solicitar ao usuário uma confirmação para a operação de
exclusão, pois após excluído não haverá mais volta.
Se a organização for direta, as disponibilidades dos registros no arquivo não são ne-
cessariamente apresentadas pelo arquivo sequencial. Ou seja, os registros não ficam
armazenados na ordem em que são gravados, por meio da chave, cada registro será
locado em uma posição reservada.
Para exemplificar uma organização direta, suponha que o gestor do RH da sua em-
presa deseja armazenar informações dos colaboradores, como o nome do colabora-
dor e salário. Para tal, ele utiliza como chave a matrícula do colaborador, esta matri-
cula também faz parte do registro e é única, ou seja, cada colaborador possui apenas
uma matrícula, não havendo chances de existirem mais de um colaborador com a
mesma matrícula.
Na medida que novos colaboradores são admitidos pela empresa, o gestor precisa
cadastrá-los no arquivo. A posição é determinada pela chave de acesso (matrícula do
colaborador) e para utilizar a chave para localizar e mover para a posição no arquivo,
utilizamos o código:
em que:
Observando a Figura 66, logo após o registro ser recuperado pela chave (linha 13)
utilizando-se o comando posicione, o registro está apto a ser manipulado. Observe
que não foi necessário percorrer todos os registros do arquivo na busca da matrícula
informada, como na busca sequencial.
UNIDADE 8
OBJETIVO
Ao final desta
unidade,
esperamos
que possa:
8 DEPURAÇÃO E TESTES DE
ALGORITMOS.
8.2 TESTES
Inthurn (2001), afirma que há um gasto frequente muito grande de tempo e dinheiro
em correções de software, em que o desenvolvimento não foi adequado e a fase de
testes não foi satisfatória. Assim, entende-se que os testes são importantes, também,
para evitar surpresas desagradáveis no futuro. Além disso, os testes são responsáveis
por mostrar que o software possui erros e que os mesmos devem ser corrigidos, mas
isso não significa que a partir das correções, o software estará imune a possíveis falhas
posteriores. Novos erros podem ser inseridos ao software no momento em que outros
erros são corrigidos, através de alterações realizadas no código a fim de corrigir os er-
ros descobertos até o momento.
Os defeitos nos softwares ocorrem porque que os escrevem são pessoas humanas, e
por sua vez sabemos que as pessoas não são perfeitas e estão sujeitas a falhas. Entre-
tanto, elas têm habilidades para tal responsabilidade, mas também não são perfeitas,
o que nos leva a admitir que são suscetíveis de cometer erros. E muitas vezes, quem
demanda o software não tem total clareza da sua real necessidade, fazendo com que
os softwares sejam desenvolvidos sobre crescente pressão para entregá-los em pra-
zos rigorosos, sem tempo para checar as atividades realizadas.
Também conhecida como teste unitário ou teste de módulo, é a fase em que se va-
lida as menores unidades de software desenvolvidas (pequenas partes ou unidades
do sistema) como: uma classe de métodos, funções isoladas e unidades lógicas. O
objetivo é o de encontrar falhas de funcionamento dentro de uma pequena parte do
sistema funcionando independentemente do todo. Medeiros (2005), afirma que este
tipo de teste deve ser realizado várias vezes, dia a dia, pois é muito mais fácil corrigir
pequenas falhas do que o sistema inteiro após construído.
Como o software no todo é composto por várias unidades e estas unidades apresen-
tando erros fazendo com que o software não funcione, fato que credencia o teste de
unidade a um dos primeiros testes a serem realizados no software. Diante disso, o
esforço é concentrado nessas unidades menores e só após o teste de unidade segue
avançando para as próximas fases. Alguns dos defeitos mais comuns encontrados nos
testes de unidade são:
3. operações mescladas;
4. imprecisão numérica;
As boas práticas defendem que o teste unitário deve ser independente de outros
testes, validando assim cada parte ou funcionalidade individualmente. Quando uma
unidade que está sendo testada necessita realizar chamadas a outros métodos ou
classes que não estão sob teste, são utilizados os mocks, que são objetos criados para
simular o comportamento de objetos reais nos testes, simulando assim seu funciona-
mento na unidade. O teste unitário também é caracterizado pelo uso de controlado-
res (drivers) e simuladores (stubs), este último tem como objetivo, simular o compo-
nente invocado a fim de focar o teste na unidade previamente selecionada.
Um ponto importante, que diz respeito também aos testes unitários, é com relação à
futura manutenção do código. Não é necessário se preocupar com o fato de que pos-
síveis refatorações ou alterações no código façam com que novos erros inesperados
ocorram, já que todas as funcionalidades do sistema estão sendo testadas novamente.
Nesta abordagem a ordem dos testes dos módulos são inversos da top-down, onde
os módulos menos importantes, ou de níveis mais baixos da hierarquia são desen-
volvidos e testados primeiro, depois os próximos níveis acima na hierarquia até que o
último módulo seja testado.
O objetivo deste teste é executar o sistema sob ponto de vista de seu usuário final,
varrendo todas as funcionalidades em busca de falhas em relação aos objetivos ori-
ginais do produto. Os testes devem ser executados em condições similares ao de um
ambiente de produção, ou seja, um ambiente próximo ao utilizado pelo usuário final
no seu dia-a-dia. Contemplando interfaces sistêmicas, massas de dados e configura-
ções de hardwares.
1. teste de recuperação;
2. teste de segurança;
3. teste de estresse;
4. teste de desempenho;
Neste tipo de teste, a procura por defeitos já não faz parte do objetivo maior, e sim,
a tentativa de estabelecer a confiança entre as partes, de um lado o sistema ou uma
característica específica do sistema e do outro quem demandou o sistema.
Teste de aceite do usuário: tem como objetivo verificar se o sistema está apropriado
para o uso por um determinado perfil de usuário;
8.4 DEPURAÇÃO
Deus (2009), afirma que, assim como os testes, depuração é uma área de pesquisa que
está recebendo muita atenção da sociedade acadêmica, vários experimentos, mo-
delos e ferramentas já foram desenvolvidos buscando aperfeiçoar essa atividade na
construção de software. Pode-se citar um modelo de depuração chamado Hipótese-
Validação, técnica de fatiamento de programas e outras. As informações oriundas
do teste podem ter um papel importante durante a fase de depuração, fornecendo
informações para acelerar o processo de busca e correção dos defeitos.
Identificar os erros semânticos não é uma tarefa trivial, requer uma análise investiga-
tiva validando e checando as saídas do programa avaliando o resultado do algoritmo.
O compilador não fornece informações sobre o que está errado. Somente o usuário
consegue identificar o que o programa realmente deveria fazer, porém, não o está.
A origem deste tipo de erro se dá de o fato do programador não ter expressado corre-
tamente, através da linguagem de programação, a sequência de instruções a serem.
Como pode ser observado no algoritmo acima, o cálculo da média está sendo feito de
forma errada já que existem 11 números (de 0 até 10) e não apenas 10 como escrito.
O terceiro tipo de erro é o runtime error, ou erro em tempo de execução, assim cha-
mado porque só aparece quando você executa o programa. Também são conheci-
dos como exceções, porque normalmente indicam que alguma coisa excepcional
aconteceu.
Os erros de execução ocorrem quando o programa tenta fazer algo impossível. Estes
tipos de erros são detectados somente na execução do algoritmo. Erros típicos:
REFERÊNCIAS
AGUILAR, Luis Joyanes. Fundamentos de Programação: Algoritmos, estruturas
de dados e objetos. Porto Alegre: AMGH, 2008.
FARRER, Harry. et al. Pascal Estruturado. Rio de Janeiro: LTC, 3º ed, 1999.
WILLRICH, R.; CARMO, Luiz Fernando Rust da Costa; FARINES, Jean Marie;
GAUTHIER, F.A.O.. Uma abordagem Semântica para a especificação de
Qualidade de Serviço em Redes de Computadores. 2010. Dissertação (Mestrado
em Ciências da Computação) - Universidade Federal de Santa Catarina.
EAD.MU LTIVIX.EDU.BR
FACULDADE CAPIXABA DA SERRA/EAD
Credenciada pela portaria MEC nº 767, de 22/06/2017, Publicada no D.O.U em 23/06/2017
SUMÁRIO 135