notas de aula - 1

Propaganda
ATENÇÃO!
Este documento é apenas um rascunho da obra em desenvolvimento!
CAP241 – Computação Aplicada I
Parte 1. Introdução a Lógica de Programação
Dr. Gilberto Ribeiro de Queiroz
Notas de Aula
URL do documento original:
<http://www.dpi.inpe.br/~gribeiro>
INPE
São José dos Campos
2016
LISTA DE FIGURAS
Pág.
Figura 1.1 – Algoritmo MDC em Linguagem Natural. .................................................................................8
Figura 1.2 – Algoritmo que escreve a sequência de Fibonacci. ..................................................................11
Figura 1.3 – Segunda versão do algoritmo de Fibonacci.............................................................................12
Figura 1.4 – Primeiro refinamento do algoritmo de Fibonacci. ..................................................................12
Figura 1.5 – Versão final do algoritmo de Fibonacci após os refinamentos. ..............................................13
Figura 1.6 – Algoritmo de Fibonacci na forma de diagrama de blocos. .....................................................16
Figura 1.7 – Simbologia em um diagrama de blocos. .................................................................................17
Figura 1.8 – Algoritmo de Fibonacci na forma de diagrama de blocos. .....................................................18
Figura 1.9 – Exemplo de instrução MIPS instruções. .................................................................................19
Figura 1.10 – Exemplo de instrução MIPS instruções. ...............................................................................19
Figura 1.13 - Programa em Assembler: x = 9 + 71. ....................................................................................20
Figura 1.14 – Algoritmo MDC expresso em C++. ......................................................................................21
Figura 1.15 – Fluxo de compilação de um programa C++. .........................................................................22
Figura 1.16 – Exemplo de código em ALGOL 60. .....................................................................................24
Figura 1.17 –Algumas Linguagens de Programação. ..................................................................................25
Figura 1.18 – Relação entre variáveis, memória e representação do tipo de dados. ...................................28
Figura 1.19 – Declaração da variável v. ......................................................................................................29
Figura 1.20 – (a) Equação geral de uma reta; (b) Reta definida pela expressão. ........................................31
Figura 1.21 – Exemplos de comandos de atribuição. ..................................................................................36
Figura 1.22 – Exemplos de chamada de função. .........................................................................................37
Figura 1.23 – Uso de comentários ...............................................................................................................39
Figura 1.24 – Comando condicional simples. .............................................................................................41
Figura 1.25 – Fluxo de execução de um programa com a presença de um único comando if. ...................42
Figura 1.26 – Exemplo de teste condicional simples. .................................................................................42
Figura 1.27 – Comando condicional simples com um bloco de comandos.................................................43
Figura 1.28 – Exemplo de teste condicional com blocos de comandos. .....................................................43
Figura 1.29 – Estrutura condicional composta. ...........................................................................................44
Figura 1.30 – Ideia da estrutura condicional composta. ..............................................................................44
Figura 1.31 – Exemplo de uso da estrutura condicional composta. ............................................................45
Figura 1.32 – Comandos condicionais aninhados. ......................................................................................46
Figura 1.33 – Comandos condicionais aninhados. ......................................................................................47
Figura 1.34 – Somátórios. ............................................................................................................................48
Figura 1.35 – Laços de repetição com interrupção no início .......................................................................48
Figura 1.36 – Somatório dos números entre [1:5]. ......................................................................................49
Figura 1.37 – Laços de repetição com interrupção no final. .......................................................................50
Figura 1.38 – Somatório dos números entre [1:5]. ......................................................................................51
Figura 1.39 – Laços de repetição com variável de controle. .......................................................................52
Figura 1.40 – Somatório dos números entre [1:5] com laço do tipo “para”. ...............................................52
Figura 1.41 – Laços de repetição com variável de controle sobre um conjunto. ........................................53
Figura 1.42 – Esquema de um vetor de números inteiros............................................................................55
Figura 1.43 – Calculando o número de alunos com nota acima da média. .................................................56
Figura 1.44 – Tabela de notas de alunos......................................................................................................57
Figura 1.45 – Procedimento de transposição de uma matriz. ......................................................................59
Figura 1.46 – Função de transposição de uma matriz. ................................................................................60
Figura 1.47 – Função iterativa para geração do n-th termo da série de Fibonacci. .....................................61
Figura 1.48 – Função recursiva para geração do n-th termo da série de Fibonacci. ...................................62
Figura 1.49 – Pontos de intersecção entre dois conjunto de segmentos de reta. .........................................63
Figura 1.50 – Algoritmo para computação dos pontos de intersecção entre dois conjuntos de segmentos de
reta. ..............................................................................................................................................................64
ii
Figura 1.51 – Implementação do algoritmo para computação dos pontos de intersecção entre segmentos
de dois conjuntos apresentado na Figura 3.2 em C++. ................................................................................65
iii
LISTA DE TABELAS
Pág.
Tabela 1.1 – Operadores aritméticos. ..........................................................................................................30
Tabela 1.2 – Operadores relacionais. ...........................................................................................................32
Tabela 1.3 – Operadores lógicos. ................................................................................................................34
Tabela 1.4 – Precedência dos operadores. ...................................................................................................35
Tabela 1.5 – Funções sobre uma string. ......................................................................................................38
iv
SUMÁRIO
Pág.
1 Introdução à Lógica de Programação .....................................................................................................7
1.1. Algoritmos ..........................................................................................................................................8
1.2. Representação de Algoritmos ...........................................................................................................10
1.2.1. Pseudocódigo .................................................................................................................................10
1.2.2. Diagrama de Blocos .......................................................................................................................14
1.2.3. Diagrama de Chapin ......................................................................................................................18
1.2.4. Linguagens de Programação ..........................................................................................................19
1.2.5. Considerações sobre estilo de algoritmos ......................................................................................25
1.3. Técnicas Básicas para Desenvolvimento de Algoritmos ..................................................................27
1.3.1. Constantes ......................................................................................................................................27
1.4. Variáveis ...........................................................................................................................................28
1.5. Declaração de Variáveis ...................................................................................................................29
1.6. Expressões.........................................................................................................................................30
1.6.1. Expressões e Operações Aritméticas .............................................................................................30
1.6.2. Expressões Lógicas ........................................................................................................................32
1.6.3. Precedência dos Operadores ..........................................................................................................35
1.7. Atribuição .........................................................................................................................................36
1.8. Invocando Funções ...........................................................................................................................37
1.9. Comentários ......................................................................................................................................39
1.10. Comandos de Entrada e Saída (E/S) ...............................................................................................40
1.11. Estrutura Condicional .....................................................................................................................41
1.11.1. Esrutura condicional simples .......................................................................................................41
1.11.2. Estrutura condicional composta ...................................................................................................44
1.11.3. Comandos condicionais aninhados (ou encadeados) ...................................................................46
1.12. Repetições .......................................................................................................................................48
1.12.1. Laços de repetição com interrupção no início .............................................................................48
1.12.2. Laços de repetição com interrupção ao final da primeira iteração ..............................................50
1.12.3. Repetição com variável de controle .............................................................................................52
1.12.4. Interrompendo o laço de repetição ...............................................................................................53
1.12.5. Desviando a sequência de laço de repetição ................................................................................53
1.13. Vetores ............................................................................................................................................54
1.14. Matrizes (Arrays) ............................................................................................................................57
1.15. Estruturas ou Registros ...................................................................................................................58
1.16. Criando Procedimentos e Funções ..................................................................................................59
1.17. Recursividade ..................................................................................................................................61
1.18. Modelo de Programação .................................................................................................................63
v
1.19. Instruções sobre ambiente de programação ....................................................................................68
1.20. Considerações Finais do Capítulo ...................................................................................................69
1.21. Referências Bibliográficas do Capítulo ..........................................................................................70
1.22. Exercícios Propostos .......................................................................................................................72
1.23. Exercícios Especiais........................................................................................................................74
vi
1 Introdução à Lógica de Programação
“Programconstructionconsistsofasequenceofrefinementsteps.”
Niklaus Wirth (Wirth, 1971b)
Um computador é uma ferramenta para solução de problemas envolvendo
processamento de dados. Um programa é formado por uma sequência de
instruções que diz ao computador como realizar uma determinada tarefa.
Essas instruções dizem, por exemplo, como o programa deve interagir com o
usuário, como ele deve utilizar o hardware, e como operar os dados para
realizar computações.
Quando um computador segue as instruções de um programa, dizemos
que ele está executando o programa. Os programas são escritos por
programadores. A atividade de programar consiste na habilidade de dizer ao
computador como ele deve realizar um determinado trabalho. Para isso,
precisamos de uma forma de expressar nossos programas.
As Linguagens de Programação servem a esse propósito. Porém, para
conseguir escrever programas em alguma linguagem é preciso conhecer um
pouco de lógica de programação.
Este capítulo apresenta uma introdução aos principais conceitos e
técnicas envolvidos na atividade de programação de computadores.
1.1.
Algoritmos
O termo algoritmo é usado na Ciência da Computação para descrever
métodos
finitos,
determinísticos
e
efetivos,
que
são
adequados
à
implementação na forma de programas de computador (Sedgewick e Wayne,
2011).
Para entender melhor a definição acima, vamos descrever um algoritmo
para computar o máximo divisor comum entre dois números inteiros positivos
quaisquer:
ComputaroMDCentredoisnúmerosinteirosnãonegativos,peq:
•
Seqfor0,oresultadoseráp;
•
Casocontrário,obterorestodadivisãorentrepeq.
•
OMDCentreqerseráoresultado.
Figura 1.1 – Algoritmo MDC em Linguagem Natural.
Fonte: Adaptado de Sedgewick e Wayne (2011).
Observe que o algoritmo acima é formado pelo encadeamento de uma
série de instruções ou ações, que seguidas nos levarão à solucionar nosso
problema: computar o MDC entre dois números inteiros positivos.
Para verificar nosso algoritmo, vamos tomar como valores de p e q,
respectivamente, os números 21 e 4. Nesse caso nosso algoritmo se comporta
da seguinte forma:
Inicialmente, verificamos se o valor de q é maior do que 0.
Neste caso, como o valor de q é 4, vamos a próxima instrução.
Calculamos o resto da divisão entre 21 e 4 obtemos o valor 1.
Usando nosso algoritmo novamente, temos agora que computar o MDC(4, 1).
Verificamos agora se o valor de q é maior do que 0.
Neste caso, como o valor de q é 1, vamos a próxima instrução.
Calculando o resto da divisão entre 4 e 1 obtemos o valor 0.
Usando nosso algoritmo novamente, temos agora que computar o MDC(1, 0).
Agora, na primeira instrução, o valor de q é 0. Neste caso, o resultado é então
o valor 1.
Agora, vamos tomar como valores de p e q, respectivamente, os
números 6 e 12. Nesse caso nosso algoritmo se comporta da seguinte forma:
Inicialmente, verificamos se o valor de q é maior do que 0.
Neste caso, como o valor de q é 12, vamos a próxima instrução.
Calculamos o resto da divisão entre 6 e 12 obtemos o valor 6.
Usando nosso algoritmo novamente, temos agora que computar o MDC(6, 6).
Verificamos agora se o valor de q é maior do que 0.
Neste caso, como o valor de q é 6, vamos a próxima instrução.
Calculando o resto da divisão entre 6 e 6 obtemos o valor 0.
Usando nosso algoritmo novamente, temos agora que computar o MDC(6, 0).
Agora, na primeira instrução, o valor de q é 0. Neste caso, o resultado é então
o valor 6.
1.2.
Representação de Algoritmos
Na prática, podemos representar um algoritmo de diversas formas.
1.2.1. Pseudocódigo
A forma mais usual de representar uma algoritmo é através de um
procedimento descrito em linguagem natural, que utiliza algumas convenções
especiais, como estruturas de controle e palavras reservadas para
representar comandos e operações. Esta forma é conhecida como
Português Estruturado, Portugol ou pseudocódigo.
Para entender como é esta representação, vamos escrever um algoritmo
para computar a série de Fibonacci para valores inferiores a um certo valor v1.
A sequência de Fibonacci é definida como tendo os dois primeiros
termos o valor 1 e cada termo seguinte sendo igual à soma dos dois termos
imediatamente anteriores.
Portanto, a série de Fibonacci para valores inferiores a 100 é a seguinte:
1123581321345589
1
Para informações sobre algoritmos para computação da séries de Fibonacci veja a Wikibooks.
A Figura 1.2 mostra uma primeira versão desse algoritmo:
algoritmo
leiav
escrevasérie-fibonacci(v)
fimalgoritmo
Figura 1.2 – Algoritmo que escreve a sequência de Fibonacci.
Fonte: Adaptada de Farrer et al. (1989)
No algoritmo acima, as palavras algoritmo e fim algoritmo foram
utilizadas para delimitar o início e o fim das instruções que compõem o
algoritmo. Esta primeira versão é formada por duas ações (ou comandos):
•
A primeira realiza a leitura do valor V, para o qual iremos computar a
séries de Fibonacci. Repare que utilizamos a palavra leia para
indicar um comando, isto é, uma ação, que realiza a leitura de um
valor numérico a partir de um dispositivo de entrada, como o teclado.
•
A segunda ação, escreve, em um dispositivo de saída, como a tela, a
sequência de Fibonacci, supondo a existência de um comando de
escrita chamado escreva, e um comando em que computa a série
de Fibonacci.
As palavras em azul e sublinhadas são chamadas de palavras
reservadas, e fazem parte da nossa gramática para escrita de pseudocódigos.
Obviamente, que o algoritmo acima ainda está longe de servir ao
propósito que queremos, que é expressar uma forma geral de como computar
a série de Fibonacci.
Na construção de algoritmos, é comum utilizarmos uma técnica
conhecida como refinamentos sucessivos (Wirth, 1971b): começamos o
algoritmo com uma descrição de mais alto nível, contendo um conjunto de
comandos que serão expandidos à medida que vamos detalhando esse
algoritmo.
Utilizando essa abordagem, podemos reescrever o algoritmo da Figura
1.2 da seguinte forma:
algoritmo
leiav
processeos2primeirostermos
processeostermosrestantes
fimalgoritmo
Figura 1.3 – Segunda versão do algoritmo de Fibonacci.
Fonte: Adaptada de Farrer et al. (1989)
Aplicando esta técnica ao comando “processe os 2 primeiros
termos”, temos o algoritmo mostrado na Figura 1.4.
algoritmo
leiav
atribua1aoprimeirotermo
seeleformenorquev
entãoescreva-o
fimse
atribua1aosegundotermo
seeleformenorquev
entãoescreva-o
fimse
processeostermosrestantes
fimalgoritmo
Figura 1.4 – Primeiro refinamento do algoritmo de Fibonacci.
Fonte: Adaptada de Farrer et al. (1989)
Na versão da Figura 1.4 já podemos perceber a inclusão de uma
estrutura condicional, uma das estruturas de controle que determina a
ordem em que os comandos devem ser executados.
Agora, refinando o comando “processeostermosrestantes”, temos
a versão final do algoritmo, conforme apresentado na Figura 1.5.
algoritmo
leiav
atribua1aoprimeirotermo
seeleformenorquev
entãoescreva-o
fimse
atribua1aosegundotermo
seeleformenorquev
entãoescreva-o
fimse
repita
calculeonovotermosomandoosdoisanteriores
senovotermoformaiorouigualav
entãointerrompa
fimse
escrevanovotermo
fimrepita
fimalgoritmo
Figura 1.5 – Versão final do algoritmo de Fibonacci após os refinamentos.
Fonte: Adaptada de Farrer et al. (1989)
Nessa versão final, aparece uma segunda estrutura de controle, a
estrutura de repetição, que faz com que os comandos dentro dela sejam
executados repetidamente até que uma certa condição interrompa a
repetição.
Vamos ver como funciona nosso algoritmo para uma entrada de valor
100:
Primeiro termo = 1
Como 1 < 100, escreve o valor 1
Segundo termo = 1
Como 1 < 100, escreve o valor 1
Calcula o novo termo, como sendo 1 + 1 = 2
Como 2 < 100, escreve o valor 2
Calcula o novo termo, como sendo 1 + 2 = 3
Como 3 < 100, escreve o valor 3
Calcula o novo termo, como sendo 2 + 3 = 5
Como 5 < 100, escreve o valor 5
Calcula o novo termo, como sendo 3 + 5 = 8
Como 8 < 100, escreve o valor 8
Calcula o novo termo, como sendo 5 + 8 = 13
Como 13 < 100, escreve o valor 13
Calcula o novo termo, como sendo 8 + 13 = 21
Como 21 < 100, escreve o valor 21
Calcula o novo termo, como sendo 13 + 21 = 34
Como 34 < 100, escreve o valor 34
Calcula o novo termo, como sendo 21 + 34 = 55
Como 55 < 100, escreve o valor 55
Calcula o novo termo, como sendo 34 + 55 = 89
Como 89 < 100, escreve o valor 89
Calcula o novo termo, como sendo 55 + 89 = 144
Como 144 não é menor que 100, interrompe
Resultado final: 1123581321345589.
1.2.2. Diagrama de Blocos
Outra forma que temos para representação de um algoritmo é através da
representação gráfica em diagrama de blocos. Este tipo de diagrama é capaz
de apresentar o fluxo de comandos de um algoritmo.
O algoritmo da Figura 1.5, para computação da sequência de Fibonacci,
é apresentado na Figura 1.6 na forma de um diagrama de blocos equivalente.
Figura 1.6 – Algoritmo de Fibonacci na forma de diagrama de blocos.
Fonte: Adaptada de Farrer et al. (1989)
A simbologia do diagrama acima é mostrada na Figura 1.7.
Terminal: usado para indicar início ou fim do algoritmo.
E/S Dados: usado para indicar entrada ou saída de
dados de forma genérica.
Fluxo de comandos: indica qual a próxima instrução a
ser executada.
Processamento: usado para
comandos a serem executados.
indicar
cálculos
ou
Comando de Decisão: usado para indicar desvios no
fluxo de instruções do algoritmo dependendo da avaliação
de uma certa condição.
Saída em Vídeo: saída de dados no monitor.
Figura 1.7 – Simbologia em um diagrama de blocos.
1.2.3. Diagrama de Chapin
O diagrama de Chapin ou Nassi-Sneider é outra forma de expressão de
algoritmos que utiliza quadros para fornecer uma visão hierárquica e mais
estruturada de um algoritmo. A Figura 1.8 apresenta o algoritmo da Figura 1.5,
para computação da sequência de Fibonacci, utilizando a notação de digrama
de Chapin.
Figura 1.8 – Algoritmo de Fibonacci na forma de diagrama de blocos.
Fonte: Adaptada de Farrer et al. (1989)
1.2.4. Linguagens de Programação
Todo processador ou família de processadores, possui seu próprio conjunto de
instruções e operações. Essas instruções e operações são representadas por
padrões de bits que correspondem a diferentes comandos de máquina, como
carga de dados nos registradores, adição, subtração, multiplicação ou divisão
de valores de dois registradores, ou instruções para transferência do fluxo de
execução de um programa.
Essas instruções permitem que o computador realize as atividades mais
básicas possíveis. Veja o exemplo do formato das instruções MIPS (32-bits):
Tipo
R
I
J
31
opcode(6-bits)
opcode(6-bits)
opcode(6-bits)
rs(5)
rs(5)
rt(5)
rt(5)
0
shamt(5) funct(6)
immediate(16)
address(26)
rd(5)
Figura 1.9 – Exemplo de instrução MIPS2 instruções.
Fonte: Adaptada de Wikipedia.
Uma operação para adicionar o conteúdo dos registradores 1 e 2 e
armazenamento no registrador 6 tem a seguinte forma:
op
000000
rs
00001
rt
00010
rd
00110
shamt
00000
funct
100000
Figura 1.10 – Exemplo de instrução MIPS3 instruções.
Fonte: Adaptada de Wikipedia.
2
3
https://en.wikipedia.org/wiki/MIPS_instruction_set
https://en.wikipedia.org/wiki/MIPS_instruction_set
Um programa, ou executável, é formado por um conjunto dessas
instruções numa determinada ordem para realizar tarefas.
Suponha por exemplo que você esteja colocando para rodar sua IDE de
programação favorita. O Sistema Operacional irá copiar os bytes contendo as
instruções do seu programa para a memória RAM do seu computador, e a CPU
irá começar a executar seu programa a partir da primeira instrução.
O grande problema com o código de máquina é que ele é bastante
inconveniente para a escrita de programas diretamente por seres humanos.
Por conta disso, foram desenvolvidas as Linguagens de Montagem
(Assembly), que usam símbolos chamados mnemonic4 para representar cada
instrução ou operação (load, store, jump). O programa responsável por
converter o programa escrito na linguagem de montagem para o código de
máquina é chamado de montador ou assembler. Uma linguagem de
montagem contém o mesmo número de instruções que a linguagem de
máquina associada, sendo porem mais legível.
LDA#9;loadsthenumber9intotheaccumulator
ADD#71;addsthenumber71tothecontentsoftheaccumulator=80
STO34;savetheaccumulatorresulttothememoryaddress34
Figura 1.13 - Programa em Assembler: x = 9 + 71.
Fonte: Adaptada de Wikibooks.
Escrever um programa diretamente em linguagem de montagem não é
uma tarefa simples, pelo menos nos dias de hoje, onde temos grandes
sistemas para construir. Além de ser de difícil compreensão, os processadores
possuem um conjunto de instruções bem reduzidos e nossas necessidades ao
criarmos programas são bem maiores.
4
https://en.wikipedia.org/wiki/Mnemonic
Por conta dessas dificuldades, foram criadas as linguagens de alto
5
nível , nos anos 50, para fornecer abstrações de mais alto nível do que as
instruções de máquina. Essas linguagens são mais próximas também das
linguagens naturais. Portanto, linguagem de alto nível é outra forma que
temos para escrevermos nossos algoritmos, como mostrado no código da
Figura 1.14.
unsignedintMDC(unsignedintp,unsignedintq)
{
if(q==0)
returnp;
unsignedintr=p%q;
returnMDC(q,r);
}
Figura 1.14 – Algoritmo MDC expresso em C++.
Fonte: Adaptada de Sedgewick e Wayne (2011).
5
https://en.wikipedia.org/wiki/High-level_programming_language
As linguagens de alto nível também foram concebidas para evitar que
um dado programa tivesse que ser reescrito toda vez que fosse ser executado
em um arquitetura diferente. Para isso, foram criados os compiladores, que
são responsáveis por traduzir o código na linguagem de alto nível em uma
linguagem de montagem que posteriormente pode ser transformada em
linguagem de máquina por um assembler. A Figura 1.15 mostra este fluxo para
um compilador C++ (GNU g++).
Figura 1.15 – Fluxo de compilação de um programa C++.
Em 1953, John Backus desenvolveu uma linguagem chamada
Speedcoding (Backus, 1954), considerada a primeira linguagem de alto nível,
para ajudar no desenvolvimento de software no computador IBM 701. Ao
contrário das linguagens de máquina da época, Speedcoding introduzia uma
linguagem simbólica e tinha como objetivo ter uma expressividade mais
próxima à linguagem natural. Essa linguagem introduziu suporte a aritmética
em ponto flutuante. Tratava-se de uma linguagem interpretada, cujo
desempenho estava longe do ideal: seu desempenho era de 10 a 20 vezes pior
do que de um código escrito diretamente em código de máquina.
O grande problema identificado por Backus na época, e que motivou o
desenvolvimento das linguagens de programação de alto nível, era que grande
parte dos custos e do tempo para solucionar problemas científicos e de
engenharia
com
a
ajuda
de
grandes
computadores,
era
gasto,
respectivamente, com a preparação do problema (2/3 custo) e com a escrita e
debug do programa criado (90% do tempo) (Backus et al., 1957).
Somente com o desenvolvimento da Linguagem FORTRAN (Backus et
al., 1957) para o IBM 704, é que as linguagens de alto nível passaram a ganhar
ampla aceitação. O IBM 704 introduziu um hardware com capacidade de
realização de aritmética em ponto flutuante. FORTRAN possibilitou que os
programadores
especificassem
procedimentos
numéricos
usando
uma
linguagem próxima à da matemática, e ainda assim ser capaz de gerar código
de máquina eficiente no IBM 704. Essa foi a maneira encontrada por Backus
para reduzir a codificação e atividades de debug para menos de 1/5 do
trabalho.
Na sequência, vieram as Linguagens ALGOL, acrônimo de ALGOrithmic
Language. A primeira versão, ALGOL 58, que inicialmente seria batizada de
IAL (International Algebraic Language), representa um marco histórico por ter
sido desenvolvida em conjunto por cientistas americanos e europeus na
tentativa de criar uma linguagem padronizada. ALGOL foi utilizada,
principalmente, na comunidade científica para especificar os algoritmos
apresentados em revistas e jornais, como a ACM.
Essa família de linguagens formalizou diversos recursos disponíveis nas
linguagens atuais, entre elas, o uso de estrutura de comandos em blocos,
comandos condicionais, laços, procedimentos e funções, formas de passagem
de parâmetro (valor, referência, nome), funções aninhadas, escopo léxico6. A
Figura 1.16 mostra um exemplo de código em ALGO 60.
procedureAbsmax(a)Size:(n,m)Result:(y)Subscripts:(i,k);
valuen,m;arraya;integern,m,i,k;realy;
commentTheabsolutegreatestelementofthematrixa,ofsizenbym,
istransferredtoy,andthesubscriptsofthiselementtoiandk;
begin
integerp,q;
y:=0;i:=k:=1;
forp:=1step1untilndo
forq:=1step1untilmdo
ifabs(a[p,q])>ythen
begin
y:=abs(a[p,q]);
i:=p;k:=q
end
endAbsmax
Figura 1.16 – Exemplo de código em ALGOL 60.
Fonte: Wikipedia
6
https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping
Por conta da especificação da Linguagem ALGOL 58, Backus criou a
notação BNF, refinada mais tarde por Peter Naur durante a especificação de
ALGOL 60. ALGOL influenciou o desenvolvimento de diversas outras
linguagens, como mostrado na Figura 1.17. Esta figura mostra apenas uma
pequena lista das linguagens de programação que foram criadas ou que
existem atualmente.
Figura 1.17 –Algumas Linguagens de Programação.
De
tudo
que
conversamos
nesta
seção,
o
importante
a
ser
compreendido é que um programa escrito em uma linguagem de programação
é apenas uma forma de expressar um dado algoritmo.
1.2.5. Considerações sobre estilo de algoritmos
Nossa noção de algoritmo será baseada em conceitos presentes nas
linguagens imperativas7, isto é, linguagens cuja propriedade principal é a
existência de comandos que podem produzir efeito colateral em um programa,
como a alteração do valor de uma variável. Isto significa que alguns comandos
podem alterar o estado da aplicação.
As linguagens de programação declarativas, por outro lado, descrevem a
lógica sem a necessidade de uso de instruções de controle de fluxo, em teoria,
focando mais nos aspectos de especificação de como um programa deve
atingir seus resultados.
Esta discussão ficará mais clara quando falarmos dos paradigmas de
programação e como as linguagens suportam esses paradigmas.
7
https://en.wikipedia.org/wiki/Imperative_programming
1.3.
Técnicas Básicas para Desenvolvimento de Algoritmos
Nesta seção iremos detalhar os principais conceitos envolvidos na escrita de
um algoritmo.
1.3.1. Constantes
Uma constante é um valor fixo que não se modifica ao longo de todo o tempo
de execução de um programa. Em linguagens de programação, como C++,
também chamamos essas constantes de literais.
Uma constante pode ser um número, um valor lógico ou uma
sequência de caracteres (string).
As constantes numéricas são usadas para representar números inteiros
ou reais, positivos ou negativos:
•
9,-21,3.14,-2.73
As duas constantes lógicas possíveis são: verdadeiro efalso.
As constantes string representam sequências de caracteres, formadas
por letras e dígitos. Em geral representamos a sequência entre aspas duplas:
•
"Gilberto Ribeiro de Queiroz", "30/12/1976", "7654",
"VERDADEIRO"
1.4.
Variáveis
Um programa, além de manipular constantes literais também manipula o que
chamamos de variáveis. Cada variável corresponde a uma posição de memória
cujo conteúdo pode variar ao longo do tempo de execução de um programa
(Figura 1.18). Uma variável possui um nome usado como identificador e, em
geral, é associada com um tipo de dado. O tipo de dados define os possíveis
valores que podem estar associado a uma variável8.
Figura 1.18 – Relação entre variáveis, memória e representação do tipo de dados.
Cada linguagem de programação possui suas regras para nomenclatura
das variáveis, inclusive para dizer se há diferenças entre variáveis com letras
maiúsculas e minúsculas.
8
Tipagem é um conceito clássico de linguagens fortemente tipadas. Atualmente, existem inúmeras
linguagens fracamente tipadas, em que uma variável pode alterar o tipo de valor associado.
1.5.
Declaração de Variáveis
Os nossos pseudocódigos deverão conter a declaração das variáveis, isto é,
antes de usar uma variável, devemos especificar seu nome e o tipo de dados
associado a ela. A declaração de variáveis terá a seguinte sintaxe:
declareidentificadorestipo
Retomando o algoritmo de Fibonacci (Figura 1.5), teríamos a seguinte
declaração da variável v:
algoritmo
declarevnumérico
leiav
...
fimalgoritmo
Figura 1.19 – Declaração da variável v.
Após a declaração de uma variável, qualquer referencia a ela, ou
melhor, ao seu identificador, implica na referencia ao conteúdo da posição de
memória associada a ela.
1.6.
Expressões
Através da combinação das operações e operandos podemos criar
expressões, como as expressões matemáticas convencionais. Uma expressão
deve resultar em um valor de um certo tipo.
1.6.1. Expressões e Operações Aritméticas
As operações aritméticas disponíveis para os tipos numéricos são muito
semelhantes às que usamos na matemática. A Tabela 1.1 mostra os
operadores
aritméticos
disponíveis
na
nossa
linguagem
(pseudocódigo).
Tabela 1.1 – Operadores aritméticos.
Operadores
Expressões
Resultado
+ (adição)
5.1+8
13.1
- (subtração)
3-2
1
* (multiplicação)
3*4.1
12.3
/(divisão)
6/4
1.5
n
x (radiciação)
xn(potenciação)
3
27 3
33
27
algorítmica
Esses operadores podem ser combinados para escrita de expressões,
como a forma geral de uma equação de reta:
Ax+By+C=0
(equação geral de uma reta)
A equação matemática acima pode ser reescrita como uma expressão
envolvendo constantes e variáveis, como mostrado abaixo:
3*x-2*y–6
Figura 1.20 – (a) Equação geral de uma reta; (b) Reta definida pela expressão.
Veja que na expressão, não é válido omitir o operador ‘*’ (multiplicação).
Além disso, quando uma expressão contém mais de dois operadores,
como no caso da equação acima, a ordem em que eles são aplicados é
significante e, por isso, existe uma convenção bem definida da precedência de
cada operador. No caso das operações aritméticas, a prioridade é a seguinte:
1. potenciação e radiciação;
2. multiplicação e divisão;
3. adição e subtração.
Assim como na matemática, podemos usar parênteses para controlar
essa prioridade.
Além das regras de precedência, temos também a ordem de aplicação.
Várias das expressões que montamos são infixas, isto é, temos um literal ou
variável ou expressão, seguido do operador, seguido por outro literal ou
variável ou expressão. Nas linguagens de programação, vários operadores
possuem uma associatividade da esquerda para direita.
1.6.2. Expressões Lógicas
Veremos que esse tipo de expressão é essencial para controlar o fluxo de
execução de nossos programas, nos comandos condicionais e testes
condicionais dos laços de repetição.
1.6.2.1. Operadores Relacionais
Os operadores relacionais ou operadores de comparação permitem
comparar dois valores e produzir um valor booleano (ou lógico) como resultado.
Esses operadores são mostrados na Tabela 1.2.
Tabela 1.2 – Operadores relacionais.
Operadores
Expressões
5==4
== (igual)
5==5
5!=4
!= (diferente)
5!=5
5<4
< (menor que)
4<5
5<=4
<=(menor ou igual)
4<=5
5>4
> (maior que)
4>5
5>=4
>= (maior ou igual)
4>=5
Resultado
falso
verdadeiro
verdadeiro
falso
falso
verdadeiro
falso
verdadeiro
verdadeiro
falso
verdadeiro
falso
Retomando nossa equação da reta poderíamos escrever uma expressão
da seguinte forma:
(3*x-2*y–6)>0
No caso dos operadores relacionais, a prioridade é a seguinte:
1. <, >, <=, >=;
2. ==, !=
1.6.2.2. Operadores Lógicos
Os operadores lógicos são baseados na álgebra de proposições,
retornando o resultado de operações denominadas booleanas. Esses
operadores são mostrados na Tabela 1.3.
Tabela 1.3 – Operadores lógicos.
Operadores
não
(negação)
Valor
Expressões
Resultado
a=verdadeiro;
nãoa
falso
a=falso;
nãoa
verdadeiro
aeb
verdadeiro
aeb
falso
aeb
falso
aeb
falso
aoub
verdadeiro
aoub
verdadeiro
aoub
verdadeiro
aoub
falso
a=verdadeiro;
b=verdadeiro;
a=verdadeiro;
e
(conjunção)
b=falso;
a=falso;
b=verdadeiro;
a=falso;
b=falso;
a=verdadeiro;
b=verdadeiro;
a=verdadeiro;
ou
(disjunção)
b=falso;
a=falso;
b=verdadeiro;
a=falso;
b=falso;
Com esses operadores, podemos construir expressões como:
media>7emin(n1,n2)>5
No caso dos operadores lógicos, a prioridade é a seguinte:
1. Negação: não
2. Conjunção: e
3. Disjunção: ou
1.6.3. Precedência dos Operadores
Tabela 1.4 – Precedência dos operadores.
Nível
Operador
Descrição
1
não
não lógico
2
+, -
operadores unários que definem o sinal
3
n
x ,xn
radiciação, potenciação
4
*, /
multiplicação, divisão
5
+, -
adição, subtração
6
<, >, <=, >=
operadores de comparação
7
==, !=
igualdade e desigualdade
8
e
e lógico
9
ou
ou lógico
1.7.
Atribuição
A atribuição é um comando que associa um valor de um determinado tipo de
dados a uma variável. Essa associação pode também ser o resultado de uma
expressão. Portanto, este comando possui a seguinte forma:
identificador←expressão
Exemplo:
algoritmo
declarev1numérico
declarev2numérico
declaresomanumérico
v1←10
v2←30
soma←v1+v2
v1←45
v2←76
soma←v1+v2
fimalgoritmo
Figura 1.21 – Exemplos de comandos de atribuição.
O valor da expressão avaliada deve ser compatível com o tipo de dado
da variável na sua declaração.
1.8.
Invocando Funções
Apenas com o conjunto de operações básicas seria muito difícil expressarmos
nossos algoritmos, mesmo em pseudocódigo. Por isso, várias funcionalidades
que iremos incluir na escrita dos nossos programas pressupõe a existência de
algumas funções e procedimentos auxiliares. Como por exemplo:
•
sin, cos, log, resto.
Estas funcionalidades podem ser incluídas no nosso algoritmo na forma
de chamada de uma função (function call), que é uma forma de desviar o
fluxo de controle do nosso programa para uma outra parte que irá realizar
uma determinada computação e depois irá retornar o fluxo de controle ao
ponto onde foi chamada (ou invocada).
A chamada de uma função é feita colocando-se o nome da função e a
lista de argumentos que será passada à função, para que ela realize sua
computação. Por exemplo:
algoritmo
declarev1numérico
declarev2numérico
declareresultadonumérico
v1←10
v2←30
resultado←sen(v1)
resultado←sen(v1+v2)
resultado←sen(45)
fimalgoritmo
Figura 1.22 – Exemplos de chamada de função.
Na nossa linguagem de pseudocódigo, não teremos a disposição
operadores para string. Ao invés disso, teremos a lista de funções mostradas
na Tabela 1.5.
Tabela 1.5 – Funções sobre uma string.
Operadores
Valores
Expressões
Resultado
concat(a,b)
a←"nome:"
b←"Gilberto"
concat(a,b)
"nome:Gilberto"
sub (s, i, l)
a←"Gilberto"
sub(a,0,3)
"Gil"
cumprimento (s)
a←"Gilberto"
cumprimento (a)
8
1.9.
Comentários
Comentários são parte importante de qualquer programa, não sendo
considerados instruções a serem executadas. Servem apenas ao proposito de
documentar o código.
algoritmo
declarex,ynumérico{coordenadasdeumponto}
declareresultadonumérico
leiax
leiay
resultado←3*x-2*y–6{calculaseopontoencontra-sesob}
{areta3x-2y–6}
fimalgoritmo
Figura 1.23 – Uso de comentários
1.10. Comandos de Entrada e Saída (E/S)
A sintaxe destes comandos é a seguinte:
leialistadevariáveis
escrevaexpressão
1.11. Estrutura Condicional
As estruturas condicionais ou comandos condicionais permitem alterar a
sequência de execução de um programa dependendo do resultado de uma
expressão lógica.
1.11.1.
Esrutura condicional simples
Um único comando se...então permite que nosso programa execute outro
comando ou bloco de comandos (Figura 1.24), se uma dada condição for
verdadeira, isto é, se a expressão lógica definida pela condição for avaliada
como verdadeira.
seexpressão-lógica
entãocomando(s) Comando(s) executado(s) caso a condição seja verdadeira.
fimse
comando
Figura 1.24 – Comando condicional simples.
Na Figura 1.24, os comandos no bloco da palavra chave então só serão
executados se a expressão lógica for avaliada como verdadeira. Neste caso, o
comando fora da estrutura de controle poderá ser executado após a sequência
de comandos delimitada pelo teste condicional, caso nenhum desses
comandos termine o programa.
Caso a expressão-booleana seja avaliada como falsa, apenas o
comando fora da estrutura de controle será executado.
A Figura 1.25 ilustra como é o fluxo do nosso programa dependendo da
avaliação da expressão lógica do comando se.
Figura 1.25 – Fluxo de execução de um programa com a presença de um único
comando if.
Um exemplo desse tipo de comando condicional é mostrado na Figura
1.26:
01
02
03
04
05
06
07
08
09
declareidadenumérico
idade←39;
seidade>=40
entãoescreva"nacasados40!"
fimse
seidade<40
entãoescreva"aindanãochegounos40!"
fimse
escreva"Qualqueridadeéboa,desdequebemaproveitada!"
Figura 1.26 – Exemplo de teste condicional simples.
Outra possibilidade, é a estrutura condicional ter uma sequência ou
bloco de comandos (Figura 1.27).
Figura 1.27 – Comando condicional simples com um bloco de comandos.
Um exemplo desse tipo de comando condicional é mostrado abaixo
(Figura 1.28):
01
02
03
04
05
06
07
08
declareidadenumérico
idade←39;
se(idade>38)e(idade<40)
então
escreva"quasenos40!"
escreva"umaboaidade!"
fimse
escrevaidade
Figura 1.28 – Exemplo de teste condicional com blocos de comandos.
1.11.2.
Estrutura condicional composta
Neste caso, além da sequência de comandos a ser executada caso a
expressão lógica seja avaliada como verdadeira, temos também um conjunto
de instruções que pode ser executado caso ela seja avaliada como falsa.
seexpressão-lógica
entãocomando(s)
senãocomando(s)
fimse
comando(s)
Figura 1.29 – Estrutura condicional composta.
Essas estrutura condicionais são chamadas de comandos if-thenelse.
Conforme mostrado na Figura 1.30, este tipo de comando nos permite
escolher entre dois fluxos possíveis.
Figura 1.30 – Ideia da estrutura condicional composta.
Exemplo:
algoritmo{resultadododesempenhodeumaaluno}
declaren1,n2numérico
declarenomestring
leianome,n1,n2
se((n1+n2)/2)>7)
entãoescrevanome,"aprovado!"
senãoescrevanome,"reprovado!"
fimse
fimalgoritmo
Figura 1.31 – Exemplo de uso da estrutura condicional composta.
1.11.3.
Comandos condicionais aninhados (ou encadeados)
Podemos colocar como comando a ser executado em uma estrutura
condicional outro comando condicional. Ou seja, dentro de um bloco de um
comando se...então...senão, podemos ter vários tipos de comandos,
incluindo os comandos condicionais (Figura 1.32).
seexpressão-lógica-1
então
comando(s)
seexpressão-lógica-2
comando condicional aninhado.
entãocomando(s)
fimse
comando(s)
senão
comando(s)
seexpressão-lógica-3
entãocomando(s)
comando condicional aninhado.
fimse
comando(s)
fimse
comando;
Figura 1.32 – Comandos condicionais aninhados.
Exemplo:
algoritmo{alunoaprovado?}
declaren1,n2numérico
declarenomestring
leianome,n1,n2
se((n1+n2)/2)>6)
então
escreva"Oaluno:",nome,"obteveamédia."
se(n1<4)ou(n2<4)
então
escreva"Masteráquefazerrecuperação!"
senão
escreva"Atingiutodososrequisitos.Parabéns!"
fimse
senão
escreva"Aluno:",nome,"reprovado!"
fimse
fimalgoritmo
Figura 1.33 – Comandos condicionais aninhados.
1.12. Repetições
Muitas das computações que realizamos em um programa são inerentemente
repetitivas. Nas linguagens imperativas, encontramos comandos específicos
para isso, que são chamados de comandos de repetição ou loops ou
estruturas de repetição. Através desses comandos, podemos realizar uma
computação até que uma certa condição seja satisfeita.
É muito comum utilizarmos laços quando estamos trabalhando com
séries e fórmulas matemáticas (Figura 1.34), bem como estrutura matriciais.
n
∑a
i
i=1
a=
n∑ xy − ∑ x∑ y
n∑ x 2 −
(∑ x )
2
2
∑ y∑ x − ∑ x∑ xy
b=
n∑ x − (∑ x )
2
2
Figura 1.34 – Somátórios.
1.12.1.
Laços de repetição com interrupção no início
Se a expressão lógica no início do laço for verdadeira, os comandos dentro da
estrutura de repetição são executados de maneira sequencial. Ao final da
execução dos comandos, internos ao laço, o fluxo de controle do programa
volta ao início do laço para avaliar novamente a condição de repetição. Se ela
for satisfeita novamente (verdadeira), executa outra vez o corpo do laço, até
que a repetição seja interrompida quando a condição for falsa.
comando(s)
enquantoexpressão-lógicafaça
comando(s)
fimenquanto
comando(s)
Figura 1.35 – Laços de repetição com interrupção no início
Exemplo:
algoritmo
declaresoma,numnumérico
soma←0
num←1
enquantonum<=5faça
soma←soma+num
num←num+1
fimenquanto
escrevasoma
fimalgoritmo
Figura 1.36 – Somatório dos números entre [1:5].
1.12.2.
Laços de repetição com interrupção ao final da primeira iteração
Explicação.
comando(s)
faça
comando(s)
enquantoexpressão-lógica
comando(s)
Figura 1.37 – Laços de repetição com interrupção no final.
Exemplo:
algoritmo
declaresoma,numnumérico
soma←0
num←1
faça
soma←soma+num
num←num+1
enquantonum<5
escrevasoma
fimalgoritmo
Figura 1.38 – Somatório dos números entre [1:5].
1.12.3.
Repetição com variável de controle
Este tipo de laço é muito comum quando fazemos algoritmos que manipulam
matrizes9.
paravdev1atévn[passop]faça
comando(s)
fimpara
Figura 1.39 – Laços de repetição com variável de controle.
Exemplo:
algoritmo
declaresomanumérico
soma←0
paraide1até5faça
soma←soma+i
fimpara
escrevasoma
fimalgoritmo
Figura 1.40 – Somatório dos números entre [1:5] com laço do tipo “para”.
9
https://pt.wikipedia.org/wiki/Estrutura_de_repetição
Outra variação deste tipo de laço é a seguinte:
paracadaitemdeconjuntofaça
comando(s)
fimpara
Figura 1.41 – Laços de repetição com variável de controle sobre um conjunto.
Veremos exemplos de uso deste tipo de laço quando falarmos de
vetores, matrizes, listas.
1.12.4.
Interrompendo o laço de repetição
Podemos utilizar em nossos algoritmos a instrução interrompa, que faz com
que o fluxo de execução de um laço seja quebrado, isto é, desviado para a
instrução seguinte ao laço.
Em geral, podemos colocar esta instrução dentro de um teste
condicional no corpo do laço.
1.12.5.
Desviando a sequência de laço de repetição
Outra instrução útil é a continue, que desvia o fluxo de execução de dentro do
laço para a próxima iteração do loop.
1.13. Vetores
Suponha que cada aluno da disciplina CAP-241 tenha tido sua avaliação e que
estejamos interessados em computar o número de alunos que obtiveram nota
acima da média. Como escrever este algoritmo apenas com as ferramentas
discutidas até aqui?
Para esse tipo de problema, bem como outros que precisam lidar com
uma coleção homogênea de valores, temos a estrutura chamada de vetor:
várias posições de memória identificadas por um mesmo nome de variável,
sendo acessíveis através de índices (Figura 1.42).
Figura 1.42 – Esquema de um vetor de números inteiros.
Declaramos um vetor da seguinte forma:
declarev[i:f]tipo
Obviamente, que: i≤f.
Exemplo: Algoritmo para calcular o número de alunos com nota acima
da média.
algoritmo
declarenotas[1:5]numérico{vetordasnotasdosalunos}
declaresomanumérico{somadasnotasdosalunos}
declaremedianumérico{médiadasnotasdosalunos}
declarenum_alunosnumérico{númerodealunosacimadamédia}
soma←0
media←0
num_alunos←0
paraide1até5faça
leianotas[i]
soma←soma+notas[i]
fimpara
media←soma/5
paraide1até5faça
senotas[i]>media
então
num_alunos←num_alunos+1
fimse
fimpara
escrevanum_alunos
fimalgoritmo
Figura 1.43 – Calculando o número de alunos com nota acima da média.
1.14. Matrizes (Arrays)
Podemos também criar variáveis compostas multidimensionais denominadas
matrizes.
Quando estivermos falando de grafos, esta estrutura será fundamental,
pois ela serve de suporte para uma das formas de representação conhecida
por matriz de adjacência.
Declaramos uma matriz da seguinte forma:
declarem[i0:f0;i1:f1;...;in:fn]tipo
Agora, o acesso a um elemento da matriz é feita utilizando-se índices
para cada um dos intervalos das dimensões.
Exemplo: Dada a tabela abaixo, contendo as notas de 4 provas de 5
alunos, faça um algoritmo que dada uma matriz bidimensional (5x4), calcule a
média de cada aluno e a média da turma.
N1
7.5
5
9
5
6
N2
7.2
5
7.8
4
7
N3
8.1
5
6.3
3
8
N4
9
5
9.2
2
9
Figura 1.44 – Tabela de notas de alunos.
Exemplo: supondo m[0:4; 0:3]
escreva m[0][3]{9}
escreva m[2][1]{7.8}
1.15. Estruturas ou Registros
Uma estrutura ou registro agrupa elementos de dados, possivelmente de tipos
diferentes, em um único nome. Também é uma forma de criarmos nossos
próprios tipos de dados.
A sintaxe para criação de uma estrutura é a seguinte:
declarenomeestrutura(componentes)
Vamos criar uma variável chamada ficha_aluno para representar as
informações sobre um aluno:
declareficha_alunoestrutura(matriculanumérico;
nomestring;
data_nascimentostring;
cpfstring)
Podemos acessar cada um dos componentes da estrutura utilizando um
separador “.”:
ficha_aluno.nome←"Eduardo"
escrevaficha_aluno.cpf
Observação:
•
Assim como temos vetores e matrizes dos tipos básicos (numérico,
lógico e string), podemos ter também de registros.
•
Voltaremos a conversar mais sobre estruturas e tipos de dados mais
adiante, quando estivermos falando de tipos abstratos de dados.
1.16. Criando Procedimentos e Funções
Uma forma que temos de modularizar nossos algoritmos é organizá-los em
procedimentos e funções: blocos de código auto-contidos e identificados com
um nome, uma lista de parâmetros e que pode ser invocado nos nosso
programas.
A Figura 1.45 mostra um procedimento que transpõe os valores de uma
matriz m1 para uma matriz m2. Veja que um procedimento possui um
identificador e uma lista de parâmetros.
procedimentotranspor(m1,m2,nlinhas,ncolunas)
paraide1aténlinhasfaça
parajde1aténcolunasfaça
m2[j][i]←m1[i][j];
fimpara
fimpara
fimprocedimento
Figura 1.45 – Procedimento de transposição de uma matriz.
Podemos chamar o procedimento acima nos nossos algoritmos da
mesma forma que fizemos com as funções matemáticas: colocando o nome da
função e entre parênteses, os argumentos a serem passados.
Uma função é muito semelhante a um procedimento, só que deve
retornar um valor.
funçãotranspor(m,nlinhas,ncolunas)
declarem2[1:nlinhas;1:ncolunas]
paraide1aténlinhasfaça
parajde1aténcolunasfaça
m2[j][i]←m[i][j];
fimpara
fimpara
retornam2
fimfunção
Figura 1.46 – Função de transposição de uma matriz.
Observação: iremos dar mais atenção sobre a passagem de
argumentos quando estivermos falando de C++.
1.17. Recursividade
Vamos retomar o problema de geração da série de Fibonacci (Algoritmo Figura
1.5). Desta vez, vamos apenas computar o n-th termo. Agora que conhecemos
todas as estruturas de controle, comandos, atribuição e funções, podemos
construir o seguinte algoritmo:
algoritmo
funçãofib(n)
declaret1,t2,tnumérico
t1←0
t2←1
t←1
paraide1aténfaça
t←t1+t2
t1←t2
t2←t
fimpara
retornat
fimfunção
declarevnumérico
declarefnumérico
leiav
f←fib(v)
escrevaf
fimalgoritmo
Figura 1.47 – Função iterativa para geração do n-th termo da série de Fibonacci.
Veja agora esta outra versão:
algoritmo
funçãofib(n)
sen<2
então
retornan
senão
retornafib(n–1)+fib(n–2)
fimse
fimfunção
declarevnumérico
declarefnumérico
leiav
f←fib(v)
escrevaf
fimalgoritmo
Figura 1.48 – Função recursiva para geração do n-th termo da série de Fibonacci.
Observação: iremos falar mais sobre recursividade nas próximas
aulas.
1.18. Modelo de Programação
Alguns autores de livros (Farrer et al., 1989; Manzano e Oliveira, 2004) e
professores de Introdução à Lógica de Programação e Algoritmos preferem
fazer uma separação completa da teoria e dos conceitos do uso de uma
linguagem de programação específica. Em geral, o formalismo adotado por
eles baseia-se em uma notação mais próxima à linguagem natural,
expressando os algoritmos no que chamamos de pseudocódigo. Esse
pseudocódigo mais tarde pode ser transcrito para uma linguagem de
programação qualquer.
Vamos a um exemplo concreto. Suponha que tenhamos que projetar um
algoritmo para reportar os pontos de intersecção entre segmentos de reta de
dois conjuntos, um conjunto vermelho e outro azul, conforme a Figura 1.49.
Figura 1.49 – Pontos de intersecção entre dois conjunto de segmentos de reta.
Uma possível notação algorítmica, que podemos empregar para
expressar um algoritmo capaz de determinar os pontos de intersecção entre os
segmentos é mostrado na Figura 1.50.
algoritmo
entrada:R=conjuntodesegmentosdereta
B=conjuntodesegmentosdereta
saída:conjuntodospontosdeintersecção
início
paracadardeRfaça
paracadabdeBfaça
ser∩b
entãoreportar_ponto_interseção(r∩b)
fimse
fimpara
fimpara
fim
fimalgoritmo
Figura 1.50 – Algoritmo para computação dos pontos de intersecção entre dois
conjuntos de segmentos de reta.
Transpondo o algoritmo acima para a Linguagem C++, teríamos a
possível implementação mostrada na Figura 1.51.
//Definiçãodaestruturaponto
#include<ponto.hpp>
//Definiçãodaestruturasegmentodereta
#include<segmento_reta.hpp>
//Funçãoauxiliarparacalculodaintersecçãoentredoissegmentosdereta
#include<segment_intersection.hpp>
//BibliotecaPadrãoC++
#include<utility>//definiçãodepair
#include<vector>//definiçãodevector
std::vector<point>
computa_intersecao(conststd::vector<segmento_reta>&vermelhos,
conststd::vector<segmento_reta>&azuis)
{
std::vector<point>pts_interseccao;
for(constsegment&v:vermelhos)
for(constsegment&a:azuis)
{
std::pair<point,point>par_pts_interseccao;
tipo_intert=computa_intersecao(v,a,par_pts_interseccao);
if(t==DISJUNTO)
continue;
if(t==CRUZA)
{
pts_interseccao.push_back(par_pts_interseccao.first);
}
else
{
pts_interseccao.push_back(par_pts_interseccao.first);
pts_interseccao.push_back(par_pts_interseccao.second);
}
}
returnpts_interseccao;
}
Figura 1.51 – Implementação do algoritmo para computação dos pontos de
intersecção entre segmentos de dois conjuntos apresentado na Figura 3.2 em C++.
Como podemos observar, a implementação precisa considerar muito
mais detalhes do que os existentes na descrição algorítmica.
Em geral, a justificativa para se evitar o uso de uma linguagem
específica, como mostrado na Figura 1.21, e usar uma notação como a
apresentada na Figura 1.20, é que usar linguagens de alto nível, como
FORTRAN (Backus et al., 1957), PASCAL (Wirth, 1971a), C (Kernighan e
Ritchie, 1988), C++ (Stroustrup, 2013) e Java (Gosling, 2013), não só dificulta o
aprendizado mas também impõe condições limitantes em relação à forma de
expressão do raciocínio lógico. Em outras palavras, esses autores defendem a
ideia de que a utilização de uma linguagem de programação para expressar as
várias etapas do raciocínio lógico força o iniciante a se preocupar
excessivamente com aspectos poucos significativos para o desenvolvimento do
raciocínio, que são as regras de sintaxe e os ambientes de compilação das
linguagens de programação.
Neste curso, iremos aprender os conceitos básicos, mas iremos
automaticamente conectá-los ao suporte presente na Linguagem C++. O
motivo para isso é simples: este não será um curso apenas de Introdução à
Lógica de Programação e eu quero prepará-los para as outras disciplinas
que virão na sequência!
Além disso, em cursos modernos, as dificuldades apontadas não mais
se justificam dada a quantidade de material disponível na Internet, a
quantidade de livros sobre o tópico e a facilidade de acesso aos diversos
ambientes de programação. Até mesmo autores consagrados, como Nivio
Ziviani10 (Ziviani, 2005) e Robert Sedgewick11 (Sedgewick e Wayne, 2011), já
utilizam as linguagens Pascal, C, C++ e Java para ensino de algoritmos e
estruturas de dados.
Portanto, todos os nosso programas serão implementados usando um
pequeno subconjunto da Linguagem C++. Isto significa que não vou pedir que
façam um código usando construções e idiomas avançados em C++. Mas
teremos um mínimo que cada um terá que saber.
10
11
Professor do Departamento de Ciência da Computação da UFMG.
Professor de Ciência da Computação da Universidade de Princeton.
Minha opção por não utilizar apenas pseudocódigo é justamente para
que vocês possam estudar a fundo as propriedades dos algoritmos, colocandoos em prática rapidamente.
Apesar de usarmos uma linguagem específica, vocês verão que a forma
como apresentaremos os conceitos e depois as implementações serão muito
fáceis de serem portadas para outras linguagens de programação.
1.19. Instruções sobre ambiente de programação
Os participantes do curso que tiverem interesse em realizar atividades práticas
poderão utilizar o seguinte ambiente de máquina virtual:
•
Oracle VirtualBox
•
Linux Ubuntu 14.04 LTS (Trusty)
•
Credenciais:
o Usuário: cap241
o Senha: algoritmo
•
Compilador C++ instalado: GNU g++ 4.8.4
•
Qt Creator 3.6.0
O link para download da máquina é o seguinte:
http://www.dpi.inpe.br/cap241-2016/vm
1.20. Considerações Finais do Capítulo
Este capítulo abordou os principais conceitos da lógica de programação: tipos
de dados, variáveis, comandos, operadores, expressões, comando de
atribuição, chamada de função, comandos condicionais, comandos de
repetição, vetores, matrizes, estruturas, criação de funções e recursividade.
Existem diversos livros e apostilas sobre introdução à lógica de programação,
sugiro como apoio os livros de Farrer et. al (1989) e de Manzano e Oliveira
(2004), que foram utilizados na preparação destas notas de aula.
A história das linguagens de programação é muita rica em ideias que
foram transformadas em soluções práticas, merecendo uma atenção especial
de todo cientista da computação. Existe uma página da Wikipedia12 dedicada a
este assunto, contendo links para outras páginas e documentos muito
interessantes, que explicam detalhes de como os cientistas da época partiram
de problemas práticos e desenvolveram teorias importantes que nos levaram
às ferramentas que temos hoje.
As obras de Dijkstra (1976) e Wirth (1971b), apesar de antigas, são
clássicos sobre a arte de programar, sendo recomendadas como leitura
adicional.
12
https://en.wikipedia.org/wiki/History_of_programming_languages
1.21. Referências Bibliográficas do Capítulo
BACKUS, J. W. The IBM 701 Speedcoding System. Journal of the ACM, v. 1,
n. 1, jan 1954, p. 4-6.
BACKUS, J. W.; BEEBER, R. J.; BEST, S.; GOLDBERG, R.; HAIBT, L. M.;
HERRICK, H. L.; NELSON, R. A.; SAYRE, D.; SHERIDAN, P. B.; STERN,
H.; ZILLER, I.; HUGHES, R. A.; NUTT, R. The FORTRAN automatic
coding system. In: Western Joint Computer Conference: Techniques for
Reliability, 26 a 28 fev, 1957. Anais... New York, NY, USA: ACM, 1957. p.
188-198.
DIJKSTRA, E. W. A Discipline of Programming. Prentice Hall,1976. 217 p.
FARRER, H.; BECKER, C. G.; FARIA, E. C.; MATOS, H. F.; SANTOS, M. A.;
MAIA, M. L. Algoritmos Estruturados. 2a Edição. Rio de Janeiro, Editora
Guanabara, 1989. 252 p.
GOSLING, J.; JOY, B.; STEELE JR., G. L.; BRACHA, G.; BUCKLEY, A. The
Java Language Specification: Java SE 7 Edition (Java Series). 1a
Edição. Addison-Wesley Professional, 2013. 672 p.
KERNIGHAN, B. W.; RITCHIE, D. M. The C Programming Language. 2a
Edição. Prentice Hall, 1988. 272 p.
MANZANO, J. A. N. G.; OLIVEIRA, J. F. Algoritmos: Lógica para
Desenvolvimento de Programação de Computadores. 15a Edição.
Editora Érica, 2004. 242 p.
SEDGEWICK, R.;
WAYNE, K. Algorithms. 4a Edição. Addison-Wesley
Professional, 2011. 992 p.
STROUSTRUP, B. The C++ Programming Language. 4a Edição. AddisonWesley Professional, 2013. 1368 p.
WIRTH, N (a). The Programming Language Pascal. Acta Informatica, v. 1,
1971, p. 35-63.
WIRTH,
N
(b).
Program
Development
by
Stepwise
Refinement.
Communications of the ACM, v. 14, n. 4, abr. 1971, p. 221-227.
ZIVIANI, N. Projeto de Algoritmos: com implementações em PASCAL e C.
2a Edição. Editora Thomson, 2005. 552 p.
1.22. Exercícios Propostos
EP-2.1. Por que não podemos ter um algoritmo com um comando do tipo:
“Escreva todos os termos da sequência de Fibonacci”?
EP-2.2. Mostre como é a execução do algoritmo abaixo:
algoritmo
declareA[1:10],B[1:10]numérico
paraide1até10faça
leiaA[i]
fimpara
paraide1até10faça
r←i–2*trunca(i/2)
ser==0
então
B[i]←A[i]*5
senão
B[i]←A[i]+5
fimse
fimpara
paraide1até10faça
escrevaB[i]
fimpara
fimalgoritmo
EP-2.3. Escreva um algoritmo que leia três números e escreva o valor do maior
e menor deles.
EP-2.4. Escreva um algoritmo que leia o tamanho dos lados de um triângulo,
avalie se esses valores realmente formam um triangulo, e em caso positivo,
classifique o triângulo em isósceles, escaleno ou equilátero.
EP-2.5. Escreva um algoritmo que calcule o fatorial de um número. Faça uma
versão iterativa e outra recursiva.
EP-2.6. Escreva um algoritmo que pergunte ao usuário uma temperatura em
graus Celsius e escreva a conversão para graus Fahrenheit. O programa deve
parar apenas quando o usuário informar que não quer mais continuar.
EP-2.7. Seja as seguintes séries:
•
Lucas: 1,3,4,7,11,18,29,47...
•
Pell: 1,2,5,12,29,70,169,408...
•
Triangular: 1,3,6,10,15,21,28,36...
•
Square: 1,4,9,16,25,36,49,64...
•
Pentagonal: 1,5,12,22,35,51,70,92...
Pede-se:
a) Criar um algoritmo iterativo que compute a série num vetor.
b) Criar um algoritmo recursivo que compute a série num vetor.
EP-2.8. Escreva uma algoritmo que dado um vetor de números encontre o
maior e o menor elementos neste vetor.
EP-2.9. Escreva uma algoritmo para multiplicar duas matrizes.
EP-2.10. Escreva uma algoritmo para ler as coordenadas de dois pontos e
então calcular a distância entre eles.
E-2.11. Escrever um programa que simule uma calculadora com funções
básicas.
1.23. Exercícios Especiais
EE-2.1. Escreva uma função para calcular o máximo divisor comum entre três
números inteiros positivos quaisquer.
EE-2.2. Escreva uma função para converter um número inteiro em uma
sequência de caracteres representando esse número.
EE-2.3. Defina uma estrutura para representação de um segmento de reta no
espaço 2D.
EE-2.4. Escreva uma função, que dado dois segmentos de reta, conforme
representação do exercício EE-2.3, calcule o ponto de intersecção entre eles.
EE-2.5. Escrever um algoritmo para ler as coordenadas de um ponto e de um
segmento de reta e calcular a distância perpendicular a este segmento.
Download