This is a file preview. Join to view the original file.
introducao-a-programacao-livro-1.0.0/.gitignore
/livro/AAPDIR
/livro/*.png
/livro/*.zip
/livro/*.chunked
/livro/*.pdf
/livro/*.txt
/livro/slides.html
/livro/livro.xml
/livro/livro.tex
*.pdf
*.zip
releases
originais
materiais
introducao-a-programacao-livro-1.0.0/README.asciidoc
== Livro da disciplina "Introdução à Programação"
Este repositório contém um livro da disciplina "Introdução à Programação".
Para conhecer o seu processo de produção consulte
https://github.com/edusantana/producao-computacao-ead-ufpb.
introducao-a-programacao-livro-1.0.0/ignore-html
introducao-a-programacao-livro-1.0.0/ignore-slide
introducao-a-programacao-livro-1.0.0/livro/capitulos/algoritmos.asc
[[algoritimos]]
== Algoritmos
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
* Definir algoritmo
* Descrever suas principais características
* Criar algoritmos utilizando diferentes formas de representação
____________________
É comum pensarmos em uma estratégia para executar uma tarefa do nosso
dia a dia, mesmo que ela seja muito simples. Ao escovar os dentes, por exemplo,
nós seguimos diferentes estratégias. Uns começam com a escovação dos
molares e depois partem para os dentes da frente, outros fazem o inverso.
Enfim, existem várias formas de escovarmos os dentes, assim como existem
várias maneiras de realizarmos diversas atividades. Você sabia que o conjunto
de passos para resolver um certo problema ou realizar determinada tarefa
chama-se algoritmo? E que eles são importantíssimos para a programação de
computadores?
Neste capítulo estudaremos as características dos algoritmos, suas formas de
representação e, sobretudo, a relação entre eles e a programação de
computadores.
=== Introdução
Fonte inspiradora de livros e filmes, o americano Monty Roberts (<<monty>>),
conhecido como o “encantador de cavalos”, revolucionou a forma de domar
cavalos. Ao estudar o comportamento de cavalos selvagens, percebeu que existe
entre eles uma linguagem corporal compartilhada. Entendendo tal linguagem,
conseguiu rapidamente ganhar a confiança de cavalos arredios e instruí-los
a se comportarem como desejava. Além de não usar de violência, essencial no
emprego dos métodos convencionais, seu método é capaz de domar cavalos em
poucos dias, ao contrário da maioria, que normalmente necessita de várias
semanas.
[[monty]]
.Monty Roberts.
image::images/algoritmos/monty.jpg[scaledwidth="25%"]
Assim como os cavalos, os computadores também são instruídos por meio de uma
linguagem particular. Para que eles se comportem como desejamos, basta que sejam
comandados a partir de uma linguagem que sejam capazes de entender.
Diferentemente do que ensina o senso o comum, os computadores não possuem
inteligência. Seu único trabalho é processar dados, conforme uma sequência
de instruções que fazem parte do vocabulário da linguagem que eles conseguem
compreender. A ilusão de que eles realizam tarefas de forma inteligente é
proporcionada através desse conjunto ordenado de instruções, que é
denominado de algoritmo. Neste caso, o “domador” do computador, responsável por
elaborar o algoritmo que vai orientá-lo na execução de uma determinada
tarefa, é chamado de programador de computadores.
=== O que é um algoritmo?
A palavra “algoritmo” é derivada do nome Mohammed ibn Musa
Al-Khowarizmique, que foi um matemático, astrólogo, astrônomo e autor
persa. Ele fez parte de um centro acadêmico conhecido como a Casa da
Sabedoria, em Bagdá, por volta de 800 d.C. Seus trabalhos introduziram o
cálculo hindu aos árabes e, a partir daí, ao resto da Europa.
Não obstante os algoritmos representam um conceito central na Ciência da
Computação, sua atuação não se limita a essa área do conhecimento.
Rotineiramente, lidamos com algoritmos na realização das mais variadas
tarefas. Eles podem ser utilizados para lavar um carro, preparar um bolo, tomar
banho, montar um guarda-roupa, etc. Perceba que os algoritmos não devem ser
confundidos com as atividades. Eles se referem aos passos seguidos para que
estas sejam realizadas. Como exemplo de algoritmos, podemos citar as
instruções para montagem de equipamentos, para utilização de cosméticos
como shampoos e condicionadores, para saída de emergência em meios de
transporte, receitas culinárias, manuais de uso, entre outros.
A partir do que foi exposto, podemos definir algoritmo como 'uma sequência
finita, ordenada e não ambígua de passos para solucionar determinado problema
ou realizar uma tarefa'.
Na ciência da computação, esse conceito foi formalizado em 1936, por Alan Turing e
Alonzo Church, da seguinte forma:
.Definição de Algorítimo
____________________
Um algoritmo é um conjunto não ambíguo e ordenado de passos executáveis que
definem um processo finito.
____________________
////
TODO - REV.01: Sugestão de melhoria para análise pelo autor:
apesar de parecer repetitivo, talvez fosse interessante explicar melhor a definição dada acima.
Por exemplo, exemplificar o que seria não ambíguo, ser ordenado e passos executáveis.
////
O exemplo a seguir mostra como pode ser elaborado um algoritmo para realizar
uma atividade com a qual lidamos corriqueiramente:
[[fritar-ovo]]
.Algoritmo para fritar um ovo
. Retire o ovo da geladeira.
. Coloque a frigideira no fogo.
. Coloque óleo na frigideira.
. Quebre ovo, separando a casca.
. Ponha a clara e a gema na frigideira.
. Espere um minuto.
. Apague o fogo.
. Retire o ovo da frigideira.
////
TODO - REV.02: Sugestão de melhoria para análise pelo autor:
talvez fosse interessante colocar aqui um parágrafo simples informando que
várias atividades do nosso dia a dia podem ser representadas por algoritmos
e citar outros exemplos próximos do dia a dia do aluno, além de reforçar
que existe várias formas de resolver o mesmo problema. Para só em seguida,
apresentar o exemplo dado no parágrafo seguinte.
////
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 abaixo ilustra dois possíveis
passos de um algoritmo para resolver o problema.
.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.
image::images/algoritmos/ilustracao-dois-passos.eps[scaledwidth="55%"]
Uma solução para o problema pode ser alcançada a partir do seguinte
algoritmo:
[[vasilha]]
.Algoritmo para encher vasilhas
. Encha a vasilha de nove litros.
. Usando a vasilha de nove litros, encha a de quatro.
. Coloque a quantidade que sobrou (cinco litros) na terceira vasilha (*v3 = 5*).
. Esvazie a vasilha de quatro litros.
. Encha novamente a vasilha de nove litros.
. Usando a vasilha de nove litros, encha a de quatro.
. Esvazie a de quatro litros.
. Usando a sobra da de nove litros (cinco litros), encha novamente a de quatro
litros.
. Coloque a sobra da de nove litros (agora um litro) na terceira vasilha
(*v3 = 5 + 1 = 6*).
=== Características de um algoritmo
Todo algoritmo, seja ele computacional ou não, recebe uma entrada, processa-a
e gera uma saída segundo seu conjunto de passos. No caso do algoritmo para
fritar ovo, a entrada corresponde à frigideira, ao ovo e ao óleo. O
processamento ocorre com a execução de seus passos, gerando como saída o ovo
frito.
////
TODO - REV.03: Sugestão de melhoria para análise pelo autor:
Não há como suavizar mais a transição de um parágrafo para o outro?
Uma sugestão seria:
"Além dessas três características básicas - entrada, processamento,
saída - um algoritmo apresenta outras características, dentre elas, podemos destacar:
..."
////
Os algoritmos computacionais, especificamente, possuem as seguintes
características:
Definição:: Os passos de um algoritmo devem ser bem definidos, objetivando a
clareza e evitando ambiguidades.
////
TODO - REV.04: Sugestão de melhoria para análise pelo autor:
Há como fornecer um simples exemplo do não atendimento dessa característica?
////
Finitude:: Um algoritmo deve chegar ao seu fim após um número finito de
passos.
////
TODO - REV.05: Sugestão de melhoria para análise pelo autor:
Há como fornecer um simples exemplo do não atendimento dessa característica?
////
Efetividade:: Um algoritmo deve ser efetivo, ou seja, suas operações devem
ser básicas o suficiente para que possam, em princípio, serem executadas de
maneira exata e em um tempo finito.
////
TODO - REV.06: Sugestão de melhoria para análise pelo autor:
Há como fornecer um simples exemplo do não atendimento dessa característica?
////
Entradas:: Um algoritmo deve possuir zero ou mais entradas. Estas são insumos
ou quantidades que são processados pelos algoritmos durante a execução de
seus passos.
Saídas:: Um algoritmo deve possuir uma ou mais saídas. Elas representam o
resultado do trabalhado realizado pelos algoritmos.
=== Formas de representação
As formas mais comumente utilizadas para representar algoritmos são as
seguintes:
- Descrição narrativa
- Fluxograma
- Linguagem Algorítmica
Todas elas apresentam pontos fortes e fracos, não existindo consenso entre os
especialistas sobre a melhor forma de representação. Apresentaremos as
nuances de cada uma nas próximas seções.
==== Descrição Narrativa
Os algoritmos são expressos em linguagem natural (português, inglês,
francês, espanhol, etc.). Sua principal desvantagem se encontra no fato da
linguagem natural estar bem distante da linguagem utilizada pelos computadores.
Logo, a tradução de uma para a outra se torna uma atividade bastante
dispendiosa. Além disso, linguagens naturais são mais propensas a
ambiguidades. Muitas vezes uma palavra pode ter vários significados,
dependendo do contexto no qual são utilizadas. Em contrapartida, é bem mais
fácil elaborar um algoritmo por meio de uma linguagem com a qual já temos uma
certa familiaridade, do que através de linguagens que não são utilizadas com
frequência no dia a dia.
Os exemplos de algoritmos mostrados anteriormente (<<fritar-ovo>> e <<vasilha>>)
refletem esta forma de representação.
==== Fluxograma
Consiste em usar formas geométricas padronizadas para descrever os passos a
serem executados pelos algoritmos. As formas apresentadas na Figura 1.3 são as
mais comumente utilizadas em fluxogramas.
.Formas geométricas utilizadas em fluxogramas
image::images/algoritmos/fluxograma.eps[scaledwidth="50%"]
A vantagem de se fazer uso dos fluxogramas está na facilidade de
compreendê-los. Descrições de algoritmos mediante formas gráficas são mais
facilmente compreendidas do que descrições que envolvem apenas textos. Além
do mais, os fluxogramas possuem um padrão mundial no que se refere à sua
simbologia, tornando sua utilização independente das peculiaridades das
linguagens naturais.
////
TODO - REV.07: Dúvida sobre o conteúdo:
Será explicado em algum outro momento o uso de fluxogramas para elaboração
dos algoritmos?
////
Para exemplificar o uso de fluxogramas, a <<fluxograma-calcular-media>>
mostra um algoritmo para
calcular a média final de um aluno com base em suas notas e classificá-lo
como aprovado ou reprovado. Analisando-a com mais cuidado, é possível
perceber que os fluxogramas tendem a crescer bastante quando descrevem
algoritmos constituídos de muitos passos, o que dificulta tanto sua
construção como sua visualização. Além dessa desvantagem, por impor regras
para sua utilização de acordo com cada forma geométrica, há uma limitação
no seu poder de expressão, se comparado com a descrição narrativa.
////
TODO - REV.08: Sugestão de melhoria para análise pelo autor:
Achei o exemplo pesado para o aluno que ainda viu muito pouco
sobre a elaboração de algoritmos com fluxogramas. Talvez fosse
interessante fazer o mesmo exemplo usando a representação narrativa,
isso auxiliaria o aluno no entendimento das representações e suas vantagens
e desvantagens.
////
[[fluxograma-calcular-media]]
.Fluxograma para calcular a média de um aluno
image::images/algoritmos/fluxograma-calcular-media.eps[scaledwidth="75%"]
==== Linguagem Algorítmica
////
TODO - REV.09: Sugestão de melhoria para análise pelo autor:
Para não assustar muito os alunos, eu colocaria toda essa seção que se fala
da arquitetura de von Neumann em uma seção a parte do tipo "Você sabia?".
Deixaria apenas a discussão que a linguagem algorítmica são linguagens intermediárias etc.
////
A linguagem que o computador é capaz de compreender tem grande influência na
elaboração de algoritmos projetados para ele. Seus passos não podem conter
instruções desconhecidas ou fazer referência a símbolos ou expressões que
os computadores não conseguem decifrar. Tal linguagem, tantas vezes mencionada
neste capítulo, se baseia em conceitos e em arquiteturas de hardware que
determinam o funcionamento básico de um computador. Dentre as existentes, a
mais utilizada nos computadores atuais é a arquitetura de von Neumann. Seu
autor, John Von Neumann (<<john-von-neumann>>), propôs um modelo em que as instruções
e os dados ficam juntos na memória.
O processador busca as instruções na memória e as executa uma de cada vez,
segundo o seguinte ciclo de execução:
. Busca instrução;
. Decodifica instrução;
. Executa instrução;
. Volta para o passo 1 para buscar a instrução seguinte na memória.
[[john-von-neumann]]
.John von Neumann
image::images/algoritmos/john-von-neumann.jpg[scaledwidth="15%"]
Para esclarecer como funciona a execução de um algoritmo baseado no ciclo de
execução mencionado, considere uma memória com 32 posições para
armazenamento, organizada conforme <<memoria32posicoes>>.
[[memoria32posicoes]]
.Representação de uma memória com 32 posições para armazenamento.
image::images/algoritmos/memoria32posicoes.eps[scaledwidth="40%"]
Os números do canto superior direito de cada célula indicam os endereços de
memória correspondentes a cada posição da memória representada. Nas três
primeiras células constam as instruções que serão executadas e, da oitava
à décima, constam valores armazenados nas posições de memória nomeadas por
x, y e z, respectivamente.
Supondo que a execução do algoritmo em questão inicia-se com a busca da
instrução no endereço 0 (zero), o ciclo de execução continua com a
decodificação da instrução x = 2, que, após sua realização, resulta no
armazenamento do valor 2 na posição de memória de número 8, nomeada de x. O
passo 4 então é executado, dando início à busca da próxima instrução.
Com isso, a instrução y = 3 é encontrada e decodificada, gerando como
resultado o armazenamento do valor 3 na posição de número 9, nomeada de y. O
mesmo ocorre com a instrução z = x.y, que, após sua decodificação,
armazena o valor 6 (produto de x por y) na posição de endereço 10 e rotulada
de z. O algoritmo em descrição narrativa para a execução das instruções
anteriores encontra-se logo abaixo:
.Algoritmo para multiplicar dois números
. Escreva 2 na posição de memória nomeada de x.
. Escreva 3 na posição de memória nomeada de y.
. Multiplique x e y e o resultado escreva em z.
Modelos como o mencionado anteriormente não apenas definem a forma como os
dados são processados pelo computador, mas também a linguagem que eles são
capazes de compreender. Assim sendo, a linguagem utilizada pelos computadores
está restrita a um conjunto limitado de instruções, cujo funcionamento
depende
de sua arquitetura de hardware. As linguagens de programação
imperativas (Pascal, C, Cobol etc), por exemplo, foram criadas em função da
arquitetura de von Neumman.
////
TODO - REV.10: Sugestão de melhoria para análise pelo autor:
Nada foi falado sobre linguagem de alto nível ou baixo nível, dessa forma,
será que vale a pena mencionar isso sem explicá-las aqui? Não confundiria
mais o aluno? Um sugestão seria colocar links para seções "Você sabia?" ou
vídeos, etc.
////
A linguagem algorítmica, também chamada de pseudocódigo ou pseudo-linguagem,
por sua vez, consiste no emprego de uma linguagem intermediária entre a
linguagem natural e uma linguagem de programação. Esse meio termo resulta em
uma linguagem que se aproxima das construções de uma linguagem de
programação, sem exigir, no entanto, rigidez na definição das regras para
utilização de suas instruções. Geralmente, essa forma de representação de
algoritmos é uma versão reduzida de linguagens de alto nível como C e
Pascal. Segue abaixo o algoritmo da <<fluxograma-calcular-media>>
em pseudocódigo:
////
TODO: criar um hightlight para pseudocódigo
Ver: http://xpt.sourceforge.net/techdocs/nix/tool/asciidoc-usg/ascu03-SourceCodeHighlighting/single/
Ou : asciidoc-8.6.8/filters/code
////
[source,pascal,numbered]
-------------------------------------------
ALGORITMO
DECLARE nota1, nota2, M : NUMÉRICO
LEIA nota1
LEIA nota2
M ← (nota1 + nota2) / 2
SE M >= 7.0 ENTÃO
ESCREVA “Aprovado”
SENÃO
ESCREVA “Reprovado”
FIM-SE
FIM_ALGORITMO.
-------------------------------------------
////
TODO - REV.11: Dúvida do conteúdo:
Na explicação das palavras reservadas do pseudocódigo há
palavras que não estão representadas no exemplo e são mencionados. Por exemplo,
'INICIO' e 'FIM'. Precisa deixar coerente com o apresentado, pois, em princípio
o aluno ainda não entende nada do assunto, e assim, é dificil se abstrair
dos detalhes.
////
As palavras em letras maiúsculas correspondem a palavras reservadas que fazem
parte do conjunto de regras que a linguagem algorítmica deve seguir.
////
TODO - REV.12: Sugestão de melhoria para análise pelo autor - referente as linhas 388 e 389
Acho que vale a pena aqui reforçar ao aluno que isso significa que qualquer algoritmo
desenvolvido com a linguagem só poderá ser feito usando as palavras reservadas e apenas elas.
////
Embora
sejam mais flexíveis do que as linguagens de programação em relação ao seu
uso (a instrução `LEIA`, por exemplo, muitas vezes é substituída por
`LER`, `OBTER`, etc.), algumas palavras são necessárias, pois
facilitam o entendimento e aproximam o pseudocódigo de um programa de
computador. As palavras `INÍCIO` e `FIM`, por exemplo, indicam onde
começa e termina o algoritmo. Já as instruções `LEIA` e `ESCREVA`
referem-se a operações de entrada e saída de dados (ex.: ler dados do
teclado ou exibir uma frase no monitor), presentes na maioria das linguagens de
programação.
Seguindo com a explicação do algoritmo, perceba que a linha com a instrução
`M ← (nota1 + nota2) / 2` contém dois símbolos ainda não apresentados.
O símbolo `/` diz respeito à operação aritmética da divisão, ao passo
que o símbolo `←` expressa uma operação de atribuição, que pode ser
lida da seguinte forma: 'A posição de memória, representada simbolicamente
por M, recebe o valor da soma de nota1 e nota2, dividido por dois'. Para
finalizar, a linha 6 apresenta uma estrutura de controle condicional essencial
para as linguagens de programação. Operações de atribuição, expressões e
estruturas de controle fazem parte do núcleo das linguagens de programação
imperativas e são, portanto, fundamentais para o aprendizado da programação.
Todos esses assuntos serão abordados de forma mais aprofundada em capítulos
posteriores.
A principal vantagem da forma de representação em linguagem algorítmica
está na facilidade com a qual um pseudocódigo pode ser transcrito para uma
linguagem de programação. Assim como os fluxogramas, a desvantagem fica por
conta da limitação do seu poder de expressão, devido às regras impostas
para a elaboração das instruções.
////
TODO - REV.13: Sugestão de melhoria para análise pelo autor
Talvez fosse interessante ser mencionado algumas ferramentas que podem auxiliar
o aluno no desenvolvimento de algoritmos em pseudo código como o Visualg, dentre
outros exemplos. Também há um joguinho muito bacana que pode ser utilizado
para ensinar algoritmos o Light-bot, como curiosidade dá para colocar seções
com links e etc para despertar mais a curiosidade do aluno sobre
o desenvolvimento de algoritmos, antes de apresentar uma linguagem de programação, o choque
é grande para o aluno, muito mesmo. Acho que poderia ser explorado mais
seções do tipo "Você sabia?" ou "Para saber mais...".
////
=== Recapitulando
Neste capítulo você estudou algoritmos, suas principais características e
suas formas de representação.
Apesar de ser um tema mais abordado na ciência da computação, algoritmos
estão presentes nas mais diversas áreas e em várias atividades do cotidiano.
Lidamos com eles, por exemplo, quando tomamos banho, cozinhamos, planejamos
uma rota para fugirmos do trânsito, consultamos um manual de montagem, enfim,
sempre que nos deparamos com um conjunto lógico de passos para realizarmos uma
tarefa ou solucionarmos um problema, estamos em contato com algoritmos. É por
meio deles que os computadores passam a ilusão de que são inteligentes,
realizando tarefas capazes de impressionar qualquer ser humano. No entanto,
sabemos que eles apenas processam dados, segundo um conjunto de instruções
que lhe são passadas -- os algoritmos.
Você viu que os algoritmos computacionais, aqueles elaborados para serem
executados em computadores, devem ser claros, ter um número finito de passos,
e que estes devem ser simples o suficiente para serem executados de maneira
exata e em um tempo finito. Além disso, os algoritmos computacionais devem
possuir zero ou mais entradas e uma ou mais saídas.
As formas de representação de algoritmos mais comuns são a linguagem
algorítmica, o fluxograma e o pseudocódigo. Da primeira à última há uma
aproximação em relação às linguagens de programação, ou seja, o
pseudocódigo é a forma de representação que mais se assemelha às
linguagens utilizadas na programação de computadores. Na direção inversa,
há uma maior liberdade na elaboração de algoritmos, aumentando, assim, a
capacidade de expressá-los.
No próximo capítulo abordaremos o processo de tradução de um programa
escrito em uma linguagem de alto nível, os paradigmas de programação
existentes, e introduziremos os conceitos básicos da programação de
computadores. Além disso, você terá o primeiro contato com a linguagem de
programação a ser estudada neste livro: a linguagem C.
////
TODO - REV.14: Sugestão de melhoria para análise pelo autor
Em relação aos exercícios seria interessante ter uma seção de exercícios
resolvidos e comentados, outra de exercícios propostos e uma pequena seção
com um ou dois desafios.
////
=== Exercícios Propostos
1. Explique, com suas próprias palavras, o que é algoritmo.
2. Rotineiramente, usamos algoritmos para as mais diversas tarefas. Cite três
algoritmos que podemos encontrar no dia a dia.
3. Em que consiste a característica de efetividade de um algoritmo?
4. Suponha que o quarto passo de um determinado algoritmo ordene que a
execução retorne ao primeiro. Qual característica não está sendo
satisfeita por esse algoritmo?
5. Discorra sobre as formas de representação de algoritmos mais comuns,
destacando suas vantagens e desvantagens.
6. Suponha que você foi premiado com um robô capaz de auxiliá-lo nas tarefas
domésticas. Antes que execute determinada atividade, você precisa instruí-lo
corretamente através de um algoritmo específico. Sabendo disso, escreva
algoritmos, em linguagem
natural, para ensiná-lo a realizar cada uma das
tarefas abaixo:
a. Trocar a lâmpada do seu quarto.
b. Trocar o pneu do seu carro.
c. Fazer uma vitamina de banana com açaí.
d. Lavar e secar os pratos.
e. Calcular quanto você precisar tirar na terceira nota para passar por média
em Introdução à Programação.
7. Escreva um algoritmo, utilizando fluxograma, que receba como entrada o peso e
altura de uma pessoa, calcule seu IMC (Índice de Massa Corpórea) e exiba sua
situação, segundo os seguinte critério:
+
Se o IMC > 25, a pessoa está acima de seu peso, caso contrário, está abaixo. Onde o IMC = (Peso)/(Altura^2^)
8. Usando fluxograma, faça um algoritmo que receba como entrada a idade de uma
pessoa expressa em anos, meses e dias (Atenção: são 3 entradas) e mostre-a
expressa apenas em dias. Considere anos de 365 dias e meses de 30 dias.
9. Considere as instruções armazenadas na memória a seguir:
+
image::images/algoritmos/memoria-exercicio.eps[scaledwidth="50%"]
+
Considerando que a instrução inicial se encontra no endereço `0` (zero) e as
posições `8`, `9` e `10` correspondem a `x`, `y` e `z`, respectivamente,
explique como funciona a execução das instruções acima, segundo a arquitetura
de von Neumann. Antes da execução da instrução de endereço 2 `(z=x/y + z)`, a
posição de memória referente a `z` possuía o valor `1` (um).
10. Escreva um algoritmo, em pseudocódigo, que receba como entrada a base e a
altura de um triângulo, calcule e exiba sua área.
// Sempre termine os arquivos com uma linha em branco.
introducao-a-programacao-livro-1.0.0/livro/capitulos/asciidoc-cheatsheet.asc
== AsciiDoc cheatsheet
Nesta seção são apresentadas vários recursos do
asciidoc. Verifique
o código fonte para aprender qual a sintaxe que produz este texto.
=== Como aprender a sintaxe do asciidoc?
A melhor forma é através dos exemplos contidos na página do asciidoc.
AsciiDoc cheatsheet:: http://powerman.name/doc/asciidoc
Exemplo de livro:: https://asciidoc.googlecode.com/hg/doc/book.txt
Guida do usuário:: http://www.methods.co.nz/asciidoc/userguide.html
Para matemática:: http://www.methods.co.nz/asciidoc/latexmath.pdf
=== Seções
----
== Nível de capítulo (section) 1
Texto.
=== Nível de subseção 2
Texto.
==== Level 3
Texto.
===== Level 4
Texto.
----
CAUTION: Não pode existir seções sem texto. Sempre coloque algum texto,
caso contrário o arquivo não será gerado.
=== Parágrafos
.Título do parágrafo opcional
Texto do parágrafo
aqui.
.Título do parágrafo opcional
Texto do parágrafo aqui.
Precisa começar com espaço em branco.
.Optional Title
[source,perl]
die 'connect: '.$dbh->errstr;
Este parágrafo não faz parte do código.
.Optional Title
NOTE: This is an example
single-paragraph note.
.Optional Title
[NOTE]
This is an example
single-paragraph note.
TIP: Texto.
IMPORTANT: Texto.
WARNING: Texto.
CAUTION: Texto.
=== Blocos
.Optional Title
----
*Listing* Block
Use: code or file listings
----
.Optional Title
[source,perl]
----
# *Source* block
# Use: highlight code listings
# (require `source-highlight` or `pygmentize`)
use DBI;
my $dbh = DBI->connect('...',$u,$p)
or die "connect: $dbh->errstr";
----
.Optional Title
****
*Sidebar* Block
Use: sidebar notes :)
****
.Optional Title
==========================
*Example* Block
Use: examples :)
Default caption "Example:"
can be changed using
[caption="Custom: "]
before example block.
==========================
.Optional Title
[NOTE]
===============================
*NOTE* Block
Use: multi-paragraph notes.
===============================
////
*Comment* block
Use: hide comments
////
.Optional Title
....
*Literal* Block
Use: workaround when literal
paragraph (indented) like
1. First.
2. Second.
incorrectly processed as list.
....
.Optional Title
[quote, cite author, cite source]
____
*Quote* Block
Use: cite somebody
____
[[texto-secao]]
=== Text
forçando +
quebra de linha.
normal, _italic_, *bold*, +mono+.
``double quoted'', `single quoted'.
normal, ^super^, ~sub~.
Command: `ls -al`
Path: '/some/filez.txt', '.b'
// Comentário de linha.
////
Comentários aqui dentro,
com várias linhas.
////
(C) (R) (TM) -- ... -> <- => <= ¶
''''
[[macro-secao]]
=== Macros: Referencias, images & include
Exemplo de referências.
anchor:anchor-2[]
Paragraph or block 2.
Exemplo de referencias: <<texto-secao>>,
<<texto-secao,Capitulo sobre texto>>.
.Block image
image::images/novo-capitulo/bug.png[scaledwidth="60%"]
=== Código fonte de programas
Exemplo de código fonte.
-------------------------------------------
include::code/helloworld.c[]
-------------------------------------------
<1> Incluindo biblioteca de entrada/saída.
<2> Chamada à função printf, que envia para a saída padrão (o console) o texto recebido.
==== Trecho de código
Exemplo de trecho de código.
#include<stdio.h>
main() { printf("Hello World"); }
=== Listas
.Bulleted
* bullet
* bullet
- bullet
- bullet
* bullet
** bullet
** bullet
*** bullet
*** bullet
**** bullet
**** bullet
***** bullet
***** bullet
**** bullet
*** bullet
** bullet
* bullet
.Bulleted 2
- bullet
* bullet
.Ordered
. number
. number
.. letter
.. letter
. number
.. loweralpha
.. loweralpha
... lowerroman
... lowerroman
.... upperalpha
.... upperalpha
..... upperroman
..... upperroman
.... upperalpha
... lowerroman
.. loweralpha
. number
.Ordered 2
a. letter
b. letter
.. letter2
.. letter2
. number
. number
1. number2
2. number2
3. number2
4. number2
. number
.. letter2
c. letter
.Labeled
Termo 1::
Definição 1
Termo 2::
Definição 2
Termo 2.1;;
Definição 2.1
Termo 2.2;;
Definição 2.2
Termo 3::
Definição 3
Termo 4:: Definição 4
Termo 4.1::: Definição 4.1
Termo 4.2::: Definição 4.2
Termo 4.3::: Definição 4.3
Termo 5:: Definição 5
.Labeled 2
Termo 1;;
Definição 1
Termo 1.1::
Definição 1.1
[horizontal]
.Labeled horizontal
Termo 1:: Definição 1. ashkjahgsjhagkjshgkjhag jhasgjh agfsjhgafsjhgafhsgfjasjh
aj shgkjahsg khagskjhagskjhsak.
Termo 2:: Definição 2 kajshkljahskjah lskjha lkjshlaksjlkagsksjahgskjhgk ahsgka
la shlkajhsl kjahlskjahls.
[qanda]
.Q&A
Questão 1::
Resposta da pergunta 1 aqui.
Questão 2:: Resposta da pergunta 2 aqui.
.Indent is optional
- bullet
* another bullet
1. number
. again number
a. letter
.. again letter
.. letter
. number
* bullet
- bullet
.Break two lists
. number
. number
Independent paragraph break list.
. number
.Header break list too
. number
--
. List block define list boundary too
. number
. number
--
--
. number
. number
--
.Continuation
- bullet
continuation
. number
continuation
* bullet
literal continuation
.. letter
+
Non-literal continuation.
+
----
any block can be
included in list
----
+
Last continuation.
.List block allow sublist inclusion
- bullet
* bullet
+
--
- bullet
* bullet
--
* bullet
- bullet
. number
.. letter
+
--
. number
.. letter
--
.. letter
. number
=== Tabelas
You can fill table from CSV file using +include::+ macros inside table.
.An example table
[options="header,footer"]
|=======================
|Col 1|Col 2 |Col 3
|1 |Item 1 |a
|2 |Item 2 |b
|3 |Item 3 |c
|6 |Three items|d
|=======================
.CSV data, 15% each column
[format="csv",width="60%",cols="4"]
[frame="topbot",grid="none"]
|======
1,2,3,4
a,b,c,d
A,B,C,D
|======
[grid="rows",format="csv"]
[options="header",cols="^,<,<s,<,>m"]
|===========================
ID,FName,LName,Address,Phone
1,Vasya,Pupkin,London,+123
2,X,Y,"A,B",45678
|===========================
.Multiline cells, row/col span
|====
|Date |Duration |Avg HR |Notes
|22-Aug-08 .2+^.^|10:24 | 157 |
Worked out MSHR (max sustainable
heart rate) by going hard
for this interval.
|22-Aug-08 | 152 |
Back-to-back with previous interval.
|24-Aug-08 3+^|none
|====
=== Matemática
Ver https://asciidoc.googlecode.com/hg/doc/latexmath.txt e comparar com
http://www.methods.co.nz/asciidoc/latexmath.pdf
[[equacao-qualquer]]
.Uma equação qualquer.
[latexmath]
++++++++++++++++++++++++++++++++++++++++++++
\[C = \alpha + \beta Y^{\gamma} + \epsilon\]
++++++++++++++++++++++++++++++++++++++++++++
Colocar equação no meio do texto,
latexmath:[$C = \alpha + \beta Y^{\gamma} + \epsilon$], também é
possível. Mas o mais legal é referencia-la (ver <<equacao-qualquer>>).
introducao-a-programacao-livro-1.0.0/livro/capitulos/cap4-arranjos.asc
== Arranjos
.Objetivos do capítulo
____
Ao final deste capítulo você deverá ser capaz de:
* Apresentar os conceitos de vetores e matrizes
* Apresentar o conceito de strings e como manipulá-las
____
=== Introdução
Até agora vimos que uma variável armazena um único valor por vez. Por
exemplo, em um programa para ler as notas de vários alunos, cada nota é
armazenada em uma variável, e assim que as notas de um novo aluno são lidas,
as notas do aluno anterior são perdidas. Em alguns problemas, é necessário
armazenar todos ou um conjunto de valores lidos sem que haja perda de
informação. Nesse caso, seria inviável declarar uma variável distinta para
armazenar cada valor, quando a quantidade de valores a serem manipulados for
relativamente grande. Para situações como essas utilizamos Arranjos (em
inglês _Arrays_), que consistem em estruturas de dados capazes de agrupar em
uma única variável vários elementos de um mesmo tipo. O conceito de
Arranjos, bem como as diferentes formas de utilizá-los serão discutidos em
detalhes no decorrer deste capítulo, que ainda apresentará as cadeias de
caracteres, conhecidas como _strings_.
=== Vetores
Os arranjos podem ter diferentes dimensões. Um tipo especial de arranjo com
apenas uma dimensão é chamado de *((vetor))*. Portanto, vetores são arranjos
unidimensionais que representam um conjunto de variáveis com o mesmo tipo, as
quais são acessadas através de um índice que as identificam. A <<fig_vetor>>
ilustra o conceito de vetor, apresentando um vetor de inteiros com cinco
elementos, cada um com seu índice correspondente. O índice do primeiro
elemento é sempre zero.
[[fig_vetor]]
.Vetor com cinco elementos.
image::images/cap4/vetor5.eps[scaledwidth="50%"]
NOTE: O conceito de arranjo nas diferentes linguagens de programação é o
mesmo. Entretanto, em algumas linguagens alguns detalhes podem ser diferentes.
Na linguagem C, por exemplo, o índice inicial é sempre zero. Já na
linguagem Pascal, o índice inicial é definido pelo próprio programador.
==== Declaração de Vetores
Na linguagem C, devemos declarar um vetor da seguinte forma:
tipo_vetor nome_vetor[quantidade_elementos];
O `tipo_vetor` é o tipo de cada elemento do vetor. O `nome_vetor` é o nome da
variável que irá identificar o vetor. A `quantidade_elementos` representa a
quantidade máxima de elementos que o vetor poderá armazenar. Observe que essa
quantidade deve ficar entre colchetes. Os índices do vetor irão de zero até
`quantidade_elementos - 1`. O compilador irá reservar o espaço de memória
correspondente ao que o programador definiu em `quantidade_elementos`.
Vamos ver alguns exemplos de declarações de vetores:
int idades[50];
char nomes[200];
float precos[30];
No exemplo acima temos três declarações de vetores diferentes. Na primeira
linha temos a declaração de um vetor chamado `idades` que terá no máximo 50
elementos do tipo `int`, com índices de 0 a 49. Na segunda linha temos um
vetor chamado `nomes` com 200 elementos do tipo `char`, com índices de 0 a
199. Por fim, temos na última linha um vetor com identificador `precos`, com
espaço para armazenar 30 elementos do tipo `float`, cujos índices variam de 0
a 29.
==== Acessando os elementos de um vetor
Uma vez que um vetor foi declarado, poderemos armazenar e ler os valores dos
elementos desse vetor. Para isso, devemos identificar o elemento do vetor que
queremos acessar através do nome do vetor seguido do índice do elemento entre
colchetes. Observe o exemplo abaixo:
[source,c,numbered]
----
...
int main() {
int v[5], i;
for (i = 0 ; i < 5 ; i++)
scanf("%d", &v[i]);
...
----
// FIXME linhas
Na linha 3, um vetor `v` de 5 elementos do tipo `int` é declarado. Em seguida,
temos um comando `for` que irá se repetir 5 vezes, com a variável `i`
variando de 0 a 4. No corpo da estrutura de repetição, na linha 5, ocorre a
leitura de valores inteiros, na qual cada valor lido é armazenado em um
elemento do vetor `v` com índice igual ao valor da variável `i` em cada
iteração. Para um melhor entendimento, considere a situação hipotética a
seguir. Suponha que o usuário digitou os seguintes valores na leitura de
dados: 4, 3, 7, 2 e 9. Nesse caso, o vetor ficaria da seguinte forma após a
leitura dos valores:
.Configuração do vetor v após a leitura dos valores.
image::images/cap4/vetor_apos_leitura.eps[scaledwidth="30%"]
Uma vez que os elementos de um vetor foram inicializados, isto é, receberam
algum valor através de uma operação de escrita na memória, podemos acessar
tais valores para diversos fins. Por exemplo, podemos implementar um laço para
percorrer todo o vetor e imprimir na tela os valores da cada um de seus
elementos. O trecho de código a seguir ilustra como os elementos de um vetor
são acessados e mostra como um laço para percorrer um vetor pode ser
implementado:
[source,c,numbered]
----
...
printf("%d", v[0]); //mostra o valor 4 na saída
printf("%d", v[2]); // mostra o valor 7 na saída
for (i = 0 ; i < 5 ; i++)
printf("%d ", v[i]); //mostra todos os valores
printf("%d", v[5]);
...
----
Na linha 2 estamos pedindo para ser mostrado o valor do elemento de índice 0
do vetor `v`. Sendo assim, o valor 4 é mostrado na tela. Na linha 3, é
mostrado o valor do elemento de índice 2 do vetor `v`, cujo valor é 7. Já na
linha 4 temos um laço de repetição que irá mostrar todos os valores dos
elementos do vetor `v`. Em seguida, na linha 6, estamos tentando acessar o
elemento de índice 5, que não existe. O programa irá compilar normalmente,
entretanto, ocorrerá um erro em tempo de execução assim que o programa
tentar acessar o elemento inexistente do vetor.
CAUTION: Tenha cuidado ao acessar elementos de um vetor cujo índice não
existe. Caso isso aconteça, o programa irá compilar, entretanto, ocorrerá um
erro em tempo de execução.
Vamos fazer um outro exemplo para fixar melhor o assunto. Digamos que queremos
um programa para ler 20 valores do tipo inteiro e que após isso, sejam
mostrados esses mesmos valores na ordem inversa da qual foram lidos. O exemplo
abaixo mostra a solução do problema.
[source,c,numbered]
----
int main() {
int i, valores[20];
for (i = 0 ; i < 20 ; i++ ) //primeira etapa
scanf("%d", &valores[i]);
for (i = 19 ; i >= 0 ; i--) //segunda etapa
printf("%d ", valores[i]);
return 0;
}
----
Podemos dividir o problema em duas etapas. A primeira é para ``montar'' um vetor
com 20 valores digitados pelo usuário. A segunda parte é para mostrar os
valores desse vetor na ordem inversa da qual foram lidos. Inicialmente,
declaramos o vetor ``valores` com 20 elementos do tipo `int`. Para resolver
cada etapa do problema, utilizamos um laço de repetição com o comando `for`.
Na linha 3, o laço `for` é utilizado para ler os valores, cada um sendo
armazenado em um elemento do vetor. Na linha 5, temos
um outro laço `for`,
cuja variável de controle i é inicializada com o valor 19, que representa o
índice do último elemento do vetor. A condição de parada do laço é que a
variável `i` seja maior ou igual a zero e a última expressão do `for` é o
decremento da variável `i`. Isso significa que o valor da variável `i` irá
de 19 a 0 dentro do laço de repetição. Consequentemente, os valores dos
elementos do vetor `valores` irão ser mostrados na ordem inversa da que foram
lidos.
==== Exercício resolvido
ER 4.1:: Escreva um programa que leia 20 notas de alunos de uma turma. O
programa deve calcular a média da turma e apresentar na tela apenas as notas
dos alunos que ficaram acima da média calculada.
Resposta::
A primeira etapa para resolver esse problema é analisar se precisamos
realmente utilizar um arranjo. Mas antes disso, vamos tentar dividir o problema
em subproblemas menores para facilitar a elaboração da solução.
[width="100%",cols="1,3",frame="topbot",options="header"]
|====
| Subproblema| Descrição
| Subproblema 1|Ler 20 notas de uma turma
| Subproblema 2|Calcular a média da turma considerando as 20 notas lidas
| Subproblema 3|Apresentar na tela as notas da turma que estão acima da
média calculada
|====
O primeiro subproblema é ler 20 notas dos alunos. A princípio conseguiríamos
ler as 20 notas utilizando um laço de repetição e apenas uma variável.
Entretanto, se utilizarmos apenas uma variável para ler as notas, só teremos
a última nota armazenada ao final do laço. Como precisamos de todas as notas
para saber quais delas estão acima da média calculada, a solução do
subproblema 3 requer que um vetor seja utilizado ao invés de apenas uma
variável. Resolveremos, a partir de agora, a questão gradativamente a partir
das soluções dos subproblemas. Vejamos o trecho de código abaixo que resolve
o subproblema 1 da questão.
[source,c,numbered]
----
int main() {
float nota[20];
int i;
for (i = 0 ; i < 20 ; i++ )
scanf("%f", ¬a[i]);
...
----
Como foi explicado, precisaremos de um vetor para armazenar todas as notas da
turma. Portanto, na linha 3 declaramos um vetor de `float` chamado `notas` com
20 elementos. Utilizamos a estrutura de repetição `for` para ler as 20 notas
passadas como entrada para o programa (linha 6 e 7).
O subproblema 2 consiste em calcular a média das 20 notas lidas. Para isso,
precisamos primeiramente somar todas as notas lidas. Uma vez que temos essas
notas armazenadas em um vetor, poderíamos resolver esse subproblema de duas
maneiras. Uma delas é criar, após o laço de leitura das notas, um outro
laço para acessar os elementos do vetor para realizar a soma das notas. A
segunda forma, mais interessante, é somar as notas à medida que forem lidas,
no mesmo laço de repetição. Dessa forma, teremos apenas um laço de
repetição, tornando nosso código mais eficiente. Verifiquemos como ficou o
trecho de código anterior com a inclusão da solução para o subproblema 2.
[source,c,numbered]
----
int main() {
float nota[20], media, soma = 0;
int i;
for (i = 0 ; i < 20 ; i++ ) {
scanf("%f", ¬a[i]);
soma += nota[i];
}
media = soma / 20;
...
----
Perceba que incluímos na declaração as variáveis `media` e `soma`, para
armazenar, respectivamente, os valores da média e da soma das notas. Verifique
também, na linha 8, que a soma das notas é realizada à medida que cada nota
é lida. O cálculo da média, na linha 11, só pode ser feita depois que todas
as notas forem lidas e acumuladas na variável `soma`. Desse modo, ela deve
ficar fora do laço.
O subproblema 3 consiste em apresentar na tela as notas da turma que ficaram
acima da média. Uma vez que já temos a média calculada, podemos resolver
facilmente esse problema. Para tanto, precisamos percorrer todo o vetor e
comparar cada elemento deste com a média. Caso o valor do elemento seja maior
que a média, ele deve ser apresentado na tela. A versão final do programa
está descrita abaixo:
[source,c,numbered]
----
int main() {
float nota[20], media, soma = 0;
int i;
for (i = 0 ; i < 20 ; i++) {
scanf("%f", ¬a[i]);
soma += nota[i];
}
media = soma / 20;
for (i = 0 ; i < 20 ; i++)
if (nota[i] > media)
printf("%f ", nota[i]);
return 0;
}
----
As linhas 12 a 14 resolvem o subproblema 3, utilizando o comando `for` para
percorrer todo o vetor de notas e comparar cada elemento com a média. Para
realizar tal comparação, utilizamos o comando `if`. Caso o elemento `nota[i]`
seja maior que a média, ele será exibido na tela.
=== Strings
Na maioria dos programas que implementamos, precisamos lidar com cadeias de
caracteres no processamento e armazenamento das informações que nos são
fornecidas. Nas linguagens de programação, essas cadeias são chamadas de
*((strings))*. Como não há, na linguagem C, um tipo de dados específico para
representar as strings, utilizamos vetores de elementos do tipo `char` para a
manipulação de cadeias de caracteres. Em outras palavras, quando queremos
declarar uma variável para armazenar uma cadeia de caracteres, declaramos um
vetor do tipo `char`.
No exemplo abaixo, temos a declaração de três strings: `nome`, `logradouro`
e `bairro`. Perceba que cada `string` é um `array` de `char`. Significa dizer
que a string `nome` tem a capacidade de armazenar 60 caracteres, a string
`logradouro` 200 caracteres e a string `bairro` 40 caracteres.
char nome[60];
char logradouro[200];
char bairro[40];
Na linguagem C, toda string tem um caractere especial que determina o seu fim.
Esse caractere é o `\0`, que significa *((NULO))*, e ele é inserido
automaticamente pelo compilador no último elemento do vetor de elementos do
tipo `char`. Por essa razão, 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`.
==== Lendo e imprimindo Strings
Para ler e imprimir strings na linguagem C, podemos utilizar as funções
`scanf()` e `printf()` que já conhecemos. Entretanto, como muitas vezes
precisamos manipular strings no nosso programa, as linguagens de programação
possuem funções pré-definidas para serem utilizadas pelo programador,
facilitando a manipulação das cadeias de caracteres. A linguagem C, por
exemplo, possui as funções `gets()` e `puts()`, elaboradas especialmente para
ler e imprimir `strings`, respectivamente. Vejamos um exemplo.
[source,c,numbered]
----
int main() {
char s[7];
gets(s);
puts(s);
printf("%c", s[4]);
printf("%c", s[2]);
return 0;
}
----
Na linha 3, declaramos uma string `s` que armazena 6 caracteres (além do
caractere NULO). Em seguida, utilizamos a função `gets()`,que faz a leitura
de uma string, esperando que o usuário a digite e tecle ENTER. Na linha 6,
utilizamos a função `puts()`, que imprime na saída padrão a string `s`
lida. Digamos que o usuário digite a string `‘BRASIL’`. Dessa forma, a
cadeia de caracteres será armazenada na memória da seguinte forma:
.Cadeia de caracteres.
image::images/cap4/cadeia_caracteres.eps[scaledwidth="60%"]
Como uma string em C trata-se na verdade de um vetor de caracteres, podemos
acessá-los individualmente do mesmo modo que acessamos os elementos de um
vetor qualquer. Ao serem executadas as linhas 8 e 9 do código anterior, por
exemplo, são exibidos na saída padrão os caracteres ``I'' e ``A'', respectivamente.
==== Manipulando strings
Existem ainda outras funções interessantes para manipular strings na
linguagem C. A tabela abaixo apresenta algumas dessas funções, todas
presentes na biblioteca `string.h`.
[width="100%",cols="1m,2",frame="topbot",options="header"]
|======================
|Função|Descrição
|strlen(s)
|Retorna a quantidade de caracteres da string `s`
|strcpy(s1, s2)
|Copia o conteúdo da string `s2` para
`s1`
|strcat(s1, s2)
|Concatena (junta) o conteúdo da string `s2` em `s1`
|strchr(s, c)
|Retorna a posição (inteiro) do caractere `c` na string `s`
|======================
Para entender melhor o uso destas funções, considere o exemplo a seguir.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/cap4/manipulacao_string.c[code/cap4/manipulacao_string.c]
[source, c,numbered]
----
include::code/cap4/manipulacao_string.c[]
----
Inicialmente declaramos duas strings: `str1` e `str2`. Nas linhas 6 e 7,
utilizamos o comando `gets()` para que o usuário informe as duas strings que
serão armazenadas nas variáveis mencionadas. As linhas 9 e 10 imprimem os
tamanhos das strings `str1` e `str2`, enquanto que a linha 12 é responsável
por concatenar as strings `str2` e `str1`, ou seja, `str1` passa a ter o
conteúdo anterior com a adição do conteúdo de str2. Por fim, a linha 13
exibe o tamanho de `str1` após a sua concatenação com `str2`.
==== Exercício resolvido
ER 4.2:: Escreva um programa que leia uma string e substitua todos os espaços
por um traço (caractere ``-'').
Resposta::
Primeiramente, declaramos uma string `s` com capacidade para armazenar 40
caracteres e utilizamos a função `gets()`, na linha 5, a fim de que o
usuário digite uma string. Tendo a string digitada armazenada na variável
`s`, podemos agora manipulá-la. Para resolver essa questão, é importante ter
entendido o conceito de que uma string é um vetor de caracteres. Na linha 7,
utilizamos um `for` para percorrer os elementos da string. Veja que a
condição de parada do comando `for` é `i < strlen(s)`. Isso significa que o
bloco de instruções será repetido até o final da string, uma vez que
`strlen()` retorna a quantidade de caracteres de uma string. Dentro do `for`,
verificamos se o caractere da posição `i` é igual a um espaço. Caso seja,
esse elemento do array recebe o caractere ``-''. Finalmente, depois do
comando `for`, utilizamos a função `puts()` para imprimir a nova string com
os espaços trocados por ``-''.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/cap4/resolvido4-2.c[code/cap4/resolvido4-2.c]
[source, c,numbered]
----
include::code/cap4/resolvido4-2.c[]
----
=== Matrizes
Como foi dito no início do capítulo, arranjos podem ter várias dimensões.
Quando possuem mais de uma dimensão, eles são chamados de *((matrizes))*. O
conceito de matrizes em programação é bem semelhante ao homônimo da
matemática. A figura 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.
.Matriz M x N
image::images/cap4/matriz.eps[scaledwidth="45%"]
Analisemos os exemplos abaixo. A primeira matriz é 3 x 3, pois possui 3 linhas
e 3 colunas. A segunda matriz é 2 x 3, pois possui 2 linhas e 3 colunas. Se
quisermos saber o elemento da primeira matriz que possui índice A~2,3~, basta
seguirmos em direção à 2ª linha e depois em direção à 3ª coluna. Logo
o valor de A~2,3~ é 1.
.4.5. Exemplos de matrizes
image::images/cap4/matriz-exemplos.eps[scaledwidth="50%"]
Podemos representar essas matrizes na linguagem C acrescentando mais um índice
entre colchetes no identificador do arranjo. Abaixo temos alguns exemplos de
como declaramos matrizes:
int matriz[3][3];
int outra_matriz[2][3];
float matriz_de_float[30][20];
char nomes[10][50];
Na primeira linha temos a declaração de uma matriz de inteiros com 3 linhas e
3 colunas, na segunda novamente uma matriz de inteiros, só que agora com 2
linhas e 3 colunas, e na terceira uma matriz de elementos do tipo `float` com
30 linhas e 20 colunas. Agora atenção para última linha. Trata-se da
declaração de um arranjo de strings. Lembre-se que uma string é um arranjo
de caracteres. Se declararmos uma matriz de `char`, então teremos na prática
um vetor de strings, onde cada linha da matriz é uma cadeia de caracteres.
Já sabemos como declarar matrizes, agora aprenderemos a montá-las a partir da
leitura de dados da entrada padrão. Para isso, precisaremos utilizar dois
comandos `for` aninhados. Considere o exemplo abaixo:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/cap4/matriz_populando.c[code/cap4/matriz_populando.c]
[source, c,numbered]
----
include::code/cap4/matriz_populando.c[]
----
Na linha 2, temos uma declaração de uma matriz 20 x 30. Se quisermos pedir
para o usuário digitar os valores da matriz, precisaremos utilizar um `for`
para percorrer as linhas e um `for` para percorrer as colunas da matriz.
Portanto, na linha 5, temos o primeiro `for` onde `i` irá variar de 0 a 19,
justamente os índices que representam a posição dos elementos nas linhas da
matriz. Na linha 6, temos um outro `for` dentro do primeiro, onde `j` irá
variar de 0 a 29, que são justamente os índices que representam a posição
dos elementos nas colunas da matriz. Perceba que quando `i = 0`, `j` irá
variar de 0 a 29. Depois `i` passa a valer 1 e novamente o `j` irá variar de 0
a 29 novamente. Isso acontecerá repetidamente até `i` atingir o valor 19. Em
suma, o código anterior preenche os elementos da matriz linha por linha.
Inicia preenchendo a linha de índice 0, em seguida preenche a linha de índice
1, seguindo de forma sucessiva até que a linha de índice 19 seja preenchida.
=== Recapitulando
Como vimos neste capítulo, arranjos consistem em um conjunto de elementos do
mesmo tipo, que podem ter diferentes dimensões e são acessados por um
índice. Os arranjos de uma dimensão são chamados de vetores e quando possuem
mais de duas dimensões são chamados de matrizes.
Aprendemos também que strings são cadeias (arranjos) de caracteres, ou seja,
um vetor de elementos do tipo `char`. Devido à sua importância para a
programação de computadores, as linguagens de programação disponibilizam um
conjunto de funções para facilitar sua manipulação. Algumas das funções
mais utilizadas na linguagem C foram conhecidas neste capítulo, contudo, deve
ficar claro ao leitor que existe um número bem maior de funções com tal
objetivo.
=== Exercícios Propostos
1. Crie um programa que armazene números em dois vetores inteiros de cinco
elementos cada, depois gere e imprima o vetor soma dos dois.
2. Crie um programa que armazene 10 números em um vetor `A`, e gere um vetor
`B` onde cada elemento é o quadrado do elemento de `A`.
+
Exemplo:
+
....
A[1] = 4 B[1] = 16
A[2] = 3 B[2] = 9
A[3] = 6 B[3] = 36
....
3. Escreva um programa para ler uma string qualquer e exiba as seguintes
informações: quantidade de caracteres, primeira e última letra.
4. Escreva um programa para ler uma frase de no máximo 70 caracteres e exibir
a quantidade de vogais dessa frase.
5. Escreva um programa que leia uma string qualquer e mostre-a invertida.
+
--
Exemplo:
....
Entrada: casa <ENTER>
Saída: asac
....
--
6. Um palíndromo é uma cadeia de caracteres que representa a mesma palavra
nos sentidos direto e inverso. Por exemplo, ``asa'' é um palíndromo, porque o
inverso dela também é ``asa''. Faça um programa que leia uma string e diga
se esta é ou não um palíndromo.
7. Escreva um programa para ler 9 números inteiros para preencher uma matriz
`D` 3x3, ou seja, com 3 linhas e 3 colunas (considere que não serão
informados valores duplicados). A seguir, ler um número inteiro `X` e escrever
uma mensagem indicando se o valor de `X` existe ou não na matriz `D`.
////
Sempre termine os arquivos com uma linha em branco.
////
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/cap4/Makefile
CFLAGS=-Wall -g
all: manipulacao_string \
matriz_populando resolvido4-2 \
clean:
rm -rf manipulacao_string \
matriz_populando resolvido4-2
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/cap4/manipulacao_string.c
#include <stdio.h>
int main() {
char str1[100], str2[100];
gets(str1);
gets(str2);
printf("%d", strlen(str1));
printf("%d", strlen(str2));
strcat(str1, str2);
printf("%d", strlen(str1));
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/cap4/matriz_populando.c
int main() {
int matriz[20][30];
int i, j;
for (i=0; i < 20; i++)
for (j = 0; j < 30; j++)
scanf("%d", &matriz[i][j]);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/cap4/resolvido4-2.c
int main() {
char s[40];
int i;
gets(s);
for (i=0; i < strlen(s); i++) {
if (s[i] == ' ')
s[i] = '-';
}
puts(s);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/Makefile
CFLAGS=-Wall -g
all: break_interrompe \
comparaab continue_desvio \
entre10e20 \
do_while \
entre10e20 \
for_cont for_cont_decremento \
if5 \
laco_infinito \
menorq20 mostrado10vezes menor_deles menor_deles_resposta\
nota7 notas_alunos \
semana \
whilenota
clean:
rm -rf break_interrompe \
comparaab continue_desvio \
entre10e20 \
do_while \
entre10e20 \
for_cont for_cont_decremento \
if5 \
laco_infinito \
menorq20 mostrado10vezes menor_deles menor_deles_resposta\
nota7 notas_alunos \
semana \
whilenota
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/break_interrompe.c
#include <stdio.h>
int main() {
int cont;
for (cont = 1; ; cont++) {
printf("Valor de cont: %i\n", cont);
if (cont == 10) break;
}
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/comparaab.c
#include <stdio.h>
int main() {
int a, b;
scanf("%i", &a);
scanf("%i", &b);
if (a > b)
printf("A e' maior que B.");
else if (b > a)
printf("B e' maior que A.");
else
printf("A = B");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/continue_desvio.c
#include <stdio.h>
int main() {
int cont;
for (cont = 1; cont <= 20; cont++) {
if (cont % 2 == 0) continue;
printf("Valor de cont: %i\n", cont);
}
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/do_while.c
#include <stdio.h>
int main() {
int cont = 1;
do {
printf("Isto sera' mostrado 10 vezes.\n");
cont++;
} while (cont <= 10);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/entre10e20.c
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
if (x > 10 && x < 20)
printf("x esta' entre 10 e 20.");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/for_cont.c
#include <stdio.h>
int main() {
int cont;
for (cont = 1; cont <= 10; cont++)
printf("Isto sera' mostrado 10 vezes.\n");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/for_cont_decremento.c
#include <stdio.h>
int main() {
int cont;
for (cont = 10; cont > 0; cont--)
printf("Valor de cont: %i\n", cont);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/if5.c
#include <stdio.h>
int main() {
int x = 5;
if (x)
printf("Isto sera' mostrado");
if (x - 5)
printf("Isto nao sera' mostrado");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/laco_infinito.c
#include <stdio.h>
int main() {
int cont;
for (cont = 1; ; cont++)
printf("Laco infinito.\n");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/menor_deles.c
#include <stdio.h>
int main() {
int quantidade = 1;
float valor, menor;
printf("Informe um valor: ");
scanf("%f", &menor);
while (quantidade < 50) {
printf("Informe um valor: ");
scanf("%f", &valor);
if (valor < menor)
menor = valor;
}
printf("Menor valor lido: %f", menor);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/menor_deles_resposta.c
#include <stdio.h>
int main() {
int quantidade = 1;
float valor, menor;
printf("Informe um valor: ");
scanf("%f", &menor);
while (quantidade < 50) {
printf("Informe um valor: ");
scanf("%f", &valor);
if (valor < menor)
menor = valor;
quantidade++; // Solucao do problema
}
printf("Menor valor lido: %.2f", menor);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/menorq20.c
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
if (x < 20)
printf("O valor de x e' menor que 20.");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/mostrado10vezes.c
#include <stdio.h>
int main() {
int cont = 1;
while (cont <= 10) {
printf("Isto sera' mostrado 10 vezes.\n");
cont++;
}
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/nota7.c
#include <stdio.h>
int main() {
float nota;
scanf("%f", ¬a);
if (nota >= 7)
printf("Aprovado");
else
printf("Reprovado");
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/notas_alunos.c
#include <stdio.h>
int main() {
float nota = 0, soma = 0;
int cont = 0;
while (nota != -1) {
printf("Entre com a nota: ");
scanf("%f", ¬a);
if (nota != -1) {
soma += nota;
cont++;
}
}
printf("Media das notas: %.2f", soma / cont);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/semana.c
#include <stdio.h>
int main() {
int semana;
printf("Digite um numero de 1 a 7: ");
scanf("%d", &semana);
switch (semana) {
case 1:
printf("Domingo");
break;
case 2:
printf("Segunda-feira");
break;
case 3:
printf("Terca-feira");
break;
case 4:
printf("Quarta-feira");
break;
case 5:
printf("Quinta-feira");
break;
case 6:
printf("Sexta-feira");
break;
case 7:
printf("Sabado");
break;
default:
printf("Numero fora do intervalo permitido.");
break;
}
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/estruturas-de-controle/whilenota.c
#include <stdio.h>
int main() {
float nota;
scanf("%f", ¬a);
while (nota != -1) {
if (nota >= 7)
printf("Aprovado\n");
else
printf("Reprovado\n");
scanf("%f", ¬a);
}
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/Makefile
CFLAGS=-Wall -g -c
#
# Este Makefile precisa ser revisto,
# ele não está gerando os executáveis no linux.
#
all: equacao \
equacao2 \
exercicio4 \
fatorial2 \
fatorial \
media_globais \
media_param2 \
media_param \
media_proc
\
media_sem_proc \
retorno_func \
retorno_proc \
situacao \
situacao_prot \
sombra \
troca \
troca_glob \
troca_main \
troca_ref
clean:
rm -rf \
equacao2 \
equacao \
exercicio4 \
fatorial2 \
fatorial \
media_globais \
media_param2 \
media_param \
media_proc \
media_sem_proc \
retorno_func \
retorno_proc \
situacao \
situacao_prot \
sombra \
troca \
troca_glob \
troca_main \
troca_ref
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/equacao.c
#include <stdio.h>
#include <math.h>
float calculo_delta(float a, float b, float c) { // <1>
return (b * b) - (4 * a * c);
}
float raiz_positiva(float a, float b, float delta) { // <2>
return (-b + sqrt(delta)) / 2 * a;
}
float raiz_negativa(float a, float b, float delta) { // <3>
return (-b - sqrt(delta)) / 2 * a;
}
int main() {
float a, b, c;
float delta;
printf("Entre os coeficientes A, B e C, nesta ordem: ");
scanf("%f %f %f", &a, &b, &c);
delta = calculo_delta(a, b, c);
if (delta < 0.0)
printf("Delta negativo, nao existem raizes\n");
else if (delta == 0)
printf("Delta = 0, uma raiz: %f\n", raiz_positiva(a, b, delta));
else
printf("Raizes: %f e %f\n", raiz_positiva(a, b, delta), raiz_negativa(a, b, delta));
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/equacao.out
Entre os coeficientes A, B e C, nesta ordem: 1.0 -5.0 6.0
Raizes: 3.000000 e 2.000000
Entre os coeficientes A, B e C, nesta ordem: 1.0 -6.0 9.0
Delta = 0, uma raiz: 3.000000
Entre os coeficientes A, B e C, nesta ordem: 4.0 -3.0 7.0
Delta negativo, nao existem raizes
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/equacao2.c
#include <stdio.h>
#include <math.h>
float calculo_delta(float a, float b, float c) {
return (b * b) - (4 * a * c);
}
float raiz(float a, float b, float delta, int sinal) {
if (sinal == 1)
return (-b + sqrt(delta)) / 2 * a;
else
return (-b - sqrt(delta)) / 2 * a;
}
int main() {
float a, b, c;
float delta;
printf("Entre os coeficientes A, B e C, nesta ordem: ");
scanf("%f %f %f", &a, &b, &c);
delta = calculo_delta(a, b, c);
if (delta < 0.0)
printf("Delta negativo, nao existem raizes\n");
else if (delta == 0)
printf("Delta = 0, uma raiz: %f\n", raiz(a, b, delta, 1));
else
printf("Raizes: %f e %f\n", raiz(a, b, delta, 1), raiz(a, b, delta, -1));
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/exercicio4.c
#include <stdio.h>
int x = 5;
int y = 9;
int f(int x) {
int z = x * x;
return z * y;
}
int main() {
int y = 3;
printf("%d \n", x);
printf("%d \n", f(y));
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/fatorial.c
#include <stdio.h>
// prototipo
int fatorial(int); // <1>
int main() {
int n;
printf("Entre o numero para calcular o fatorial: ");
scanf("%d", &n);
printf("%d! = %d\n", n, fatorial(n));
return 0;
}
int fatorial(int n) {
if (n == 0 || n == 1)
return 1; // <2>
else
return n * fatorial(n - 1); // <3>
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/fatorial.out
Entre o numero para calcular o fatorial: 4
4! = 24
Entre o numero para calcular o fatorial: 5
5! = 120
Entre o numero para calcular o fatorial: 6
6! = 720
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/fatorial2.c
#include <stdio.h>
// prototipo
int fatorial(int);
int main() {
int n;
printf("Entre o numero para calcular o fatorial: ");
scanf("%d", &n);
printf("%d! = %d\n", n, fatorial(n));
return 0;
}
int fatorial(int n) {
if (n <= 1) // <1>
return 1;
else
return n * fatorial(n - 1);
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/fatorial2.out
Entre o numero para calcular o fatorial: 5
5! = 120
Sandman:funcoes andrei$ ./fat
Entre o numero para calcular o fatorial: -4
-4! = 1
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/fatorial_neg.out
Entre o numero para calcular o fatorial: -4
Segmentation fault: 11
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_erro.c
#include <stdio.h>
float calc_media() {
return (nota1 + nota2 + nota3) / 3.0; // <1>
}
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
printf("Media: %f\n", calc_media());
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_globais.c
#include <stdio.h>
float nota1, nota2, nota3; // <1>
float calc_media() {
return (nota1 + nota2 + nota3) / 3.0; // <2>
}
int main() {
printf("Entre as três notas: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3); // <3>
printf("Media: %f\n", calc_media());
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_param.c
#include <stdio.h>
#include <math.h>
void imprime_separador(float nota) { // <1>
int i;
printf("\n");
for (i = 0; i < (int) lround(nota * 5.0); i++) {
printf("=");
}
printf(" %3.2f / 10.0\n", nota);
printf("\n");
}
int main () {
float nota1, nota2, nota3, media;
printf("Entre a nota da primeira prova: ");
scanf("%f", ¬a1);
imprime_separador(nota1); // <2>
printf("Entre a nota da segunda prova: ");
scanf("%f", ¬a2);
imprime_separador(nota2);
printf("Entre a nota da terceira prova: ");
scanf("%f", ¬a3);
imprime_separador(nota3);
media = (nota1 + nota2 + nota3) / 3.0;
printf("Media: %3.2f\n", media);
imprime_separador(media);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_param.out
Entre a nota da primeira prova: 6.2
=============================== 6.20 / 10.0
Entre a nota da segunda prova: 7.8
======================================= 7.80 / 10.0
Entre a nota da terceira prova: 9.2
============================================== 9.20 / 10.0
Media: 7.73
======================================= 7.73 / 10.0
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_param2.c
#include <stdio.h>
float calc_media(float n1, float n2, float n3) {
return (n1 + n2 + n3) / 3.0;
}
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
printf("Media: %f\n", calc_media(nota1, nota2, nota3));
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_proc.c
#include <stdio.h>
void imprime_separador() { // <1>
printf("\n"); // <2>
printf("=============================================\n"); // <2>
printf("\n"); // <2>
}
int main () {
float nota1, nota2, nota3, media;
printf("Entre a nota da primeira prova: ");
scanf("%f", ¬a1);
imprime_separador(); // <3>
printf("Entre a nota da segunda prova: ");
scanf("%f", ¬a2);
imprime_separador();
printf("Entre a nota da terceira prova: ");
scanf("%f", ¬a3);
imprime_separador();
media = (nota1 + nota2 + nota3) / 3.0;
printf("Media: %f\n", media);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_sem_proc.c
#include <stdio.h>
int main () {
float nota1, nota2, nota3, media;
printf("Entre a nota da primeira prova: ");
scanf("%f", ¬a1);
printf("\n"); // <1>
printf("=============================================\n"); // <1>
printf("\n"); // <1>
printf("Entre a nota da segunda prova: ");
scanf("%f", ¬a2);
printf("\n");
printf("=============================================\n");
printf("\n");
printf("Entre a nota da terceira prova: ");
scanf("%f", ¬a3);
printf("\n");
printf("=============================================\n");
printf("\n");
media = (nota1 + nota2 + nota3) / 3.0;
printf("Media: %f\n", media);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/media_sem_proc.out
Entre a nota da primeira prova: 7.0
=============================================
Entre a nota da segunda prova: 8.0
=============================================
Entre a nota da terceira prova: 9.6
=============================================
Media: 8.200000
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/retorno_func.c
#include <stdio.h>
int possui_negativo(float n1, float n2, float n3) { // <1>
if (n1 < 0.0 || n2 < 0.0 || n3 < 0.0) // <2>
return 1; // <3>
return 0; // <4>
}
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas, separadas por espacos: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
if (possui_negativo(nota1, nota2, nota3) == 1) // <5>
printf("Nao e' possivel calcular a media, uma ou mais notas sao negativas\n");
else
printf("Media: %f\n", (nota1 + nota2 + nota3) / 3.0);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/retorno_func.out
Entre as três notas, separadas por espacos: 6.8 9.7 -2.3
Nao e' possivel calcular a media, uma ou mais notas sao negativas
Entre as três notas, separadas por espacos: 6.8 9.7 7.2
Media: 7.900000
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/retorno_proc.c
#include <stdio.h>
void possui_negativo(float n1, float n2, float n3) {
if (n1 < 0.0) {
printf("Numero negativo encontrado!\n");
return; // <1>
}
if (n2 < 0.0) {
printf("Numero negativo encontrado!\n");
return;
}
if (n3 < 0.0) {
printf("Numero negativo encontrado!\n");
return;
}
printf("Nenhum numero negativo encontrado\n"); // <2>
}
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas, separadas por espacos: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
possui_negativo(nota1, nota2, nota3);
printf("Media: %f\n", (nota1 + nota2 + nota3) / 3.0);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/retorno_proc.out
Entre as três notas, separadas por espacos: 4.5 5.6 8.9
Nenhum numero negativo encontrado
Media: 6.333333
Entre as três notas, separadas por espacos: 4.5 -6.7 9.9
Numero negativo encontrado!
Media: 2.566667
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/situacao.c
#include <stdio.h>
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
situacao(nota1, nota2, nota3); // <1>
return 0;
}
void situacao(float n1, float n2, float n3) { // <2>
float media = (n1 + n2 + n3) / 3.0;
printf("Media %f, ", media);
if (media >= 7.0)
printf("Aluno aprovado\n");
else if (media < 4.0)
printf("Aluno reprovado por media\n");
else
printf("Aluno na prova final");
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/situacao.out
situacao.c:12:6: error: conflicting types for 'situacao'
void situacao(float n1, float n2, float n3) { // <2>
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/situacao_prot.c
#include <stdio.h>
// Prototipo da funcao situacao
void situacao(float, float, float); // <1>
int main() {
float nota1, nota2, nota3;
printf("Entre as três notas: ");
scanf("%f %f %f", ¬a1, ¬a2, ¬a3);
situacao(nota1, nota2, nota3); // <2>
}
void situacao(float n1, float n2, float n3) {
float media = (n1 + n2 + n3) / 3.0;
printf("Media %f, ", media);
if (media >= 7.0)
printf("Aluno aprovado\n");
else if (media < 4.0)
printf("Aluno reprovado por media\n");
else
printf("Aluno na prova final");
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/situacao_prot.out
Entre as três notas: 6.8 5.2 9.0
Media 7.000000, Aluno aprovado
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/sombra.c
#include <stdio.h>
int x = 5; // <1>
void f() {
int x = 60; // <2>
int y = x * x; // <3>
printf("x = %d, y = %d\n", x, y);
}
int g() {
int y = x * x; // <4>
return y;
}
int main() {
f();
printf("g = %d\n", g());
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/sombra.out
x = 60, y = 3600
g = 25
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca.c
#include <stdio.h>
void troca_variaveis(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("Dentro de troca_variaveis: a = %d, b = %d\n", a, b);
}
int main() {
int x = 5;
int y = 7;
printf("Antes da troca: x = %d, y = %d\n", x, y);
troca_variaveis(x, y);
printf("Depois da troca: x = %d, y = %d\n", x, y);
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca.out
Antes da troca: x = 5, y = 7
Dentro de troca_variaveis: a = 7, b = 5
Depois da troca: x = 5, y = 7
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca_glob.c
#include <stdio.h>
int x = 5; // <1>
int y = 7;
void troca_variaveis() { // <2>
int temp = x;
x = y;
y = temp;
printf("Dentro de troca_variaveis: x = %d, y = %d\n", x, y);
}
int main() {
printf("Antes da troca: x = %d, y = %d\n", x, y);
troca_variaveis();
printf("Depois da troca: x = %d, y = %d\n", x, y);
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca_glob.out
Antes da troca: x = 5, y = 7
Dentro de troca_variaveis: x = 7, y = 5
Depois da troca: x = 7, y = 5
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca_main.c
#include <stdio.h>
int main() {
int x = 5;
int y = 7;
printf("Antes da troca: x = %d, y = %d\n", x, y);
// troca_variaveis
int a = x, b = y;
int temp = a;
a = b;
b = temp;
printf("Dentro de troca_variaveis: a = %d, b = %d\n", a, b);
// fim de troca_variaveis
printf("Depois da troca: x = %d, y = %d\n", x, y);
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca_ref.c
#include <stdio.h>
void troca_variaveis(int *a, int *b) { // <1>
int temp = *a;
*a = *b; // <2>
*b = temp;
printf("Dentro de troca_variaveis: a = %d, b = %d\n", *a, *b);
}
int main() {
int x = 5;
int y = 7;
printf("Antes da troca: x = %d, y = %d\n", x, y);
troca_variaveis(&x, &y); // <3>
printf("Depois da troca: x = %d, y = %d\n", x, y);
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/funcoes/troca_ref.out
Antes da troca: x = 5, y = 7
Dentro de troca_variaveis: a = 7, b = 5
Depois da troca: x = 7, y = 5
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/helloworld.c
/* Hello World program */
#include<stdio.h> // <1>
main()
{
printf("Hello World"); // <2> imprime "Hello Word" na tela.
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/calculo_das_medias.c
#include <stdio.h>
typedef struct {
int matricula;
char nome[100];
float nota1;
float nota2;
} Aluno;
#define QUANTIDADE_DE_ALUNOS 3
int main(){
Aluno alunos[QUANTIDADE_DE_ALUNOS];
printf("Dados: nome(sem espacos), matricula, nota1, nota2\n");
for(int i=0; (i < QUANTIDADE_DE_ALUNOS); i++){
printf("\nInforme os dados do aluno(%i): ",i+1);
scanf("%s %i %f %f",alunos[i].nome, &alunos[i].matricula,
&alunos[i].nota1, &alunos[i].nota2);
}
printf("\nMatricula\tNome\tMedia\n");
for(int i=0; (i < QUANTIDADE_DE_ALUNOS);
i++){
printf("%i\t%s\t%1.2f\n",alunos[i].matricula,alunos[i].nome,
(alunos[i].nota1 + alunos[i].nota2)/2);
}
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/calculo_das_medias.data
Jesuíno 2887399 6.0 7.5
Maria 2887398 7.0 9.0
Virgulino 2887400 10.0 8.0
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/calculo_das_medias_pseudo.txt
QUANTIDADE_DE_ALUNOS = 3
DECLARA alunos: Aluno[QUANTIDADE_DE_ALUNOS]
PARA i=0 ATÉ QUANTIDADE_DE_ALUNOS FAÇA
LEIA alunos[i].nome
LEIA alunos[i].matricula
LEIA alunos[i].nota1
LEIA alunos[i].nota2
FIM_PARA
PARA i=0 ATÉ QUANTIDADE_DE_ALUNOS FAÇA
ESCREVA alunos[i].matricula
ESCREVA alunos[i].nome
ESCREVA (alunos[i].nota1 + alunos[i].nota2)/2 <1>
FIM_PARA
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/calculo_das_medias_simulacao.txt
Dados do aluno: nome(sem espacos), matricula, nota1, nota2
Informe os dados do aluno(1): Jesuíno 2887399 6.0 7.5
Informe os dados do aluno(2): Maria 2887398 7.0 9.0
Informe os dados do aluno(3): Virgulino 2887400 10.0 8.0
Matricula Nome Media
2887399 Jesuíno 6.75
2887398 Maria 8.00
2887400 Virgulino 9.00
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/conta_bancaria.c
#include <stdio.h>
typedef struct {
char nome[256];
long long cpf;
} Cliente;
typedef struct {
long numero_da_conta;
long cpf_do_cliente;
double saldo;
} Conta;
#define QUANTIDADE_DE_CLIENTES 3
#define OPERACAO_SAQUE 1
#define OPERACAO_DEPOSITO 2
int main(){
Cliente clientes[QUANTIDADE_DE_CLIENTES];
Conta contas[QUANTIDADE_DE_CLIENTES];
printf("Campos: cpf nome deposito-inicial\n");
for(long i=0; (i < QUANTIDADE_DE_CLIENTES); i++){
printf("\nDados para abertura da conta(%ld): ",i+1);
scanf("%Ld %s %lf",&clientes[i].cpf,clientes[i].nome,
&contas[i].saldo);
contas[i].numero_da_conta = i;
contas[i].cpf_do_cliente = clientes[i].cpf;
printf("\nCliente: %s Conta: %ld Saldo inicial: %1.2lf\n",
clientes[i].nome, contas[i].numero_da_conta, contas[i].saldo);
}
int operacao; // como ainda não aprendemos a comparar strings,
// vamos usar 'operação' como numérico.
long num_conta;
double valor;
int sair=0; // FALSE
while (!sair){
printf("\nInforme a operação: 1-Saque 2-Deposito 3-Sair: ");
scanf("%d", &operacao);
if (operacao == OPERACAO_SAQUE || operacao == OPERACAO_DEPOSITO){
printf("\nInforme numero-da-conta e valor: ");
scanf("%ld %lf", &num_conta, &valor);
for(int i=0; (i < QUANTIDADE_DE_CLIENTES); i++){
if (contas[i].numero_da_conta == num_conta) {
if (operacao == OPERACAO_SAQUE){
contas[i].saldo -= valor;
printf("\nSAQUE: %1.2lf", valor);
}
if (operacao == OPERACAO_DEPOSITO){
contas[i].saldo += valor;
printf("\nDEPOSITO: %1.2lf", valor);
}
for(int j=0; j < QUANTIDADE_DE_CLIENTES; j++){
if (clientes[j].cpf == contas[i].cpf_do_cliente)
printf("\nCliente: %s Saldo atual: %1.2lf",
clientes[j].nome, contas[i].saldo);
}
}
}
}else{
sair = 1; // TRUE
}
}
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/conta_bancaria_simulacao.txt
Campos: cpf nome deposito-inicial
Dados para abertura da conta(1): 48755891748 Jesuíno 1500
Cliente: Jesuíno Conta: 0 Saldo inicial: 1500.00
Dados para abertura da conta(2): 72779162201 Maria 200
Cliente: Maria Conta: 1 Saldo inicial: 200.00
Dados para abertura da conta(3): 71443626406 Virgulino 600
Cliente: Virgulino Conta: 2 Saldo inicial: 600.00
Informe a operação: 1-Saque 2-Deposito 3-Sair: 1
Informe numero-da-conta e valor: 0 300
SAQUE: 300.00
Cliente: Jesuíno Saldo atual: 1200.00
Informe a operação: 1-Saque 2-Deposito 3-Sair: 2
Informe numero-da-conta e valor: 2 400
DEPOSITO: 400.00
Cliente: Virgulino Saldo atual: 1000.00
Informe a operação: 1-Saque 2-Deposito 3-Sair: 3
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/exercicio_aluno.data
Maria 12887398 7.0 9.0
Jesuíno 12887399 3.5 6.0
Virgulino 12887400 10.0 8.0
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/exercicio_pessoa.data
Jesuíno 1.82 79 48755891748 m
Maria 1.66 52 72779162201 f
Virgulino 1.75 80 71443626406 m
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/exercicio_ponto.data
1 3
-2 4
-3 -3
2 -5
4 0
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/exercicio_produto.data
11 laranja 1.4
12 rosquinha 3
13 leite-moca 4.5
14 farinha-de-trigo 2.7
15 coxinha 1.5
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/imc_calculo.c
#include <stdio.h>
typedef struct{
char nome[100];
char sexo; // 'm': masculino, 'f': femino
float peso;
float altura;
long long cpf;
} Pessoa;
#define QUANTIDADE_DE_PESSOAS 3
int main(){
Pessoa pessoas[QUANTIDADE_DE_PESSOAS];
printf("Campos: nome, altura, peso, cpf, sexo\n");
for(int i=0; (i < QUANTIDADE_DE_PESSOAS); i++){
printf("\nInforme os dados da pessoa(%i): ",i+1);
scanf("%s %f %f %Lu %c",pessoas[i].nome, &pessoas[i].altura,
&pessoas[i].peso, &pessoas[i].cpf, &pessoas[i].sexo);
}
printf("\nInforme o CPF da pessoa: ");
long long cpf_localizador;
scanf("%Lu",&cpf_localizador); // <1>
printf("\nSexo\tNome\tIMC");
for(int i=0; (i < QUANTIDADE_DE_PESSOAS); i++){ //<2>
if (cpf_localizador == pessoas[i].cpf){ //<2>
float imc = pessoas[i].peso / (pessoas[i].altura *
pessoas[i].altura);
printf("\n%c\t%s\t%1.2f\n",pessoas[i].sexo,
pessoas[i].nome, imc);
break;
}
}
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/imc_calculo_pseudo.txt
QUANTIDADE_DE_PESSOAS = 3
PARA i=0 ATÉ QUANTIDADE_DE_PESSOAS FAÇA
LEIA pessoas[i].nome
LEIA pessoas[i].altura
LEIA pessoas[i].peso
LEIA pessoas[i].cpf
LEIA pessoas[i].sexo
FIM-PARA
DECLARA cpf_localizador: NUMÉRICO
LEIA cpf_localizador <1>
PARA i=0 ATÉ QUANTIDADE_DE_PESSOAS FAÇA <2>
SE pessoas[i].cpf == cpf_localizador ENTÃO <2>
ESCREVE pessoas[i].nome
ESCREVE pessoas[i].sexo
// IMC = peso / (altura * altura)
ESCREVE pessoas[i].peso / (pessoas[i].altura * pessoas[i].altura)
FIM-PARA
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/imc_calculo_simulacao.txt
Campos: nome, altura, peso, cpf, sexo
Informe os dados da pessoa(1): Jesuíno 1.82 79 48755891748 m
Informe os dados da pessoa(2): Maria 1.66 52 72779162201 f
Informe os dados da pessoa(3): Virgulino 1.75 80 71443626406 m
Informe o CPF da pessoa: 72779162201
Sexo Nome IMC
f Maria 18.87
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/ponto_proximo.c
#include <stdio.h>
#include <limits.h> // contém definição de INT_MAX
typedef struct{
int x;
int y;
} Ponto;
#define QUANTIDADE_DE_PONTOS 5
int main(){
Ponto pontos[QUANTIDADE_DE_PONTOS];
printf("Campos: x, y\n");
for(int i=0; (i < QUANTIDADE_DE_PONTOS); i++){
printf("\nInforme as coordenadas do ponto(%i): ",i+1);
scanf("%d %d",&pontos[i].x,&pontos[i].y);
}
int menor_distancia_ao_quadrado = INT_MAX; // maior inteiro
int ponto_mais_proximo = 1;
for(int i=1; (i < QUANTIDADE_DE_PONTOS); i++){
int distancia_ao_quadrado = (pontos[i].x-pontos[0].x)*
(pontos[i].x-pontos[0].x)+(pontos[i].y-pontos[0].y)*
(pontos[i].y-pontos[0].y);
if(distancia_ao_quadrado < menor_distancia_ao_quadrado){
ponto_mais_proximo = i;
menor_distancia_ao_quadrado = distancia_ao_quadrado;
}
}
printf("\nPonto mais proximo: (%d,%d)\n",
pontos[ponto_mais_proximo].x,
pontos[ponto_mais_proximo].y);
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/ponto_proximo.data
0 0
4 6
6 1
5 3
7 2
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/ponto_proximo_pseudo.txt
QUANTIDADE_DE_PONTOS = 5
PARA i=0 ATÉ QUANTIDADE_DE_PONTOS FAÇA
LEIA p[i].x
LEIA p[i].y
FIM_PARA
menor_distancia_ao_quadrado = MAIOR_INTEIRO <1>
ponto_mais_proximo = 1 <2>
PARA i=1 ATÉ QUANTIDADE_DE_PONTOS FAÇA
distancia_ao_quadrado = (pontos[i].x-pontos[0].x)*
(pontos[i].x-pontos[0].x)+(pontos[i].y-pontos[0].y)*
(pontos[i].y-pontos[0].y) <3>
SE distancia_ao_quadrado < menor_distancia_ao_quadrado ENTÃO <1>
ponto_mais_proximo = i <2>
menor_distancia_ao_quadrado = distancia_ao_quadrado <1>
FIM_PARA
ESCREVA p[ponto_mais_proximo].x,p[ponto_mais_proximo].y
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/ponto_proximo_simulacao.txt
Campos: x, y
Informe as coordenadas do ponto(1): 0 0
Informe as coordenadas do ponto(2): 4 6
Informe as coordenadas do ponto(3): 6 1
Informe as coordenadas do ponto(4): 5 3
Informe as coordenadas do ponto(5): 7 2
Ponto mais proximo: (5,3)
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/prob_conta_bancaria_pseudo.txt
QUANTIDADE_DE_CLIENTES = 3
DECLARA clientes: Cliente[QUANTIDADE_DE_CLIENTES]
DECLARA contas: Conta[QUANTIDADE_DE_CLIENTES]
PARA i=0 ATÉ QUANTIDADE_DE_CLIENTES FAÇA
LEIA clientes[i].cpf
LEIA clientes[i].nome
LEIA contas[i].saldo // depósito inicial
clientes[i].codigo = i
contas[i].numero_da_conta = i
contas[i].codigo_do_cliente = clientes[i].codigo
FIM_PARA
DECLARA operacao: TEXTUAL
DECLARA num_conta, valor, sair=0: NUMÉRICO
ENQUANTO sair == 0 FAÇA
LEIA operacao
SE operacao == "saque" OU operacao == "deposito" ENTÃO
LEIA num_conta, valor
PARA i=0 ATÉ QUANTIDADE_DE_CLIENTES FAÇA
SE contas[i].numero_da_conta == num_conta ENTÃO
SE operacao == "saque" ENTÃO
contas[i].saldo = contas[i].saldo - valor
SE operacao == "deposito" ENTÃO
contas[i].saldo = contas[i].saldo + valor
PARA j=0 ATÉ QUANTIDADE_DE_CLIENTES FAÇA
SE clientes[j].codigo == contas[i].codigo_do_cliente ENTÃO
ESCREVE clientes[j].nome, contas[i].saldo
FIM_PARA
FIM_PARA
SENÃO operacao == "sair" ENTÃO
sair = 1
FIM_ENQUANTO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/prob_supermercado_pseudo.txt
QUANTIDADE_DE_PRODUTOS = 5
DECLARA produtos: Produto[QUANTIDADE_DE_PRODUTOS]
PARA i=0 ATÉ QUANTIDADE_DE_PRODUTOS FAÇA
LEIA produtos[i].codigo
LEIA produtos[i].nome
LEIA produtos[i].preco
FIM_PARA
PARA i=0 ATÉ QUANTIDADE_DE_PRODUTOS FAÇA
ESCREVA produtos[i].codigo
ESCREVA produtos[i].nome
FIM_PARA
DECLARA codigo_digitado: NUMÉRICO
LEIA codigo_digitado
PARA i=0 ATÉ QUANTIDADE_DE_PRODUTOS FAÇA
SE produtos[i].codigo == codigo_digitado ENTÃO
ESCREVA produtos[i].preco
FIM_PARA
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_aluno.c
typedef struct {
int matricula;
char nome[100];
float nota1;
float nota2;
} Aluno;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_aluno_exemplo.c
#include <stdio.h>
#include <string.h>
typedef struct {
int matricula;
char nome[100];
float nota1;
float nota2;
} Aluno;
int main(){
Aluno aluno;
aluno.matricula = 201328; //<1>
strncpy(aluno.nome, "Maria Bonita", sizeof(aluno.nome)); //<2>
aluno.nota1 = 8.0; //<1>
aluno.nota2 = 9.0; //<1>
printf("\n%d %s %1.2f %1.2f", aluno.matricula, aluno.nome, // <3>
aluno.nota1, aluno.nota2); //<3>
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_aluno_exemplo_simulacao.txt
201328 Maria Bonita 8.00 9.00
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_aluno_pseudo.txt
REGISTRO Aluno
matricula: NUMÉRICO
nome: TEXTO
nota1, nota2: NUMÉRICO
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_atribuicao.c
#include <stdio.h>
typedef struct {
int matricula; // <1>
char nome[100];// <1>
float nota1; // <1>
float nota2; // <1>
} Aluno;
typedef struct {
char nome[256];// <2>
long long cpf; // <2>
} Cliente;
int main() {
Aluno a = {15, "Virgulino da Silva", 9.0f, 10.0f}; // <1>
Cliente c = {"Maria Bonita", 72779162201}; // <2>
printf("Aluno: %s Mat.: %d Nota1: %1.2f Nota2: %1.2f\n",
a.nome, a.matricula, a.nota1, a.nota2);
printf("Cliente: %s CPF: %1Ld\n", c.nome,c.cpf);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_atribuicao_simulacao.txt
Aluno: Virgulino da Silva Mat.: 15 Nota1: 9.00 Nota2: 10.00
Cliente: Maria Bonita CPF: 72779162201
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_cliente.c
typedef struct {
char nome[256];
long long cpf;
} Cliente;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_cliente_pseudo.txt
REGISTRO Cliente
cpf: NUMÉRICO
nome: TEXTUAL
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_conta.c
typedef struct {
long numero_da_conta;
long cpf_do_cliente;
double saldo;
} Conta;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_conta_pseudo.txt
REGISTRO Conta
numero_da_conta, cpf_do_cliente, saldo: NUMÉRICO
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_infopessoal.c
#include <stdio.h>
typedef struct {
long long cep;
int estado_civil; // 1:Solteiro 2:Casado 3:Viuvo 4:Divorciado
} InformacaoPessoal;
typedef struct {
int matricula;
char nome[100];
float nota1;
float nota2;
InformacaoPessoal info_pessoal;
} Aluno;
typedef struct {
char nome[256];
long long cpf;
InformacaoPessoal info_pessoal;
} Cliente;
int main() {
Aluno a = {15, "Virgulino da Silva", 9.0f, 10.0f, {58051400, 1}};
Cliente c = {"Maria Bonita", 72779162201, {58051400, 2}};
printf("Aluno: %s %1Ld %d.\n", a.nome, a.info_pessoal.cep,
a.matricula);
printf("Cliente: %s %1Ld %1Ld.\n", c.nome, c.info_pessoal.cep,
c.cpf);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_infopessoal_pseudo.txt
REGISTRO InformacaoPessoal
cep: NUMÉRICO
estado_civil: TEXTO
FIM_REGISTRO
REGISTRO Aluno
matricula: NUMÉRICO
nome: TEXTO
nota1, nota2: NUMÉRICO
info_pessoal: InformacaoPessoal
FIM_REGISTRO
REGISTRO Cliente
cpf: NUMÉRICO
nome: TEXTUAL
info_pessoal: InformacaoPessoal
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_pessoa.c
typedef struct{
char nome[100];
char sexo; // 'm': masculino, 'f': femino
float peso;
float altura;
long long cpf;
} Pessoa;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_pessoa_pseudo.txt
REGISTRO Pessoa
nome, sexo: TEXTO
peso, altura, cpf: NUMÉRICO
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_ponto.c
typedef struct{
int x;
int y;
} Ponto;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_ponto_exemplo.c
#include <stdio.h>
#include <string.h>
typedef struct{
int x;
int y;
} Ponto;
#define QUANTIDADE_DE_PONTOS 3 // <1>
int main(){
Ponto pontos[QUANTIDADE_DE_PONTOS]; // <2>
pontos[0].x = -4; pontos[0].y = 7;// <3>
pontos[1].x = 2; pontos[1].y = -9;// <3>
pontos[2].x = 5; pontos[2].y = 3;// <3>
for (int i = 0; i < QUANTIDADE_DE_PONTOS ; i++){
if(pontos[i].y > 0)
printf("\nPonto acima da reta: (%d,%d)",
pontos[i].x,
pontos[i].y);
}
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_ponto_exemplo_simulacao.txt
Ponto acima da reta: (-4,7)
Ponto acima da reta: (5,3)
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_ponto_matematica_pseudo.txt
REGISTRO Ponto
x, y: NUMÉRICO
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_produto.c
typedef struct {
long codigo;
char nome[100];
float preco;
} Produto;
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_produto_exemplo.c
#include <stdio.h>
typedef struct {
long codigo;
char nome[100];
float preco;
} Produto;
int main(){
Produto p;
scanf("%ld %s %f", &p.codigo, p.nome, &p.preco); //<1>
if (p.preco < 4)
printf("\nProduto em promocao: %s R$ %1.2f", p.nome, p.preco);
else
printf("\nProduto cadastrado.");
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_produto_exemplo_simulacao.txt
Produto em promocao: banana R$ 3.99
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_produto_supermercado_pseudo.txt
REGISTRO Produto
codigo: NUMÉRICO
nome: TEXTUAL
preco: NUMÉRICO
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_triangulo.c
#include <stdio.h>
typedef struct {
int x;
int y;
} Ponto ;
typedef struct {
Ponto p1;
Ponto p2;
Ponto p3;
} Triangulo ;
int main() {
Triangulo t;
t.p1.x= 1; t.p1.y=0;
t.p2.x=-1; t.p2.y=0;
t.p3.x= 0; t.p3.y=1;
printf("Triangulo: (%d, %d), (%d, %d), (%d, %d).\n",
t.p1.x, t.p1.y, t.p2.x, t.p2.y, t.p3.x, t.p3.y);
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/reg_triangulo_pseudo.txt
REGISTRO Ponto
x, y: NUMÉRICO
FIM_REGISTRO
REGISTRO Triangulo
p1, p2, p3: Ponto
FIM_REGISTRO
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/supermercado.c
#include <stdio.h>
typedef struct {
long codigo;
char nome[100];
float preco;
} Produto;
#define QUANTIDADE_DE_PRODUTOS 5
int main(){
Produto produtos[QUANTIDADE_DE_PRODUTOS];
printf("Campos: codigo-do-produto nome preco\n");
for(int i=0; (i < QUANTIDADE_DE_PRODUTOS); i++){
printf("\nInforme os dados do produto(%i): ",i+1);
scanf("%ld %s %f",&produtos[i].codigo,produtos[i].nome,
&produtos[i].preco);
}
for(int i=0; (i < QUANTIDADE_DE_PRODUTOS); i++){
printf("\n%ld\t%s R$ %1.2f", produtos[i].codigo,
produtos[i].nome,produtos[i].preco);
}
long codigo_digitado;
printf("\nInforme o codigo do produto: ");
scanf("%ld", &codigo_digitado);
for(int i=1; (i < QUANTIDADE_DE_PRODUTOS); i++){
if (produtos[i].codigo == codigo_digitado) {
printf("\nPreço: R$ %1.2f\n", produtos[i].preco);
}
}
getchar();
return 0;
}
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/supermercado.data
1 laranja 1.4
2 rosquinha 3
3 leite-moca 4.5
4 farinha-de-trigo 2.7
5 coxinha 1.5
4
introducao-a-programacao-livro-1.0.0/livro/capitulos/code/registros/supermercado_simulacao.txt
Campos: codigo-do-produto nome preco
Informe os dados do produto(1): 1 laranja 1.4
Informe os dados do produto(2): 2 rosquinha 3
Informe os dados do produto(3): 3 leite-moca 4.5
Informe os dados do produto(4): 4 farinha-de-trigo 2.7
Informe os dados do produto(5): 5 coxinha 1.5
1 laranja R$ 1.40
2 rosquinha R$ 3.00
3 leite-moca R$ 4.50
4 farinha-de-trigo R$ 2.70
5 coxinha R$ 1.50
Informe o codigo do produto: 4
Preço: R$ 2.70
introducao-a-programacao-livro-1.0.0/livro/capitulos/estruturas-de-controle.asc
[[estrutura_controle]]
== Estruturas de Controle
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
* Entender as estruturas sequenciais, de seleção e de repetição;
* Escrever estruturas de seleção utilizando os comandos if, if-else e switch
da linguagem C;
* Escrever estruturas de repetição utilizando os comandos for, while e
do-while da linguagem C.
____________________
////
TODO - REV.01: Sugestão de melhoria para análise pelo autor
Neste capítulo é importante refletir o que realmente pretendemos ensinar para o aluno.
Aqui vejo dois conteúdos distintos um é a discussão sobre as estruturas algorítmicas
e um outro seria o ensino da linguagem C para programação. Pelo que se lê
a intenção é ensinar algoritmos usando a linguagem C como padrão, contudo,
seria aconselhável nesse caso termos algo que introduza o que é uma linguagem de programação
e suas respectivas características falando um pouco mais da linguagem C. A partir daí
passaria a explanação do desenvolvimento de algoritmos utilizando como base essa
linguagem, inclusive na finalizaçaõ do capítulo de Algoritmos é dito isso.
A transição do conteúdo do capítulo anterior para esse não está suave para
o aluno.
////
=== Introdução
Vimos no <<algoritimos>> que os algoritmos são instruções que contém passos para
solucionar um determinado problema. Vimos também que estes algoritmos podem
ser representados através de linguagens de programação, como por exemplo, a
linguagem C, que estamos aprendendo aqui. Estes passos são executados na
sequência que eles aparecem. Entretanto, em muitas situações, é necessário
alterar o fluxo de execução destas instruções. Pode ser que seja necessário
executar um passo, ou um conjunto deles, apenas se uma determinada condição `for`
verdadeira, ou talvez, pode ser que seja preciso repetir um conjunto de passos várias vezes
até uma determinada condição. Neste sentido, este capítulo irá explicar as
diferentes estruturas de controle existentes nos algoritmos e seus respectivos
comandos na linguagem C.
=== Estrutura Sequencial
Um algoritmo que possui uma estrutura sequencial significa que suas
instruções são executadas na sequência em que elas aparecem, sem nenhuma
alteração no seu fluxo, a não ser, claro, que exista alguma instrução
explícita para a mudança deste fluxo. Vejamos o código em C na
<<estrutura_sequencial>> abaixo.
[[estrutura_sequencial]]
.Estrutura sequencial na linguagem C
image::images/estruturas-de-controle/sequencia.eps[scaledwidth="40%"]
Este algoritmo irá ler dois valores e guardá-los, respectivamente, nas
variáveis `x` e `y`. Após isso, a variável inteira 'soma' receberá a soma dos
valores de `x` e `y`. Em seguida, será mostrada na saída padrão, o resultado
desta soma. Perceba que os passos do algoritmo são executados de cima para
baixo.
Entretanto, em alguns momentos os problemas que queremos resolver requerem a
alteração no fluxo normal de execução do algoritmo. Na próxima seção,
iremos aprender como executar um conjunto de instruções de acordo com uma determinada
condição.
=== Estrutura de Decisão
Como foi dito, muitas vezes é necessário criar blocos de instruções no
algoritmo que são executados apenas se uma determinada condição `for`
verdadeira. Veja o algoritmo abaixo:
"Se hoje não chover, então João irá à praia".
No algoritmo acima, João irá à praia se, e somente se, não chover hoje.
Significa que esta instrução de João ir à praia só será executada se a
condição de não chover for verdadeira. Este tipo de estrutura é chamado de
*estrutura de decisão*, também conhecido como estrutura de seleção ou
condicional.
Podemos ter três tipos de estrutura de decisão: decisão simples, decisão
composta e decisão múltipla. Vamos ver adiante estes três tipos e quais são
os comandos na linguagem C, respectivamente, para cada um destes tipos.
////
TODO - REV.02: Sugestão de melhoria para análise pelo autor
Senti muito falta de gráficos e/ou diagramas nas explicações que seguem.
O conteúdo pode ser bem representado usando os fluxogramas que auxiliam
o entendimento das estruturas de
controle. Será que não seria interessante
ilustrar com esse tipo de gráfico?
////
==== Decisão simples
(((Se)))
Quando queremos que uma determinada instrução ou um conjunto de instruções
execute apenas se uma determinada condição for verdadeira. A estrutura da
decisão simples é a seguinte:
SE condição ENTÃO instrução
Uma condição deve ter como resultado apenas dois valores possíveis:
verdadeiro ou falso. A instrução só será executada se a condição tiver o
valor verdadeiro.
////
TODO - REV.03: Sugestão de melhoria para análise pelo autor
Seria possível na explicação que segue referenciar o número da linha
de código onde os comandos aparecem? Ficaria mais claro para o aluno.
Sugeriria que todo código tivesse número de linha, isso ajudaria
a referenciar durante a explicação.
////
Vamos analisar o exemplo a seguir. Este algoritmo lê um valor digitado pelo
usuário e armazena na variável `x`. Em seguida, o comando `SE` verifica se o
valor de `x` é menor que `20`. Caso seja, a instrução `ESCREVA` é executada,
mostrando na tela a frase "o valor de X é menor que 20".
(((LEIA)))(((ESCREVA)))
[[alg_menorq20]]
.Algoritmo que comparava valor lido com 20
============
LEIA x
SE x < 20 ENTÃO
ESCREVA "O valor de x é menor que 20."
============
Para cada linguagem de programação há uma sintaxe para criar estruturas de
controle. Na Linguagem C, a estrutura de decisão simples possui a seguinte
forma:
[source, c]
----
if (expressão)
instrução;
----
Na linguagem C, a condição é definida como uma expressão, que pode ser
lógica ou aritmética. Ao ser executada, a expressão é verificada. Se o
resultado desta for verdadeiro, a instrução que vem após a expressão é
executada.
Entretanto, muitas vezes queremos que mais de uma instrução seja executada
caso a condição seja verdadeira. Neste caso, devemos utilizar um bloco de
instruções dentro do comando *if*, como é mostrado abaixo.
(((if)))
.Estrutura de decisão simples com blocos na linguagem C
[source, c]
----
if (expressão) {
instrução 1;
instrução 2;
...
}
----
As duas formas, com e sem bloco, se diferenciam apenas pelo fato de que a
primeira possui apenas uma instrução a ser executada caso a condição seja
verdadeira. No segundo caso, um bloco de instruções será executado. Na
linguagem C, um bloco de instruções deve estar entre chaves.
[IMPORTANT]
Sempre que você precisar executar um bloco de instruções, utilize as chaves
para delimitar o início e o fim deste bloco.
////
FIXME - REV.04: Possível erro para análise pelo autor
No parágrafo seguinte se menciona que as expressões aritméticas foram vistas
no capítulo 2, mas o cap 2 é o capítulo corrente não?
////
Como foi dito, a expressão pode conter uma expressão lógica ou aritmética.
As expressões aritméticas foram vistas no capítulo 2. Vamos ver agora como
funcionam as expressões lógicas na linguagem C.
// FIXME adicionar link para o capítulo 2 no paragrafo acima.
===== Expressões lógicas
(((Expressões lógicas)))
////
TODO - REV.05: Sugestão de melhoria para análise pelo autor
Seria interessante deixar claro que são os operandos e o operador
em uma expressão lógica.
////
As expressões lógicas são usualmente utilizadas para fazer comparações
entre operandos. Para isso, estas expressões são compostas por operadores
lógicos e relacionais, e possuem apenas dois valores possíveis: verdadeiro ou
falso. Por exemplo, quando queremos saber se um valor é maior, menor, igual ou
diferente de um outro valor.
Na linguagem C, os seguintes operadores relacionais podem ser utilizados:
[width="50%",cols="^1,^1",options="header"]
|========================
| Operador | Descrição
| `>` | Maior que
| `<` | Menor que
| `>=` | Maior ou igual a
| `<=` | Menor ou igual a
| `==` | Igual a
| `!=` | Diferente de
|========================
Considere o código <<menorq20>> abaixo. Este reflete o mesmo algoritmo de <<alg_menorq20>>,
sendo que agora, implementado na linguagem C. Na linha 6, estamos pedindo para
o usuário entrar com o valor de `x`. Na linha 8, temos um comando `if`, onde há
uma expressão relacional `x < 20`. Portanto, essa expressão é verificada e caso
seja verdadeira, será mostrado na saída padrão "O valor de x é menor que a
20." (linha 9). Caso a expressão seja falsa, o algoritmo se encerra sem mostrar
nada na saída padrão, pois a instrução após o comando `if` não é executada.
////
TODO - REV.06: Sugestão de melhoria para análise pelo autor
Seria interessante representar esse algoritmo em c também usando
o fluxograma, isso daria ao aluno uma visão mais clara do condicional
e o que seria executado dependendo do resultado da expressfão lógica.
////
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/menorq20.c[code/estruturas-de-controle/menorq20.c]
[[menorq20]]
[source, c, numbered]
.menorq20.c
----
include::code/estruturas-de-controle/menorq20.c[]
----
Em outros casos, necessitamos utilizar operadores lógicos nas expressões para
avaliar mais de uma expressão relacional. Por exemplo, digamos que no problema
acima queremos verificar se o valor digitado para a variável `x` está dentro do
intervalo entre `10` *e* `20`. Neste caso, precisamos que a condição verifique as
duas expressões relacionais: `(x > 10)` *e* `(x < 20)`. Portanto, precisamos
conectar as duas expressões relacionais utilizando um operador lógico `E`. A
tabela abaixo apresenta os operadores lógicos possíveis:
////
TODO - REV.07: Sugestão de melhoria para análise pelo autor
O quadro apresentado a seguir parece confuso. Na verdade,
ele apresenta o operador na linguagem C e diz como descrição o equivalente
em linguagem algoritmica (LA). Sugeriria fazer o inverso e adicionar uma
melhor explicação do que é cada operador, por exemplo, na 1a
coluna seria o operador em LA, em seguida fornecer uma descrição mais
explicativa, e depois apresentar a sintaxe equivalente na linguagem C.
////
(((E))) (((Ou))) (((Não)))
[width="50%",cols="^1,^1",options="header"]
|========================
| Operador em C | Operador em linguagem algorítmica
| && | E
| \|\| | OU
| ! | NÃO
|========================
Portanto, o algoritmo para resolver o problema acima, na linguagem C, é o
seguinte:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/[code/estruturas-de-controle/entre10e20.c]
[source, c, numbered]
.entre10e20.c
----
include::code/estruturas-de-controle/entre10e20.c[]
----
Perceba que agora a condição do comando `if` possui duas expressões
relacionais conectadas por um operador lógico `E` (`&&`). Nesse caso, se ambas as
expressões forem verdadeiras, será mostrada na saída padrão "x esta' entre
10 e 20". Caso alguma das expressões seja falsa, nada será mostrado, pois o
resultado da expressão completa é falso. Vejamos a tabela abaixo, denominada de tabela verdade,
que mostra a relação lógica entre duas expressões e seus respectivos resultados.
////
TODO - REV.09: Sugestão de melhoria para análise pelo autor
Sugeriria dividir o quadro que segue em três, isso deixaria o texto mais
legível para o leitor.
Seria interessante falar que os operadores tem precedência e como
fazer para alterá-la de forma explícita.
////
[cols="^1,^1m,^1,^1",options="header"]
|========================
|Expressão 1|Operador|Expressão 2|Resultado
|Verdadeiro|E|Verdadeiro|Verdadeiro
|Verdadeiro|E|Falso|Falso
|Falso|E|Falso|Falso
|Verdadeiro|OU|Verdadeiro|Verdadeiro
|Verdadeiro|OU|Falso|Verdadeiro
|Falso|OU|Falso|Falso
|Verdadeiro|NÃO|-|Falso
|Falso|NÃO|-|Verdadeiro
|========================
////
TODO - REV.10: Sugestão de melhoria para análise pelo autor
Considerando os exercícios resolvidos, nesse ponto o leitor sabe o que é uma variável? Sabe o que é uma atribuição de valor?
Acredito não ter ficado claro até o momento esses pontos básicos da programação.
Não seria interessante ter falado um pouco sobre isso antes?
////
===== Exercício resolvido
*ER 3.1.* Considere quatro variáveis `a`, `b`, `c` e `d` com valores iniciais de 5, 7, 3
e 9. Dada as condições abaixo, indique se o resultado final da expressão
será verdadeiro ou falso.
a. `(a != 3 || b < 10 || c == 5)`
b. `(d > 8 && c == 3 || a >=10)`
c. `!(d == 12 && a != 10)`
d. `(c == 4 || d <= 6) && (a >= 5 && b != 9) || (!(a < 5))`
Resposta:
a. Neste caso temos três expressões lógicas. A primeira `(a != 3)` é
verdadeira. A segunda `(b < 10)` é verdadeira, e a terceira `(c == 5)` é falsa.
Como as expressões estão conectadas por um operador OU (`||`), então basta que
uma das expressões seja verdadeira para o resultado da expressão completa ser
verdadeira.
b. Temos neste caso três expressões. Para um melhor entendimento, vamos
utilizar uma tabela. As duas primeiras expressões `(d > 8 e c == 3)` são verdadeiras
e estão conectadas pelo operador lógico `&&`, logo `R1 && R2` é verdadeiro.
A terceira expressão `(a >= 10)`, por sua vez, é falsa. Então, resolvendo `R3 || R4`,
temos o resultado final como verdadeiro.
+
[format="csv",cols="^1,^1,^1",options="header"]
|===================================================
Rs, Expressão, Resultado
R1, `d > 8`, VERDADEIRO
R2, `c == 3`, VERDADEIRO
R3, `R1 && R2`, VERDADEIRO
R4, `a >= 10`, FALSO
R5, `R3 || R4`, VERDADEIRO
|===================================================
c. Utilizando novamente a tabela, temos que a primeira expressão `(d == 12)` é
falsa. A segunda expressão `(a != 10)` verdadeira. A relação entre `R1 && R2`
é falsa, pois apenas `R2` é verdadeira. A última expressão é uma negação
de `R3`, ou seja, se `R3` é falso, então `R4` é verdadeiro.
+
[format="csv",cols="^1,2,3",options="header"]
|===================================================
R, Expressão, Resultado
R1, `d == 12`, FALSO
R2, `a != 10`, VERDADEIRO
R3, `R1 && R2`, FALSO
R4, `!R3`, VERDADEIRO
|===================================================
d. Vamos utilizar novamente a tabela para nos auxiliar. Temos que prestar
bastante atenção nos parênteses das expressões que podem ser utilizadas
para explicitar a precedência da avaliação.
+
[format="csv",cols="^1,2,3",options="header"]
|===================================================
Rs, Expressão, Resultado
R1, `c == 4`, FALSO
R2, `d <= 6`, FALSO
R3, `R1 || R2`, FALSO
R4, `a >= 5`, VERDADEIRO
R5, `b != 9`, VERDADEIRO
R6, `R4 && R5`, VERDADEIRO
R7, `a < 5`, FALSO
R8, `!R7`, VERDADEIRO
R9, `R3 && R6`, FALSO
R10, `R9 || R8`, VERDADEIRO
|===================================================
===== Verificação da condição com expressões aritméticas na Linguagem C
(((if)))
Anteriormente, dissemos que a expressão dentro de um comando `if` pode ser
lógica ou aritmética. Vimos como funciona nos casos de expressões lógicas.
Nos casos de expressões aritméticas, na linguagem C, 'Falso' assume o valor
zero, e 'Verdadeiro' assume qualquer valor diferente de zero. Neste sentido,
quando utilizamos uma expressão aritmética dentro da condição de um comando
`if` para verificar se esta é verdadeira ou falsa, temos que ter o cuidado de
analisar o valor resultante. Vamos verificar o exemplo no código abaixo.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/if5.c[code/estruturas-de-controle/if5.c]
[source, c, numbered]
.if5.c
----
include::code/estruturas-de-controle/if5.c[]
----
Inicialmente a variável inteira `x` recebe o valor `5` (linha 4). Na linha 6 existe uma
estrutura de decisão simples, onde há a verificação da expressão que está entre parênteses.
Nesse caso, a expressão é apenas a própria variável `x`, logo o resultado da expressão é
o valor desta, que é `5`. Considerando o que foi dito, quando o resultado for
*diferente de zero*, ele é considerado *verdadeiro*. Logo, o resultado da
expressão também é verdadeiro, e então a instrução que vem após a condição é executada.
Já na linha 9, também há outra estrutura de decisão simples, na qual a
condição a ser avaliada é a expressão `x - 5`. O resultado dessa expressão é *zero*, fazendo com
seja avaliada como *falsa*. Consequentemente, a instrução que vem após a condição não é executada.
==== Decisão composta
(((if-else)))
////
FIXME - REV.11: Possível erro para análise pelo autor
Rever a frase: "...momentos, ao termos uma estrutura...".
////
Em alguns momentos, ao termos uma estrutura de decisão, queremos que uma outra
instrução ou um outro bloco de instruções seja executado caso a condição
de decisão seja falsa. Esta estrutura é chamada de decisão composta.
O pseudocódigo abaixo exemplifica uma estrutura de decisão composta.
////
TODO - REV.12: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar também um gráfico em fluxograma exemplificando
a decisão composta.
////
[[alg_decisao_composta]]
.Pseudocódigo com decisão composta
=====================
LEIA nota
SE nota >= 7 ENTÃO
ESCREVA "Aprovado"
SENÃO
ESCREVA "Reprovado"
=====================
Na linguagem C, utilizamos a palavra `else`, após a instrução ou bloco de
instruções do `if`, para definir que queremos executar um outro conjunto de
instruções, caso a expressão condicional seja falsa.
////
TODO - REV.13: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar um quadro em que apresentasse o comando equivalente em C.
////
[source, c]
----
if (expressão)
instrução 1;
else
instrução 2;
----
////
TODO - REV.14: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar lembretes em seções do tipo "Atenção!" ou "Importante!" reforçando
algumas particularidades da linguagem C. Por exemplo, toda instrução precisa ser terminada
com ";" e coisas assim.
////
Quando possuímos apenas uma instrução a ser executada, não precisamos
utilizar o delimitador de bloco de instruções (as chaves). Neste caso, a
condição é verificada. Caso seja positiva, a instrução 1 é executada,
caso contrário, a instrução 2 é executada. Caso queiramos que um conjunto
de instruções seja executado, devemos utilizar então as chaves, como mostra
o código abaixo.
////
TODO - REV.15: Sugestão de melhoria para análise pelo autor
Sugeriria representar o mesmo código também usando o fluxograma.
////
[source, c]
----
if (condição) {
instrução 1;
instrução 2;
...
} else {
instrução 3;
instrução 4;
...
}
----
Neste caso temos dois blocos de instruções: um para o bloco do `if`, que será
executado caso a condição seja verdadeira, e o bloco de instruções do `else`,
caso a condição seja falsa.
(((indentação)))
IMPORTANT: Tabule as instruções que estão dentro dos blocos, colocando-os mais a
direita (utilize a tecla TAB do teclado). Esta organização do código
chama-se *indentação*. Dessa maneira, seu código se torna mais legível, e
consequentemente mais fácil de encontrar possíveis erros.
Vamos traduzir o pseudocódigo apresentado no <<alg_decisao_composta>> para a linguagem C.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/nota7.c[code/estruturas-de-controle/nota7.c]
[source, c, numbered]
.nota7.c
----
include::code/estruturas-de-controle/nota7.c[]
----
===== Exercício resolvido
////
TODO - REV.16: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar pelo menos mais dois exercícios resolvidos.
Outro ponto para refletir em relação aos exercícios é a sintaxe
de programação da linguagem C, será que os alunos tem
amadurecimento para fazer o código como apresentado no
exemplo, entender & e etc? Particularmente, estou achando
que com o que eles conhecem até o momento não conseguiriam fazer.
////
*ER 3.2.* Escreva um programa para ler 2 números inteiros do teclado (A e B),
verificar e imprimir
qual deles é o maior, ou a mensagem "A=B", caso sejam
iguais.
Resposta:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/comparaab.c[code/estruturas-de-controle/comparaab.c]
[source, c, numbered]
.comparaab.c
----
include::code/estruturas-de-controle/comparaab.c[]
----
==== Comando de decisão múltipla
(((switch)))
////
TODO - REV.17: Sugestão de melhoria para análise pelo autor
Aqui já se fala direto no comando equivalente em C para o comando
escolha da linguagem algoritmica, isso deixa um pouco confuso o capítulo
em termos de conteúdo. É preciso estabelecer uma coerência para auxiliar o leitor
no entendimento do assunto que é considerado dificil por boa parte dos alunos.
////
Uma outra forma de escrever uma estrutura de condição é utilizando o comando
de decisão múltipla `switch`. Este tipo de estrutura condicional tem a mesma
função do `if-else-if`, com a diferença que o comando `switch` não aceita
expressões, apenas constantes. A vantagem de se utilizar este comando é a
legibilidade do código quando conhecemos os possíveis valores para uma
determinada variável. Vamos ver o formato de uso do comando `switch`.
[source, c]
----
switch (variável) {
case VALOR1:
instruçao1;
instrução2;
break;
case VALOR2:
instruçao3;
instrução4;
break;
default:
instruçao5;
instrução6;
break;
}
----
Uma variável do tipo `char` ou `int` é colocada entre parênteses após o comando
`switch`. Os valores desta variável que serão avaliados logo em seguida, através
das declarações `case`. Para cada possível valor da variável, existe uma
declaração `case` correspondente. Caso o valor seja aquele que corresponde na
declaração `case`, então as instruções abaixo dela serão executadas até
encontrar o comando `break`. A declaração `default` é opcional, e é executada
apenas se a variável não for igual a nenhuma das constantes definidas nas
declarações `case`.
IMPORTANT: O comando `break` tem a função de interromper um determinado fluxo de
execução. Este comando será melhor explicado na seção 3.4.5 que fala sobre
os comandos de desvio. O importante a saber por hora é que o comando `break`
deve ser utilizado ao final das instruções de cada declaração `case`. Caso
não seja colocado, as instruções das outras declarações `case` também
serão executadas.
//FIXME corrigir seção acima
////
TODO - REV.18: Sugestão de melhoria para análise pelo autor
Nada foi falado sobre tipos de variáveis possiveis. Sugeriria
mais uma vez que o código C apresentado fosse representado também
usando gráficos (fluxograma). Precisamos ser o mais didático possível,
principalmente, considerando essa parte introdutória da programação!
////
Para um melhor entendimento, vamos analisar o código <<semana_c>> abaixo. A ideia deste
programa é que o usuário digite o valor numérico correspondente ao dia da
semana e o programa mostre por extenso este dia. Uma variável chamada `semana`
do tipo `int` é declarada (linha 4) e guardará o valor que o usuário irá
digitar (linha 7). Em seguida, o comando `switch` foi utilizado (linha 9). Para
cada dia da semana existe uma declaração `case` correspondente. Isto significa
que se o usuário digitou o valor 1, a instrução da linha 11 será executada, mostrando
na saída a string "Domingo". Caso o valor digitado seja 2, a instrução da linha 14 é
executada, mostrando na saída a string "Segunda-feira". A mesma ideia acontece
para os outros 5 dias da semana. Caso o valor digitado pelo usuário não esteja
entre 1 e 7, as instruções da declaração `default` serão executadas, mostrando a
string "Numero fora do intervalo permitido.".
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/semana.c[code/estruturas-de-controle/semana.c]
[[semana_c]]
[source, c, numbered]
.semana.c
----
include::code/estruturas-de-controle/semana.c[]
----
=== Estrutura de Repetição
(((Enquanto)))
Considere um algoritmo em que você precise repetir um determinado conjunto de
passos, por exemplo, um algoritmo para retirar uma lâmpada do bocal. Um passo
deste algoritmo é realizar um movimento com a mão para girar a lâmpada. Este
passo deve ser repetido até que a lâmpada desencaixe do bocal. Neste sentido,
existem as estruturas de repetições nas linguagens de programação para
permitir que uma instrução ou um bloco de instruções seja repetido em um
algoritmo computacional. Estas estruturas também são conhecidas como
estruturas de iteração ou estruturas de laço.
Vamos tomar como base <<alg_decisao_composta>>, onde uma nota é solicitada ao
usuário. Caso a nota seja maior que 7, é mostrado ``Aprovado'' na tela, caso
contrário, é mostrado "Reprovado". O algoritmo utilizado, até então, só
permite que uma nota seja digitada, ou seja, quando o usuário digita a nota,
o programa apresenta o resultado e em seguida fecha. Mas digamos que agora
queremos que o programa continue executando, solicitando notas e apresentando o
resultado, até que o usuário digite o valor -1 para sair do programa.
Este algoritmo é mostrado no pseudocódigo abaixo. Perceba
que temos uma condição `nota <> -1` que será avaliada antes de executar as
instruções que estão dentro do bloco do `ENQUANTO`. Desse modo, enquanto a nota
digitada pelo usuário for diferente de `-1`, o programa irá solicitar uma nota
e apresentar o resultado.
////
TODO - REV.19: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar a forma gráfica para apoiar ainda mais a explicação.
////
[[alg_estrutura_repeticao]]
.Pseudocódigo com estrutura de repetição
========================
LEIA nota
ENQUANTO nota <> -1 FAÇA
SE nota >= 7 ENTÃO
ESCREVA "Aprovado"
SENÃO
ESCREVA "Reprovado"
LEIA nota
FIM-ENQUANTO
========================
Na linguagem C há três opções diferentes para se criar estruturas de
repetição. São os comandos `while`, `do-while` e `for`. Veremos cada um deles em
detalhes a seguir.
==== Comando while
(((while)))
Podemos usar o comando `while` quando desejamos que uma ou mais instruções
sejam repetidas até que uma determinada condição seja atendida. A estrutura
do `while` na linguagem C é bastante parecida com a do pseudocódigo apresentado
anteriormente. Veja abaixo:
[source, c]
----
while (expressão) {
instrução 1;
instrução 2;
...
}
----
As instruções serão executadas repetidamente enquanto a expressão for
verdadeira. Assim que essa condição tornar-se falsa, o laço para. Vejamos o
exemplo abaixo, que consiste no pseudocódigo do <<alg_estrutura_repeticao>>
escrito na linguagem C.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/whilenota.c[code/estruturas-de-controle/whilenota.c]
[source, c, numbered]
.whilenota.c
----
include::code/estruturas-de-controle/whilenota.c[]
----
Vamos tentar entender o código acima. Inicialmente, uma variável `nota`, do tipo `float`,
é declarada (linha 4). Logo depois ocorre a leitura da primeira nota.
Caso ela seja diferente de `-1`, o bloco de instruções dentro do `while` (linhas 8 a 14)
será executado. O comando `while` fará com que as instruções em seu corpo sejam executadas,
repetidamente, enquanto a condição `nota != -1` for verdadeira.
Vejamos outro exemplo. O programa <<mostrado10vezes>> abaixo escreve 10 vezes na tela "Isto sera'
mostrado 10 vezes.". A condição de parada do comando `while` é `cont <= 10`, o que
significa que enquanto o valor de `cont` for menor ou igual a `10`, o bloco de
instruções será executado. Para que o laço tenha fim, a variável `cont` precisa ser incrementada
até que alcance o valor `11`.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/mostrado10vezes.c[code/estruturas-de-controle/mostrado10vezes.c]
[[mostrado10vezes]]
[source, c, numbered]
.mostrado10vezes.c
----
include::code/estruturas-de-controle/mostrado10vezes.c[]
----
===== Exercício resolvido
*ER 3.3*: Escreva um programa que leia várias notas de alunos de uma turma. O
programa deve ler notas até que o usuário digite o valor `-1`. Após isso, o
programa deve mostrar a média dessas notas.
Resposta:
Neste programa não sabemos previamente a quantidade de notas que o usuário
precisa. Precisamos, nesse caso, utilizar um laço de repetição que fique
lendo notas até que o valor `-1` seja passado pelo usuário. Desse modo, podemos
utilizar o comando `while` (linha 7) com a condição de parada `nota != -1`, ou seja,
o usuário seguirá entrando com as notas enquanto os valores digitados forem
diferentes de `-1`. Dentro do laço, é solicitado que o usuário digite a nota (linha 8).
Na linha 9, o valor digitado pelo usuário é armazenado na variável `nota`.
Para calcular a média das notas, precisamos de
duas informações: a soma de todas as notas e a quantidade de notas lidas.
Utilizamos a variável `soma` para armazenar a soma das notas (linha 12) e a
variável `cont` para armazenar a quantidade de notas (linha 13). A média
das notas será mostrada depois do laço, ou seja, quando o usuário digitar `-1`
para a nota. Como o cálculo da média precisa da quantidade total das notas, seu
cálculo e o comando para sua exibição devem ser executados após o término do laço (linha 17).
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/notas_alunos_c.c[code/estruturas-de-controle/notas_alunos_c.c]
[[notas_alunos_c]]
[source, c, numbered]
.notas_alunos.c
----
include::code/estruturas-de-controle/notas_alunos.c[]
----
////
TODO - REV.22: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar, pelo menos, mais um exercício resolvido e
comentado.
////
==== Comando do-while
(((do-while)))
Assim como o `while`, o comando `do-while` também é uma estrutura de repetição.
Com semelhanças que vão além dos seus nomes, sobretudo em termos de funcionalidade,
a principal diferença entre eles é que, no caso do `do-while`, o bloco de instruções
dentro do laço é executado pelo menos uma vez, mesmo que a condição seja
falsa. Isso acontece porque a condição de parada só é avaliada depois que
as instruções são executadas. Segue abaixo a estrutura do comando `do-while`.
[source, c]
----
do {
instrução 1;
instrução 2;
...
} while (condição);
----
O comando começa com a palavra `do`, seguida do bloco de instruções a ser
repetido. Após a execução dessas instruções, a condição é avaliada.
Caso seja verdadeira, as instruções são executadas novamente, caso
contrário, o laço se encerra. Para entender melhor, observe o exemplo abaixo.
Este exemplo faz exatamente o mesmo que o exemplo <<mostrado10vezes>>, porém, utilizando
o comando `do-while`.
////
FIXME: faz exatamente o mesmo que o exemplo anterior... qual anterior?
////
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/do_while.c[code/estruturas-de-controle/do_while.c]
[[do_while_c]]
[source, c, numbered]
.do_while.c
----
include::code/estruturas-de-controle/do_while.c[]
----
==== Comando for
(((for)))
Este comando 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.
O formato do comando `for` é o seguinte:
[source, c]
----
for (expressão1; condição; expressão2) {
instrução1;
instrução2;
...
}
----
////
TODO - REV.23: Sugestão de melhoria para análise pelo autor
Sugeriria explicar melhor o que é um incremento e o que é um decremento.
Normalmente, o aluno tem uma certa dificuldade em entender a primeira vista
esses conceitos.
////
Em `expressão1`, uma variável, geralmente um contador, recebe um valor inicial.
Essa variável será incrementada ou decrementada de acordo com a expressão
definida em `expressão2`. O laço ficará executando as instruções
(`instrução1` e `instrução2`) até que a `condição` seja falsa.
Vejamos o exemplo abaixo para entender a sequência de operações que são realizadas
durante a execução do comando `for`. O programa <<for_cont>> abaixo tem o mesmo
resultado de <<do_while_c>> mostrado anteriormente. Inicialmete, uma variável inteira é
declarada (linha 4).
Diferentemente do programa <<do_while_c>>, não atribuímos nenhum valor inicial à variável,
visto que ela será inicializada na primeira expressão do comando `for`. Na linha 6
existe um comando `for`, cuja primeira expressão é a inicialização da variável `cont`.
Em seguida, a condição `cont <= 10` é avaliada. Como inicialmente `cont = 0`,
expressão é avaliada como verdadeira, e a instrução na linha 7 é executada. No fim da
execução das instruções (nesse caso, especificamente, há apenas uma instrução) contidas
dentro do `for`, a terceira expressão do `for`
é avaliada, que nesse caso é um incremento `(cont++)`. Então a variável `cont`
é incrementada e passa a valer `2`. Em seguida, a condição é verificada
novamente e, caso seja verdadeira, executa novamente a instrução dentro do
`for`. Essa sequência de passos é repetida até que a condição seja avaliada como falsa.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/for_cont.c[code/estruturas-de-controle/for_cont.c]
[[for_cont]]
[source, c, numbered]
.for_cont.c
----
include::code/estruturas-de-controle/for_cont.c[]
----
Para entender melhor o funcionamento do comando `for`, vamos ver outro exemplo
que utiliza decremento (`--`) ao invés do incremento. A ideia é muito parecida com a do
exemplo anterior, entretanto, ao invés de incremento, temos um
decremento da variável `cont`. No comando `for`, a variável `cont` é inicializada
com o valor `10`. Em seguida, a condição `cont > 0` é avaliada. Como o valor de
`cont` é `10`, a condição é verdadeira, e a instrução dentro do `for` é
executada (linha 7). Ao fim da execução dessa instrução, a variável `cont`
é decrementada e passa a valer `9`. Novamente a condição `cont >` 0 é
verificada e continua sendo verdadeira, o que faz com que a instrução da
linha 7 seja executada novamente. O laço continua até a variável `cont`
passar a valer `0`, caso no qual a condição `cont > 0` será falsa.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/for_cont_decremento.c[code/estruturas-de-controle/for_cont_decremento.c]
[source, c, numbered]
.for_cont_decremento.c
----
include::code/estruturas-de-controle/for_cont_decremento.c[]
----
Uma particularidade do comando `for` é que nenhum dos três elementos que o compõe é
obrigatório, ou seja, podemos omitir qualquer um desses elementos ou até mesmo
uma combinação deles. Contudo, essa é uma prática que deve ser evitada.
==== Laço infinito
(((Laço infinito)))
Quando a condição de parada de uma determinada estrutura de repetição nunca
é alcançada, as intruções do laço são executadas indefinidamente, e consequentemente
o programa nunca chega ao seu fim. Tal comportamento é algo que deve ser evitado
em programação (na grande maioria das situações) e, por ser um problema tão recorrente,
recebe um nome especial: laço infinito. O exemplo abaixo ilustra esse tipo de laço:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/laco_infinito.c[code/estruturas-de-controle/laco_infinito.c]
[source, c, numbered]
.laco_infinito.c
----
include::code/estruturas-de-controle/laco_infinito.c[]
----
Observe que na linha 6 temos um comando `for` cuja condição de parada não
foi definida. Consequentemente, o laço entra em loop e a
execução da instrução na linha 7 é executada indefinidamente.
WARNING: Ao criar uma estrutura de repetição, observe bem a condição de parada
para verificar se ela realmente será alcançada em algum momento. Sem esse cuidado
você corre
o risco de criar um laço infinito, e seu programa, possivelmente, não
terá o resultado esperado.
==== Exercício Resolvido
*E.R 3.4.* Analise o programa abaixo. O objetivo do programa é ler 50 valores e
mostrar, ao final da leitura, o menor deles. Entretanto, ele possui um problema.
Identifique e corrija o erro.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/menor_deles.c[code/estruturas-de-controle/menor_deles.c]
[source, c, numbered]
.menor_deles.c
----
include::code/estruturas-de-controle/menor_deles.c[]
----
Resposta:
Vamos analisar o código considerando seu objetivo: ler 50 valores e apresentar
o menor deles. Vamos iniciar nossa análise na estrutura de repetição `while`,
na linha 10. A condição é `quantidade < 50`. Logo percebemos que a variável
`quantidade`, que tem o valor inicial de `1` na sua declaração, não é alterada
em nenhum momento dentro do laço, o que sugere algum problema. Para o laço
ter um fim, é necessário que a variável `quantidade` atinja o valor `50` em
algum momento, e não é isso que está acontecendo. Portanto, temos um laço
infinito. Para corrigir o problema, devemos incluir uma linha dentro do laço
a fim de incrementar o valor da variável `quantidade`.
O código abaixo apresenta a solução do problema com a inclusão da linha 12,
na qual a variável `quantidade` é incrementada. Dessa forma, o laço deixou de ser
infinito, uma vez que ele atingirá o valor `50` em algum momento, tornando
falsa a condição `quantidade < 50` do laço.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/menor_deles_resposta.c[code/estruturas-de-controle/menor_deles_resposta.c]
[source, c, numbered]
.menor_deles_resposta.c
----
include::code/estruturas-de-controle/menor_deles_resposta.c[]
----
==== Comandos de desvio
Vimos no último exemplo o que é um laço infinito. Em alguns momentos,
precisamos utilizar um comando para realizar um desvio dentro do laço de
repetição. Esses comandos são: `break` e `continue`. Ambos podem ser utilizados
em qualquer estrutura da repetição.
===== Comando break
(((break)))
O comando `break` interrompe a execução do laço, fazendo com que as
instruções dentro do laço após esse comando não sejam executadas. Vamos
ver o exemplo <<break_interrompe>>. Apesar de não haver condição de parada
no `for`, ele irá parar quando o comando `break` for executado, ou seja, quando
o valor da variável `cont` for igual a `10`. Caso houvesse mais alguma outra
instrução após a linha 8, dentro do bloco de instruções do `for`, ela não seria
executada.
////
TODO - REV.28: Sugestão de melhoria para análise pelo autor
Sugeriria exemplificar também de forma gráfica ou com animação.
////
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/break_interrompe.c[code/estruturas-de-controle/break_interrompe.c]
[[break_interrompe]]
[source, c, numbered]
.break_interrompe.c
----
include::code/estruturas-de-controle/break_interrompe.c[]
----
===== Comando continue
(((continue)))
O comando `continue` faz com que o fluxo de execução "salte" para a avaliação da
condição de parada do laço, no caso do `while` e `do-while`, e para a expressão
de incremento e decremento, no caso do comando `for`. Isso significa que as
instruções após esse comando não são executadas.
Agora vamos analisar o programa <<continue_desvio>> abaixo. Esse programa mostra na tela os
números ímpares no intervalo de 1 a 20. A variável `cont` é inicializada com
o valor `1` no `for`. Como a condição é verdadeira, as instruções dentro do
bloco são executadas. Agora atenção para o comando `if` (linha 7). Na
condição do `if`, há uma expressão `cont % 2 == 0`, o que significa que se o
resto da divisão inteira entre a variável `cont` e `2` for `0`, o comando
`continue` é executado. Entretanto, o resto da divisão será `1`. Nesse caso, a
a instrução da linha 8 é executada, mostrando o valor `1` na saída.
Em seguida, a expressão de incremento do `for` é
avaliada, e `cont` passa a valer `2`. Como `cont` ainda é menor ou igual a `20`, as
instruções do bloco são executadas novamente. Mais uma vez, na linha 6, a
condição do `if` é avaliada. Todavia, o resto da divisão de `cont` e `2` agora é
igual a `0`, e então a instrução `continue` é executada. Com sua execução, o fluxo de
volta para o `for`, e a expressão de incremento é avaliada. Note que a instrução da linha 7,
nesse caso, não será mais executada. O valor de `cont` então é incrementado para `3`,
repetindo o que foi explicado quando `cont` era igual a `1`.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/continue_desvio.c[code/estruturas-de-controle/continue_desvio.c]
[[continue_desvio]]
[source, c, numbered]
.continue_desvio.c
----
include::code/estruturas-de-controle/continue_desvio.c[]
----
=== Recapitulando
Este capítulo apresentou as estruturas de controle da linguagem C e como
elas podem ser utilizadas.
A estrutura sequencial significa que o fluxo de execução das instruções
segue uma linha sequencial, que no caso da linguagem C, é de cima para baixo
e da esquerda para a direita.Entretanto, podemos mudar o fluxo de execução
desas instruções utilizando as estruturas de decisão e de repetição.
No caso da estrutura de decisão, aprendemos a utilizar os comandos `if`,
`else` e `switch` para executar uma instrução ou um bloco de instruções caso
uma determinada condição seja verdadeira.
Aprendemos também a construir laços de repetição com os comandos `while`,
`do-while` e `for`. Vimos que todos esses comandos proporcionam a repetição de
instruções até que uma determinada condição seja falsa. Diante disso, é
importante ter bastante atenção na condição de parada dessas estruturas de
repetição, para não criar um laço infinito.
No próximo capítulo estudaremos os arranjos: uma estrutura de dados que
tem o objetivo de representar um conjunto de valores do mesmo tipo. Vamos também aprender a
manipular as cadeias de caracteres, também conhecidas como strings.
////
TODO - REV.31: Sugestão de melhoria para análise pelo autor
De uma maneira geral, acho que o que foi explicado até o momento
não é o suficiente para os alunos sozinhos resolverem os exercícios propostos.
Precisamos ser mais ilustrativos, mais explicativos e didáticos. Apesar de parecer
redundante algumas explicações, o aluno EAD precisa de um material que lhe
dê um maior apoio a aprendizagem.
Não há curiosidades, links para outras informações que poderíamos estar
adicionando aqui para despertar maior interesse do aluno pelo assunto?
////
=== Exercícios Propostos
1. Escreva um programa que verifique se um número digitado pelo usuário é
menor, igual ou maior que zero.
2. Dado o algoritmo abaixo, explique o que acontece se o valor lido para a
variável `x` for: `3`, `1` e `0`. Explique o porquê.
+
[source, c]
----
#include <stdio.h>
int main() {
int x;
scanf(&x);
if (x) printf("verdadeiro");
return 0;
}
----
3. Escreva um programa que informe se um dado ano é ou não bissexto. Obs.: um
ano é bissexto se ele for divisível por 400 ou se ele for divisível por 4 e
não por 100.
4. Escreva um programa que mostre todos os números pares no intervalo de 1 a
40 de forma decrescente, utilizando o comando `while`. Depois faça o mesmo, mas
desta vez, utilizando o comando `for`.
5. Um determinado banco abriu uma linha de crédito para os funcionários
públicos. Porém, o valor máximo da prestação não poderá ultrapassar 30%
do salário deste funcionário. Faça um programa para ajudar este banco. O
programa deve permitir o usuário entrar com o salário do funcionário e o
valor da prestação e informar se o empréstimo pode ou não ser concedido.
6. Escreva um programa que leia o mês do ano em valor numérico e exiba este
mês por extenso (utilize o comando `switch`).
7. Faça três
programas que mostrem de 1 a 10 na tela, utilizando, em cada um,
uma estrutura de laço de repetição diferente.
8. Escreva um programa que mostre na tela os números múltiplos de 3 no
intervalo de 2 a 100.
9. Escreva um programa para ler dois números inteiros M e N e, a seguir,
imprimir os números pares existentes no intervalo [M, N].
10. A organização de um evento esportivo deseja um programa que faça a
leitura do nome e a pontuação de cada um dos 10 participantes e exiba o nome
do vencedor. Elabore este programa.
11. O supermercado Excelente Preço está precisando ser informatizado. Neste
sentido, o dono quer um programa que leia os preços dos produtos até que seja
informado o valor zero. No final o programa deve informar o total da compra e
perguntar a forma de pagamento. As opções da forma de pagamento são: 1) A
vista; 2) No cartão de crédito. Se a opção escolhida for a vista, então o
programa informa o valor da compra com um desconto de 5%. Caso a compra seja no
cartão de crédito, o programa informa o valor da compra dividido em 4 vezes.
// Sempre termine os arquivos com uma linha em branco.
introducao-a-programacao-livro-1.0.0/livro/capitulos/funcoes.asc
== Funções
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
* Criar e usar funções e procedimentos em C
* Identificar quando escrever uma função ou um procedimento
* Entender as regras de escopo para variáveis em C
* Entender os diferentes tipos de passagem de parâmetro e quando utilizá-los
* Trabalhar com funções recursivas
____________________
Quando elaboramos um algoritmo para resolver um problema, e quando
escrevemos um programa para implementar este algoritmo, muitas vezes
identificamos uma parte do algoritmo que deve ser realizada várias
vezes para chegar ao resultado. Ou, ao trabalhar com vários algoritmos
diferentes, identificamos algumas partes em comum entre eles. Nestes
casos, é uma boa ideia isolar essas partes que se repetem, de maneira
que elas possam ser realizadas sem repetir o mesmo código várias vezes.
Esse objetivo pode ser atingido com o uso de *funções* e *procedimentos*.
Neste capítulo, vamos estudar as funções e procedimentos como uma forma
de reaproveitar o código de tarefas que se repetem em um programa. Funções
são importantes para a modularização de um programa, ou seja, para dividir
o programa em partes menores, cada parte com uma função específica; sem
funções ou procedimentos, um programa maior e mais complicado ficaria
organizado em uma única parte que faz tudo e é mais difícil de entender.
Neste capítulo, vamos entender quando utilizar as funções ou procedimentos
para melhor dividir o código dos nossos programas.
[IMPORTANT]
====
Neste capítulo, vamos *descartar a utilização de pseudocódigo*. Agora que você já
possui um conhecimento básico sobre a linguagem C e, provavelmente, escreveu alguns
programas nela, consideramos que não haverá mais necessidade de
apresentar sintaxes para pseudocódigos. Portanto, deste capítulo em diante, o conteúdo
apresentadao utilizará somente a sintaxe da linguagem C.
====
=== O que são funções?
((Funções)) e ((procedimentos)) podem ser compreendidos como trechos reutilizáveis
de código. Uma função ou procedimento pode, então, ser utilizado várias
vezes por um mesmo programa. Isso simplifica a criação de programas maiores,
dividindo-os em unidades menores que trabalham em conjunto. Funções e procedimentos
são bastante semelhantes e, posteriormente, vamos entender as diferenças entre eles.
[NOTE]
.Revisão
====
Lembre-se que durante o curso você já utilizou funções diversas vezes,
o que nós sabemos sobre funções até agora?
* Uma função implementa um comportamento que pode ser reutilizado;
* Para executar uma função, utilizamos o nome da função e passamos alguns
parâmetros entre parênteses e separados por vírgula. Exemplo: `printf("R$ %1.2f", preco)`;
* A função `printf` é utilizada para imprimir texto na saída;
* As funções `scanf` e `getchar` para ler dados da entrada;
* As funções `strlen`, `strcpy`, `strcat` e `strchr` são utilizadas para
manipular strings;
* As ((funções)) são agrupadas em ((módulos)). Exemplo: `stdio`;
====
Quando realizamos um processo com um determinado objetivo, é comum
identificarmos partes que se repetem. Quando se trata de processos
no mundo físico, muitas vezes criamos máquinas que ajudam a realizar
as partes que se repetem.
Por exemplo, pense no processo de limpar
as roupas que usamos para podermos usá-las novamente. Este processo
envolve lavar as roupas com água e sabão, secar as roupas lavadas e,
finalmente, passar as roupas para evitar que fiquem amassadas. Para
reduzir a quantidade de trabalho necessária para realizar esse processo,
foi inventada uma máquina de lavar, que realiza a lavagem das roupas.
Uma pessoa que tem uma máquina de lavar pode usá-la repetidamente e,
sempre que precisa de roupas limpas, precisa apenas se encarregar de
enxugar e passar as roupas; a lavagem fica por conta da máquina.
Dizemos inclusive que a *função* da máquina é lavar as roupas.
As funções e os procedimentos funcionam desta forma, capturando
partes menores de um algoritmo que podem ser utilizadas por
outros algoritmos, economizando o trabalho de sempre refazer
uma determinada tarefa.
==== Um exemplo
Vejamos um exemplo. Queremos criar um programa que calcule a média
final de um aluno em uma disciplina com três provas; o programa
deve pedir ao usuário que entre com as notas das três provas,
calcular a média aritmética das três notas e apresentar o resultado.
O programador que resolveu o problema decidiu imprimir um separador
na tela entre cada entrada de dados e o resultado final, desta forma,
produzindo o seguinte código:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_sem_proc.c[code/funcoes/media_sem_proc.c]
[source, c]
.Cálculo da média de um aluno em uma disciplina
----
include::code/funcoes/media_sem_proc.c[]
----
<1> Código que imprime um separador na tela.
.Resultado da execução do programa
....
include::code/funcoes/media_sem_proc.out[]
....
É fácil de notar que o código usado para imprimir um separador se repete três
vezes no programa. Podemos melhorar esse programa utilizando um procedimento
que terá a tarefa de imprimir o separador. Isso teria o seguinte resultado:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_proc.c[code/funcoes/media_proc.c]
[source, c]
.Cálculo da média usando procedimento
----
include::code/funcoes/media_proc.c[]
----
<1> Definição do procedimento `imprime_separador`.
<2> Código no *corpo* do procedimento.
<3> *Chamada* do procedimento `imprime_separador`.
A linha `void imprime_separador()` inicia a definição do procedimento
chamado `imprime_separador`; o *corpo* do procedimento é o conjunto
de comandos entre chaves que vem logo após o nome do procedimento.
Os parênteses e a palavra `void` no início serão explicados depois.
Dizemos que o uso de um procedimento é uma *chamada* ao mesmo. No
exemplo anterior, o código do programa chama `imprime_separador`
três vezes.
É importante notar que além de economizar a repetição de linhas
de código, o programa usando o procedimento `imprime_separador` também
é mais fácil de entender porque transmite melhor as partes que
compõem o programa. A melhor modularização do código do programa é
um motivo extremamente importante para usar funções e procedimentos,
principalmente à medida que os programas se tornam maiores e
mais complexos.
=== Parâmetros
O exemplo usando `imprime_separador` é o caso mais simples,
mas menos interessante do uso de procedimentos e funções,
quando o código a ser reutilizado é sempre o mesmo. Na maioria
das situações de interesse, queremos utilizar uma função ou
procedimento em situações com algumas
diferenças. Para tornar
um procedimento (ou função) mais flexível, é preciso que
informações sejam passadas para o procedimento. Isso é
feito com o uso de *parâmetros*.
Já vimos muitas vezes o uso de procedimentos com parâmetros.
Por exemplo, `printf` é um procedimento da biblioteca padrão
da linguagem C que imprime a _string_ passada como parâmetro.
Assim,
printf("ola, mundo!");
é uma chamada ao procedimento `printf` com parâmetro `"ola, mundo!"`.
Como exemplo do uso de parâmetros em um procedimento, voltemos ao
exemplo do cálculo das médias. Vamos utilizar o separador para
fazer uma visualização simples das notas e da média, imprimindo
uma barra de tamanho proporcional a cada valor.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_param.c[code/funcoes/media_param.c]
[source, c]
.Uso de um procedimento com parâmetro
----
include::code/funcoes/media_param.c[]
----
<1> Definição do procedimento `imprime_separador`, com o parâmetro `nota`, do tipo `float`.
<2> Chamada do procedimento `imprime_separador`, passando o argumento `nota1`.
.Resultado da execução do programa code/funcoes/media_param.c
....
include::code/funcoes/media_param.out[]
....
Como se pode ver no resultado da execução do programa, o novo procedimento
`imprime_separador` imprime uma barra de tamanho proporcional à nota passada
como argumento (são 5 caracteres `=` para cada ponto da nota). Note a
diferença de nomenclatura: o procedimento `imprime_separador` é definido
com um *((parâmetro))* de nome `nota`, mas é chamado com um *((argumento))*,
o valor que é comunicado ao procedimento. No exemplo acima, `imprime_separador`
é chamado com os argumentos `nota`, `nota2`, `nota3` e `media`. É necessário
especificar o tipo de cada parâmetro que deve ser passado para o procedimento;
no caso, o parâmetro `nota` do procedimento `imprime_separador` é do tipo
`float`.
Na chamada de um procedimento com um parâmetro, o controle do programa
executa o código do procedimento, atribuindo o valor passado como argumento
à variável `nota` dentro do procedimento `imprime_separador`. Ou seja, o
seguinte código
[source, c]
----
int main() {
float nota1;
printf("Entre a nota da primeira prova: ");
scanf("%f", ¬a1);
imprime_separador(nota1);
// resto do programa ...
}
----
funciona como se fosse o seguinte (utilizando o procedimento):
[source, c]
----
int main() {
float nota1;
printf("Entre a nota da primeira prova: ");
scanf("%f", ¬a1);
float nota = nota1; // <1>
int i; // <2>
printf("\n");
for (i = 0; i < (int) lround(nota * 5.0); i++) {
printf("=");
}
printf(" %3.2f / 10.0\n", nota);
printf("\n");
// resto do programa ...
}
----
<1> Atribuição do valor do argumento `nota1` para o parâmetro `nota`.
<2> Resto do corpo do procedimento `imprime_separador`.
Este exemplo é apenas uma ilustração; na prática, o chamado do procedimento
não funciona exatamente desta forma. Em particular, a variável `nota`, que
designa o parâmetro do procedimento, só existe enquanto o procedimento
executa. Isso será detalhado mais tarde.
O uso de parâmetros nos procedimentos os tornam muito mais flexíveis para
uso em diferentes situações. Mas assim como é útil que o código que chama
o procedimento comunique informações para o procedimento chamado, muitas
vezes também é útil que o procedimento comunique algo de volta para o
código que o chamou; neste caso, passamos dos procedimentos para as funções.
=== Retorno de Valores com Funções
Até agora só temos usado procedimentos como ferramenta de modularização do
código, mas muitas vezes é útil chamar um procedimento que retorna alguma
informação de volta para o código que o chamou. Esta é a diferença entre
procedimentos e funções: as funções retornam algum valor. Desta forma, as
funções em linguagem C são similares às funções da matemática: uma função
como
[latexmath]
+++++
\[
f: \mathbb{Z} \rightarrow \mathbb{Z},\quad f(x) = x^2 + 2
\]
+++++
tem um parâmetro x (um inteiro), e retorna um determinado valor que depende do
parâmetro passado; por exemplo,
[latexmath]
+++++
\[
f(5) = 5^2 + 2 = 27
\]
+++++
É fácil escrever a mesma função em linguagem C:
[source, c]
----
int f(int x) { // <1>
return x * x + 2; // <2>
}
----
<1> Definição da função `f`. A definição começa com `int f(...)`, significando
que o tipo de retorno da função é `int`.
<2> A palavra-chave `return` determina o valor de retorno da função `f`, que será
o resultado da expressão `x * x + 2`.
A função `f` do exemplo faz o mesmo que a versão matemática: dado o valor do parâmetro
`x`, retorna um valor que é igual a `x` ao quadrado, somado a dois. Note que é preciso
especificar o tipo do valor que é retornado pela função e, por isso, toda função começa
com o tipo de retorno antes do nome. Especificar os tipos dos parâmetros e o tipo do
valor de retorno também é similar às funções na matemática, para as quais devemos
especificar os conjuntos domínio e contra-domínio.
==== Funções, Procedimentos e o Tipo `void`
Neste ponto pode surgir uma pergunta: se é preciso especificar o tipo do valor
retornado por uma função antes do nome da função (por exemplo `int f(...)`),
por que nos procedimentos usa-se a palavra-chave `void`?
A verdade é que, embora algumas linguagens de programação façam uma distinção
entre procedimentos e funções, na linguagem C existem apenas funções. Como a
diferença entre procedimentos e funções é apenas o fato de retornar ou não
um valor, os procedimentos em C são considerados funções que retornam um valor
_vazio_. É isso que significa o `void` no início da definição de um procedimento
como `imprime_separador`, que vimos anteriormente; a rigor, `imprime_separador`
é uma função, mas retorna um valor _vazio_, ou seja, nenhum valor. O tipo `void`
na linguagem C é um tipo especial que denota a ausência de valores.
Como procedimentos em C são funções, também é possível usar `return` em
procedimentos, mas apenas para terminar sua execução e retornar
imediatamente. Isso às vezes é útil para terminar um procedimento em pontos
diferentes do seu final. Também pode-se utilizar `return` ao final do
procedimento, mas este uso é supérfluo e não é recomendado.
O seguinte exemplo demonstra o uso do `return` em procedimentos. Continuando
no tema relacionado ao cálculo de médias, queremos detectar se uma das notas
entradas pelo usuário é uma nota inválida antes de fazer o cálculo da média.
Neste caso, o programa deve apenas imprimir se algum valor negativo foi
entrado pelo usuário. O procedimento `possui_negativo` será responsável por
imprimir uma mensagem caso um dos valores seja negativo.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/retorno_proc.c[code/funcoes/retorno_proc.c]
[source, c]
.Uso de `return` em um procedimento
----
include::code/funcoes/retorno_proc.c[]
----
<1> Uso do `return` para sair prematuramente do procedimento.
<2> Este comando de impressão será executado se nenhuma das
condições testadas em `possui_negativo` for verdade, ou seja,
se nenhum dos valores dos parâmetros for negativo.
.Resultado de duas execuções do programa code/funcoes/retorno_proc.c
....
include::code/funcoes/retorno_proc.out[]
....
O procedimento `possui_negativo` deve verificar se um dos três números passados
como argumentos, mas basta achar um número entre eles para que o resultado possa
ser impresso imediatamente e o procedimento pode retornar; por isso, usamos
`return` assim que o primeiro valor negativo é encontrado.
Esse exemplo ainda tem um problema: como pode ser visto nos exemplos de execução,
mesmo que o usuário entre um valor negativo, a média aritmética das três notas
ainda é impressa na tela (o usuário apenas é avisado que um dos valores foi negativo).
Isso é uma indicação
que seria melhor que `possui_negativo` fosse uma função, e que o
programa principal verificasse o valor retornado e tomasse uma decisão. Se fizermos
essas alterações ficamos com o seguinte programa:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/retorno_func.c[code/funcoes/retorno_func.c]
[source, c]
.Reescrevendo o exemplo anterior para usar uma função
----
include::code/funcoes/retorno_func.c[]
----
<1> A função `possui_negativo` agora retorna um inteiro de valor 1 caso um dos
valores dos parâmetros seja negativo, e 0 caso contrário (todos são positivos).
<2> Teste para identificar se um ou mais dos parâmetros informados são negativos.
<3> A função retorna `1` se um dos números passados para a função for negativo.
<4> Caso nenhum dos números seja negativo, o controle passa para o comando `return`
ao final da função e o valor `0` é retornado para indicar que nenhum número negativo
foi encontrado.
<5> O programa principal verifica o valor de retorno da função `possui_negativo` e
imprime informações adequadas na tela para cada caso.
.Resultado de duas execuções do programa code/funcoes/retorno_func.c
....
include::code/funcoes/retorno_func.out[]
....
Como pode-se ver nos dois exemplos de execução do programa, a saída agora é mais
adequada. Caso uma das notas informadas seja negativa, o programa não imprime um
valor de média, apenas avisando o usuário do erro na entrada de dados. O código
da função `possui_negativo` também foi simplificado pelo uso do operador lógico
OU.
Como funções e procedimentos são tratados de maneira uniforme na linguagem C
(e em muitas outras linguagens atuais), a partir de agora vamos usar o termo
*função* tanto para funções como para procedimentos. Isso não deve gerar nenhuma
confusão. Em caso de dúvida, basta olhar para a definição da função no código-fonte
do programa; se a função for declarada com o tipo de retorno `void`, então é um
procedimento.
=== Um Exemplo Matemático: Equação de Segundo Grau
Nesta seção, veremos mais um exemplo do uso de funções em um programa
para calcular as raízes de uma equação de segundo grau. Neste exemplo,
as funções não serão utilizadas várias vezes, mas o programa principal
será mais claro e mais fácil de entender graças à melhor modularização
conseguida com o uso das funções.
Lembremos que um _polinômio de segundo grau_ é uma soma de três termos
em potências de uma variável, por exemplo
[latexmath]
++++
\[
P(x) = ax^2 + bx + c
\]
++++
onde latexmath:[$a$], latexmath:[$b$] e latexmath:[$c$] são coeficientes
constantes. Uma _equação de segundo grau_ é formada ao igualar um polinômio
de segundo grau a zero, ou seja
[latexmath]
++++
\[
ax^2 + bx + c = 0
\]
++++
Sabemos que uma equação de segundo grau pode ter até duas *raízes* reais; cada
raiz é um valor da variável latexmath:[$x$] que satisfaz a equação. Essas raízes
podem ser encontradas pela chamada _fórmula de Bhaskara_. A fórmula consiste
em calcular um valor auxiliar chamado de latexmath:[$\Delta$] (_delta_), e
usar o valor calculado para identificar quantas raízes reais distintas podem
ser encontradas para a equação:
* se latexmath:[$\Delta < 0$], a equação não tem raízes reais;
* se latexmath:[$\Delta = 0$], a equação possui uma raiz real;
* se latexmath:[$\Delta > 0$], a equação possui duas raízes reais distintas.
A fórmula para calcular latexmath:[$\Delta$] é
[latexmath]
++++
\[
\Delta = b^2 - 4 a c
\]
++++
No caso latexmath:[$\Delta >= 0$], as raízes da equação são dadas por
[latexmath]
++++
\[
x = \frac{-b \pm \sqrt{\Delta}}{2a}
\]
++++
uma das raízes sendo obtida pelo uso do sinal positivo em
latexmath:[$-b \pm \sqrt{\Delta}$], enquanto que a outra raiz é calculada pelo
uso do sinal negativo. Se [$\Delta = 0$], ambos os valores serão iguais.
Como exemplo do uso da fórmula de Bhaskara, considere a equação:
[latexmath]
++++
\[
x^2 - 5x + 6 = 0
\]
++++
Nesta equação os coeficientes são latexmath:[$a=1, b=-5$] e latexmath:[$c=6$].
Calculamos o latexmath:[$\Delta$] usando os valores dos coeficientes:
[latexmath]
++++
\[
\Delta = b^2 - 4ac = (-5)^2 - 4 \times 1 \times 6 = 25 - 24 = 1
\]
++++
E assim, podemos calcular as raízes:
[latexmath]
++++
\[
x = \frac{-b \pm \sqrt{\Delta}}{2a} = \frac{5 \pm \sqrt{1}}{2} = \frac{5 \pm 1}{2}
\]
++++
Ou seja, as raízes são latexmath:[$6/2 = 3$] e latexmath:[$4/2 = 2$].
Agora vamos escrever um programa que resolve equações do segundo grau, usando o
processo mostrado acima.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/equacao.c[code/funcoes/equacao.c]
[source, c]
.Cálculo de raízes de uma equação de segundo grau
----
include::code/funcoes/equacao.c[]
----
<1> Função que calcula o valor do latexmath:[$\Delta$].
<2> Função que calcula a raiz positiva da equação.
<3> Função que calcula a raiz negativa da equação.
.Resultado de três execuções do programa code/funcoes/equacao.c
....
include::code/funcoes/equacao.out[]
....
Podemos ver neste exemplo funções para calcular o valor do latexmath:[$\Delta$] e
das duas raízes da equação. O programa obtém o valor do latexmath:[$\Delta$] e
verifica se a equação tem nenhuma, uma ou duas raízes, e imprime o resultado
de acordo com isso. Embora cada função seja usada apenas uma vez, o programa
principal é mais claro e mais fácil de entender porque cada função faz uma
parte do processo necessário, ao invés de ter todo o código junto na função `main`.
Funções são importantes não só para reutilizar código e diminuir esforço de
programação, mas também para melhorar a modularização do programa e torná-lo mais
fácil de ser lido. Em situações práticas, muitas vezes é necessário ler um código
que já foi produzido antes e entendê-lo, seja para consertar defeitos encontrados
ou para extender suas funcionalidades. Tornar um programa mais legível auxilia e
reduz o custo relacionado à _manutenção_ do mesmo.
Entretanto, este último exemplo pode parecer estranho do ponto de vista da
modularização, já que duas de suas funções são quase idênticas. As funções que
calculam o valor das raízes, `raiz_positiva` e `raiz_negativa`, mudam apenas em
uma operação. Podemos pensar em como reescrever o programa para usar apenas uma
função ao invés de duas funções quase idênticas. A repetição desnecessária
de código pode ser um problema para a manutenção de um programa.
A chave para criar uma só função que calcula os dois valores é criar um novo
parâmetro que indica qual das duas raízes deve ser calculada. Vamos usar um
parâmetro chamado `sinal` que indica, pelo seu valor, se será usada uma soma ou
subtração no cálculo da raiz. Se `sinal` for 1, será usada uma soma, e se for
`-1` será usada uma subtração. O código resultante é mais compacto e evita
repetições:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/equacao2.c[code/funcoes/equacao2.c]
[source, c]
.Cálculo de raízes de uma equação de segundo grau
----
include::code/funcoes/equacao2.c[]
----
É comum quando escrevemos programas nos concentrarmos, inicialmente, em fazê-lo
funcionar, ou seja, resolver o problema desejado. Entretanto, é importante
depois reler o código escrito e revisá-lo para torná-lo mais claro, mais
legível e mais fácil de manter. Esse processo de revisar o código de um programa
sem mudar sua funcionalidade é muito importante na programação de computadores,
e normalmente recebe o nome de *refatoração*.
=== Escopo de Variáveis
Quando trabalhamos com programas compostos por várias funções, 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.
Um programador iniciante poderia escrever o seguinte programa para calcular a
média aritmética de três notas:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_erro.c[code/funcoes/media_erro.c]
[source, c]
.Cálculo da média usando código incorreto
----
include::code/funcoes/media_erro.c[]
----
<1> Esta linha contém erros e o programa não será compilado.
O raciocínio do programador é "se as variáveis `nota1`, `nota2` e `nota3` existem na
função `main` e a função `calc_media` é chamada dentro de `main`, as variáveis
`nota1`, `nota2` e `nota3` não deveriam ser visíveis dentro de `calc_media` ?"
Acontece que isso não é válido na linguagem C, e qualquer compilador da linguagem
vai acusar erros de compilação neste programa, avisando que as variáveis `nota1`,
`nota2` e `nota3` não foram declaradas.
Para entender como funciona a visibilidade das variáveis em um programa na linguagem
C, precisamos falar sobre as regras de *escopo* desta linguagem. O ((escopo)) de
uma variável é a parte do programa na qual ela é visível e pode ser acessada.
A linguagem C usa um conjunto de regras de escopo que recebe o nome de _escopo estático_
ou _escopo léxico_. Essas regras são bastante simples de entender e aplicar,
como veremos a seguir.
Em programas na linguagem C existem dois tipos de escopo (regiões de visibilidade):
* escopo global;
* escopos locais.
Existe apenas um escopo global e, como indicado pelo seu nome, ele contém elementos
que são visíveis em todo o programa. Já os escopos locais são vários e particulares:
basicamente, cada função define um escopo local que corresponde com o _corpo_ da
função.
Desta forma, variáveis declaradas no escopo global (ou seja, "fora" de qualquer
função) são visíveis em todo programa, enquanto variáveis declaradas dentro de
uma função são visíveis apenas dentro da mesma função. No exemplo anterior, as
variáveis `nota1`, `nota2` e `nota3` são visíveis apenas dentro da função `main`,
e por isso não podem ser acessadas dentro da função `calc_media`.
Isso pode ser resolvido mudando as variáveis `nota1`, `nota2` e `nota3` para
o escopo global, ou seja, tornando-as _variáveis globais_, como no seguinte
programa:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_globais.c[code/funcoes/media_globais.c]
[source, c]
.Cálculo de raízes de uma equação de segundo grau
----
include::code/funcoes/media_globais.c[]
----
<1> Declaração das variáveis `nota1`, `nota2` e `nota3` como variáveis globais.
Note que elas estão declaradas "fora" de qualquer função.
<2> Código dentro de `calc_media` que usa as variáveis globais. Neste programa,
as variáveis estão visíveis e não ocorrerá um erro durante a compilação.
<3> Código dentro de `main` que usa as variáveis globais. Variáveis globais
são visíveis em todo o programa, incluindo na função principal.
Este programa agora compila corretamente e funciona para o cálculo da média.
Mas é importante observar que esse tipo de prática _não é recomendada_.
Entre as boas práticas da programação está a sugestão de usar variáveis
globais apenas quando absolutamente necessário. Como variáveis globais
podem ser acessadas e ter seu valor alterado por qualquer parte do
programa, fica difícil saber que partes podem influenciar ou serem
influenciadas pelas variáveis globais, o que torna todo o programa
mais difícil de entender. Para o exemplo das notas, é melhor e mais
de acordo com boas práticas de programação comunicar as notas para
a função `calc_media` usando parâmtros, como segue:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/media_param2.c[code/funcoes/media_param2.c]
[source, c]
.Cálculo de raízes de uma equação de segundo grau
----
include::code/funcoes/media_param2.c[]
----
Este código funciona corretamente e evita o uso desnecessário de
variáveis globais.
==== Escopo dos Parâmetros
Uma pergunta que pode surgir (especialmente após o exemplo anterior)
é "qual o escopo dos parâmetros das funções?" A resposta é simples:
para questões de visibilidade, o escopo dos parâmetros das funções é
o escopo local da função da qual eles pertencem. Ou seja, os parâmetros
de uma função funcionam exatamente como variáveis locais declaradas
dentro da função.
==== Sombreamento e Sobreposição de Escopos
O que acontece se duas variáveis tiverem o mesmo nome em um só
programa? A resposta depende de onde as variáveis em questão
são declaradas.
Não podem existir duas variáveis de mesmo nome em um mesmo escopo;
um programa que tente declarar duas variáveis de mesmo nome no mesmo
escopo ocasionará um erro quando for compilado. Assim, não podem
existir duas variáveis globais de nome `x` ou duas variáveis de nome
`y` em uma mesma função.
Em escopos diferentes a regra muda: variáveis de mesmo nome podem
existir em um programa se forem declarados em escopos distintos.
Isso é bastante útil: imagine um programa com 20 ou 30 mil linhas
de código (o que hoje em dia é considerado um programa de _pequeno_
a _médio_ porte); um programa deste tamanho precisa usar um grande
número de variáveis, se cada uma delas precisasse ter um nome
diferente de todas as outras, seria muito difícil dar nomes a vários
milhares de variáveis. Imagine que um programa deste tamanho pode
ter mais de mil laços `for`, cada um com uma variável de controle, e
cada uma dessas variáveis teria que ter um nome diferente. Por isso,
as regras de escopo também são úteis para estabelecer espaços locais
onde os nomes não entram em conflitos com os nomes de outros
escopos locais.
Quando temos duas variáveis de mesmo nome em diferentes escopos locais,
ou seja, duas funções diferentes, o resultado é
simples, já que essas variáveis de mesmo nome nunca seriam visíveis
no mesmo local do programa. Mas e se tivermos duas variáveis de mesmo
nome, sendo uma variável local e uma global? Neste caso, dentro da
função que declara a variável com mesmo nome da global, existirão
duas variáveis que poderiam ser visíveis com o mesmo nome. O que
acontece nesses casos é chamado de *sombreamento*: a variável do
escopo local _esconde_ a variável do escopo global. Vamos ilustrar
essa regra com um exemplo:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/sombra.c[code/funcoes/sombra.c]
[source, c]
.Exemplo de sombreamento de variáveis globais
----
include::code/funcoes/sombra.c[]
----
<1> Declaração da variável global `x`.
<2> Declaração da variável local `x` na função `f`.
<3> Declaração da variável local `y` na função `f`.
<4> Declaração da variável local `y` na função `g`.
Vemos no exemplo que existe uma variável global chamada `x` e uma
variável local `x` na função `f`. A função `f` também tem
uma variável local chamada `y`, e há uma variável local de mesmo
nome na função `g`. As variáveis chamadas `y` em `f` e `g` não
interferem, pois são escopos totalmente diferentes.
Já as variáveis chamadas `x` interferem, já que uma está no escopo
global e outra está no escopo local da função `f`. A questão é: o que
é impresso pelo programa? Isso depende dos valores de `x` dentro da
função `f` e na função `g` (que usa `x` para calcular o valor de `y`, que
é retornado). A execução do programa imprime o seguinte:
.Resultado da execução do programa code/funcoes/sombra.c
....
include::code/funcoes/sombra.out[]
....
A primeira linha é o que é impresso na função `f`. Como existe uma
variável local `x` declarada em `f`, dentro da função `f` a variável
`x` tem o valor 60, como declarado; o valor de `y` calculado em
`f` é, então, latexmath:[$60 \times 60 = 3600$]. Já na função `g`
não existe uma variável `x` local, então o valor de `x` dentro de `g`
é o valor da variável global `x`, que é igual a 5; desta forma, `y` em
`g` tem valor latexmath:[$5 \times 5 = 25$]. Isso explica a saída do
programa como visto acima.
[NOTE]
====
Uma consequência da regra de sombreamento é que dentro de funções que
tenham variáveis locais que escondem variáveis globais de mesmo nome,
é impossível acessar ou utilizar as variáveis globais escondidas.
No
exemplo anterior, dentro da função `f` é impossível ter acesso à
variável global `x`.
====
=== Passagem de Parâmetros
Com o que vimos até agora sobre parâmetros de funções, eles funcionam
de maneira simples: o código que chama uma função especifica expressões
para cada um dos argumentos da função. Os valores de cada expressão são
calculados e transmitidos como o valor dos parâmetros declarados na
função.
Entretanto, isso não é suficiente para todos os casos em que podemos
querer usar parâmetros. Por exemplo, digamos que temos uma situação
em que é necessário trocar o valor de duas variáveis, e que isso é
necessário várias vezes ao longo do programa. Para evitar repetição,
a melhor solução é escrever uma função que realiza a troca do valor
de duas variáveis. O exemplo a seguir mostra o que acontece quando
tentamos fazer isso apenas com o que vimos até agora:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/troca.c[code/funcoes/troca.c]
[source, c]
.Tentativa de trocar o valor de duas variáveis usando uma função
----
include::code/funcoes/troca.c[]
----
.Resultado da execução do programa code/funcoes/troca.c
....
include::code/funcoes/troca.out[]
....
Como se vê no resultado da execução do programa, embora as variáveis
`a` e `b` realmente troquem de valor dentro da função `troca_variaveis`,
isso não afeta o valor das variáveis `x` e `y` em `main`. Isso acontece
porque, normalmente, os parâmetros em C são passados *por valor*, ou seja,
apenas o valor de `x` e `y` são copiados para `a` e `b`. Precisamos que
as variáveis na função `troca_variaveis`, de alguma maneira, afetem as variáveis
que foram usadas como parâmetro, e para isso é necessário usar o modo de
passagem de parâmetros chamado de passagem *por referência*. A seguir, vamos
ver em maiores detalhes como funcionam esses dois modos de passagem de
parâmetros.
==== Passagem Por Valor
A passagem de parâmetros por valor é a situação padrão na linguagem C.
Este modo de passagem de parâmetros comunica apenas valores entre
o código chamador e a função chamada.
A passagem por valor funciona da seguinte forma: para uma função `f`
com N parâmetros, uma chamada de `f` deve conter N expressões como
argumentos (se o número de argumentos não corresponder ao número
de parâmetros declarados, o compilador acusará um erro no programa).
Então o seguinte processo de chamada de função acontece:
1. O valor de cada uma das N expressões usadas como argumento é calculado
e guardado;
2. N variáveis locais são criadas para a função chamada, uma para cada
parâmetro da função, e usando o nome declarado na função;
3. Os valores calculados no passo 1 são atribuídos às variáveis criadas
no passo 2.
4. O corpo da função `f` é executado.
Como as variáveis criadas para os parâmetros são locais, elas deixam
de existir quando a função chamada termina, e isso não tem nenhum efeito
nas expressões que foram usadas para atribuir valor aos parâmetros ao início
da função. Isso significa que o programa para troca de valor de variáveis
mostrado acima funciona de maneira similar ao seguinte programa (no qual
colocamos o código da função `troca_variaveis` diretamente na função `main`):
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/troca_main.c[code/funcoes/troca_main.c]
[source, c]
.Tentativa de trocar o valor de duas variáveis usando uma função
----
include::code/funcoes/troca_main.c[]
----
Neste caso, fica claro que as variáveis `x` e `y` são usadas apenas para obter
o valor inicial das variáveis `a` e `b`, e portanto a mudança de valor das
duas últimas não deve afetar `x` e `y`.
A passagem de parâmetros por valor é simples e funciona bem na maioria dos
casos. Mas em algumas situações pode ser desejável ter uma forma de afetar
variáveis externas à uma determinada função e, para isso, usa-se a passagem de
parâmetros por referência.
==== Passagem Por Referência
A passagem de parâmetros por referência funciona passando para a função chamada
_referências_ para variáveis ao invés de valores de expressões. Isso permite à
função chamada afetar as variáveis usadas como argumento para a função.
Vamos ilustrar como isso funciona demonstrando como criar uma função que troca
o valor de duas variáveis e realmente funciona:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/troca_ref.c[code/funcoes/troca_ref.c]
[source, c]
.Função para trocar o valor de duas variáveis usando passagem por referência
----
include::code/funcoes/troca_ref.c[]
----
<1> Definição do procedimento. Os parâmetros `a` e `b` são declarados
usando `int *` ao invés de simplesmente `int`. Isso indica passagem
por referência.
<2> Ao usar as variáveis `a` e `b` que foram passadas por referência,
é necessário usar `*a` e `*b` para acessar ou modificar seu valor.
<3> Na chamada da função `troca_variaveis` é preciso passar referências
para as variáveis `x` e `y`, isso é conseguido usando `&x` e `&y`.
.Resultado da execução do programa code/funcoes/troca_ref.c
....
include::code/funcoes/troca_ref.out[]
....
A primeira coisa a notar é que são necessárias algumas mudanças sintáticas
para usar parâmetros por referência. A declaração dos parâmetros no início
da função agora define os parâmetros `a` e `b` como tendo tipo `int *`.
Quando esses parâmetros são usados na função `troca_variaveis`, eles precisam
ser precedidos de asterisco (`*a` ao invés de `a`). E para chamar a função, é
preciso passar referências para as variáveis `x` e `y` ao invés de passar seu
valor, por isso usamos `&x` e `&y` na chamada.
De maneira simplificada, a passagem por referência funciona da seguinte forma:
ao escrever um argumento como `&x` para a função `troca_variaveis`, não estamos
passando o _valor_ de `x` para a função, mas sim uma _referência_ para a própria
variável `x`. Isso significa que, dentro de `troca_variáveis`, o parâmetro `a`
se torna um nome diferente para a mesma variável `x`; desta forma, alterações
feitas em `a` (através da sintaxe `*a`) são alterações também no valor da
variável original `x`. É por isso que o programa acima funciona, como pode
ser visto no resultado da execução: a função `troca_variaveis` recebe referências
para as variáveis `x` e `y`, e por isso pode alterar o valor destas variáveis
diretamente, trocando o valor das duas.
A passagem de parâmetros por referência é usada quando uma função precisa alterar
o valor de uma variável que existe fora da própria função e que não necessariamente
é uma variável global. O mesmo efeito de ter uma função alterando uma variável
externa poderia ser atingido usando variáveis globais ao invés de passagem por
referência, mas com grande perda de flexibilidade. Além disso, o uso desnecessário
de variáveis globais não é recomendado, como comentado antes.
Por exemplo, poderíamos trocar os valores das variáveis `x` e `y` no exemplo
anterior se ambas fossem alteradas para serem variáveis globais:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/troca_glob.c[code/funcoes/troca_glob.c]
[source, c]
.Função para trocar o valor de duas variáveis usando passagem por referência
----
include::code/funcoes/troca_glob.c[]
----
<1> `x` e `y` agora são variáveis globais.
<2> `troca_variaveis` não utiliza parâmetros, já que acessa diretamente as
variáveis globais.
.Resultado da execução do programa code/funcoes/troca_glob.c
....
include::code/funcoes/troca_glob.out[]
....
O programa funciona, mas note que agora `troca_variaveis` só altera o valor de
duas variáveis específicas, `x` e `y`, enquanto que a versão usando passagem por
referência era geral e podia trocar o valor de quaisquer duas variáveis inteiras.
Se um programa precisa trocar os valores de vários pares de variáveis, em vários
locais diferentes, seria preciso criar uma função de troca para cada par,
e
fazer todas as variáveis serem globais. Isso acarretaria em muita repetição, e
muito uso desnecessário de variáveis globais que tornariam a manutenção do código
muito mais difícil. Neste caso, é muito mais recomendado usar a passagem por
referência para chegar a um código mais geral, mais fácil de manter e com menos
repetição.
[NOTE]
====
A passagem por referência vem sido usada neste livro há vários capítulos,
pois a função `scanf` usa esse modo de passagem de parâmetros. Em toda chamada
a `scanf` passamos referências para as variáveis que vão receber os valores, e
não os valores dessas variáveis. Isso faz sentido já que `scanf` precisa alterar
o valor das variáveis passadas como parâmetro, e ao mesmo tempo `scanf` não
utiliza o valor original dessas variáveis para nada.
====
[NOTE]
====
Com a passagem por referência e por valor na linguagem C acontece algo semelhante
ao que vimos com os conceitos de procedimento e função: a rigor na linguagem C
só existe a passagem por valor, mas a passagem por referência pode ser
obtida pelo uso de *ponteiros*, um conceito avançado da linguagem C. Como se
trata de um conceito avançado, não vamos detalhar mais sobre eles aqui neste
livro.
====
=== Protótipos e Declaração de Funções
Em todos os exemplos que vimos neste capítulo até agora, nós sempre definimos
uma função antes de chamá-la em outra função. Nesses exemplos, a função
`main` sempre aparece no final do arquivo, já que as chamadas para as outras
funções apareciam apenas em `main`.
Mas em muitos casos pode ser necessário chamar uma função que é definida
posteriormente no arquivo, sem precisar mudar as funções de lugar. O mesmo
ocorre em programas maiores, quando usamos vários arquivos de código-fonte
para um programa (mas este caso não será detalhado aqui).
Se tentarmos usar uma função antes de sua definição vamos observar um erro
de compilação. No exemplo abaixo, a intenção é definir uma função que
imprime a situação de um aluno em uma disciplina cuja média é 7.0. Para
isso é necessário passar para a função as três notas do aluno na disciplina.
Mas se definirmos a função após `main`, como abaixo:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/situacao.c[code/funcoes/situacao.c]
[source, c]
.Chamando uma função antes de sua definição
----
include::code/funcoes/situacao.c[]
----
<1> A função `main` chama `situacao` antes de sua definição.
<2> A definição de `situacao` começa após a função `main`.
Ao tentar compilar esse programa, um erro similar ao seguinte será acusado:
.Resultado da execução do programa code/funcoes/situacao.c
....
include::code/funcoes/situacao.out[]
....
Isso acontece porque o compilador não pode identificar se a função foi
realmente definida em algum lugar e, principalmente, se o tipo dos parâmetros
e o tipo do retorno da função estão sendo usados corretamente.
Esse problema pode ser resolvido através do uso de *protótipos* para
declarar uma função antes que seja definida. Um protótipo de função
é uma declaração que especifica as seguintes informações:
1. O tipo de retorno da função;
2. O nome da função;
3. O número de parâmetros;
4. Os tipos de cada um dos parâmetros.
Com essas informações, o compilador pode identificar se a função está sendo
usada de maneira correta com relação aos tipos, e evita os erros anteriores.
Para consertar o erro no exemplo anterior basta adicionar uma linha com o
protótipo da função:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/situacao_prot.c[code/funcoes/situacao_prot.c]
[source, c]
.Usando protótipos de funções
----
include::code/funcoes/situacao_prot.c[]
----
<1> Protótipo da função `situacao`. Note que o protótipo inclui o tipo de retorno
(`void`), o nome da função, que a função aceita três parâmetros, e que todos eles
possuem tipo `float`. Não é preciso especificar o nome dos parâmetros em um protótipo,
mas é possível especificar os nomes, se desejado; incluir os nomes dos parâmetros pode
ser útil como uma forma simples de documentação.
<2> A chamada a `situacao` em `main` agora pode acontecer sem problema.
.Resultado da execução do programa code/funcoes/situacao_prot.c
....
include::code/funcoes/situacao_prot.out[]
....
Nesse exemplo, seria possível consertar o problema simplesmente movendo a
função `situacao` completa para antes da função `main`, mas em programas
maiores o uso de protótipos toma grande importância.
=== Funções Recursivas
Uma função pode ser chamada a partir de qualquer outra função no mesmo programa.
Dessa forma, podemos pensar em uma função chamando a si mesma. Isso é possível
e útil em muitos casos. Quando uma função `f` chama a si mesma em algum ponto,
dizemos que `f` é uma *((função recursiva))*. _Recursividade_ se refere a um
objeto auto-referente, ou seja, que referencia a si próprio; isso inclui as
funções recursivas mas também outros tipos de objetos.
Um exemplo é o cálculo do fatorial de um número. O _fatorial_ de um número
inteiro positivo N (cuja notação é latexmath:[$N!$]) é definido como o
produto de todos os números de 1 até N:
[latexmath]
++++
\[
N! = N \times (N-1) \times \dots \times 2 \times 1
\]
++++
por exemplo, o fatorial de 3 é latexmath:[$3! = 3 \times 2 \times 1 = 6$],
e o fatorial de 4 é latexmath:[$4! = 4 \times 3 \times 2 \times 1 = 24$],
e assim por diante.
Para números N maiores que 1, sempre podemos fazer a seguinte relação:
[latexmath]
++++
\[
N! = N \times (N-1)!
\]
++++
ou seja, latexmath:[$4! = 4 \times 3! = 4 \times 6 = 24$]. Isso indica
a estrutura recursiva do cálculo do fatorial: o fatorial de um número N
é igual a N vezes o fatorial do número anterior a N, 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 responder diretamente: latexmath:[$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.
O programa com a função que calcula o fatorial é o seguinte:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/fatorial.c[code/funcoes/fatorial.c]
[source, c]
.Cálculo do fatorial usando uma função recursiva
----
include::code/funcoes/fatorial.c[]
----
<1> Protótipo da função `fatorial`.
<2> Caso-base na função `fatorial`: para `n` igual a 0 ou 1, retorna 1.
<3> Caso recursivo na função `fatorial`: se `n` for maior que 1,
retorne `n` multiplicado pelo fatorial de `n - 1`.
.Resultado de três execuções do programa code/funcoes/fatorial.c
....
include::code/funcoes/fatorial.out[]
....
O código calcula o fatorial de um número inteiro recursivamente. Se o número
for 0 ou 1, a resposta é direta: 1. Se o número for maior do que 0 ou 1, a resposta
é obtida pela multiplicação do número pelo fatorial do número anterior a ele. Note
que esse processo sempre termina se o parâmetro `n` da função fatorial for um
inteiro positivo, pois em cada chamada recursiva o argumento da função fatorial
será um número menor que o anterior, até eventualmente chegar a 1, cuja resposta é
direta. Como a função tem um parâmetro declarado como inteiro, o compilador C
não permitiria passar como parâmetro um número não-inteiro, então, isso não pode
criar problema para essa função recursiva. Entretanto, um erro que pode acontecer
neste caso é se o usuário especificar um número negativo:
.Cálculo do fatorial usando número negativo, usando o programa code/funcoes/fatorial.c
....
include::code/funcoes/fatorial_neg.out[]
....
A falha de segmentação
(_segmentation fault_) mostrada quando se passa um argumento
negativo para a função `fatorial` é o resultado da recursão infinita que ocorre na
função: como em cada etapa o número é diminuído de um, um número negativo nunca vai
chegar a 1 (ou 0) e portanto nunca vai parar no caso-base. Mesmo a noção matemática
do fatorial não está definida para números negativos. Uma forma de consertar o
programa acima seria imprimir um erro quando `fatorial` fosse chamado com um
argumento negativo, ou retornar um valor qualquer, sem chamar a função recursivamente.
Uma forma de fazer isso seria mudando o teste do caso base:
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/fatorial2.c[code/funcoes/fatorial2.c]
[source, c]
.Cálculo do fatorial usando uma função recursiva
----
include::code/funcoes/fatorial2.c[]
----
<1> Caso-base da função `fatorial`. O teste agora é se o parâmetro é menor ou
igual a 1, o que inclui os números negativos. Neste caso, a função retorna 1
sem fazer chamadas recursivas.
.Resultado de duas execuções do programa code/funcoes/fatorial2.c
....
include::code/funcoes/fatorial2.out[]
....
Neste caso, a função sempre retorna 1 para números negativos, o que é um
resultado estranho, mas como o fatorial não está definido para números
negativos, isso não chega a ser um grande problema.
A recursividade é um conceito de programação considerado avançado, mas
se torna bastante importante em programas mais complexos. Alguns paradigmas
de programação como a _programação funcional_ e a _programação lógica_ são
bastante baseadas no uso de funções recursivas, e muitos processos se tornam
muito mais fáceis de implementar usando funções recursivas. Por isso, apesar
de não entrarmos em mais detalhes sobre funções recursivas aqui, é importante
ter em mente que esse é um dos conceitos que se tornam importantes para a
evolução de um programador.
=== Recapitulando
Neste capítulo, vimos uma importante ferramenta para a criação de programas
na linguagem C: funções. As funções (e os procedimentos, que são tratados na linguagem C
como funções) possibilitam a reutilização de trechos de código em várias partes
de um programa, e permitem isolar determinadas componentes do programa em unidades
mais autocontidas, melhorando a modularização do programa. É raro que um programa
não-trivial na linguagem C não faça uso de funções.
Vimos exemplos do uso de procedimentos
(funções que não retornam valores) com e sem parâmetros. Vimos também como usar parâmetros para
funções e como retornar valores a partir de uma função.
As regras que regem a visibilidade
de variáveis locais, globais e parâmetros de funções foram apresentadas, assim como os
diferentes modos de passagem de parâmetros e como utilizá-los: passagem por valor e por
referência.
Também vimos como declarar funções usando protótipos, para poder utilizar essas funções
antes de sua definição (ou em arquivos diferentes de onde elas estão definidas). Um
exemplo simples de função recursiva foi mostrado, como forma de introdução ao conceito.
Compreender o conteúdo deste capítulo é extremamente importante para
aprender a programar bem na linguagem C.
=== Exercícios Propostos
1. Escreva uma função que calcula a média final de um aluno que fez prova
final em uma disciplina. A função deve receber a média parcial do aluno
(média das notas nas provas regulares da disciplina) e a nota obtida na
prova final. O cálculo para a média final é
[latexmath]
++++
MF = \frac{6 \times MP + 4 \times PF}{10}
++++
onde latexmath:[$MF$] é a média final, latexmath:[$MP$] é a média parcial
e latexmath:[$PF$] é a nota da prova final. Escreva um programa que utiliza
esta função, pedindo os dados necessários ao usuário e imprimindo o valor
da média final na tela.
2. Às vezes é útil limpar a "tela" antes de imprimir informações adicionais no
console. Uma forma de limpar a tela é imprimir mais de 24 linhas em branco
(dependendo do tamanho do console). Em geral, imprimir 30 linhas em branco
deve ser suficiente. Escreva uma função (procedimento) que limpe a tela
imprimindo 30 linhas em branco.
3. Escreva uma função que recebe dois parâmetros `a` e `b` e troca o valor
de `a` com o valor de `b` se o valor de `a` for maior do que o de `b`;
o objetivo é ter, ao final, o menor dos dois valores em `a` e o maior em
`b`. Por exemplo, se `a = 5` e `b = 3`, então os valores das duas
variáveis devem ser trocados, mas se `a = 2` e `b = 7`, então a ordem já
está correta e não é necessário trocar os valores. Utilize passagem de
parâmetros por referência para poder afetar o valor das variáveis.
Escreva um programa para testar a função.
4. O que é impresso pelo seguinte programa?
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/funcoes/exercicio4.c[code/funcoes/exercicio4.c]
[source, c]
.Cálculo do fatorial usando uma função recursiva
----
include::code/funcoes/exercicio4.c[]
----
introducao-a-programacao-livro-1.0.0/livro/capitulos/introducao-a-programacao.asc
== Introdução à Programação
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
* Entender o processo de tradução de programas escritos em linguagem de alto
nível para código de máquina
* Compreender o conceito de variável e sua relação com a memória do
computador
* Criar instruções que envolvam operações aritméticas
* Utilizar instruções de entrada e saída da linguagem C
* Escrever um programa simples em C
____________________
////
TODO - REV.00: Sugestão de melhoria para análise pelo autor - Comentário Geral
Seria interessante rever a estruturação desse capítulo como um todo. A impressão
é que o aluno sentirá uma certa dificuldade para entender devido a forma como
está estruturado. Como sugestão o livro poderia quebrar em capítulos como:
cap 01 - introdução ao algoritmo - descrevendo os conceitos básicos de algoritmo
cap 02 - linguagem algoritmica - descrevendo maiores detalhes sobre pseudocódigo (explicando conceitos básicos como estruturação, variáveis e etc)
cap 03 - introdução a programação - seria o capítulo corrente, tirando o conteúdo da linguagem C
cap 04 - A linguagem c - introduzindo a linguagem c
cap 05 - ...
Também recomendaria adicionar um maior número de exemplos, ilustrações e exercícios comentados
no decorrer de todo capítulo.
////
////
TODO - REV.01: Dúvida sobre a ordem dos capítulos
Ao iniciar a leitura fiquei em dúvida da ordem do capítulo, este
seria o 2 ou o 3?
////
Agora que você já conhece as características de um algoritmo computacional e
é capaz de criar algoritmos utilizando suas diferentes formas de
representação, está na hora de você escrever seu primeiro programa. Porém,
antes que isso ocorra, você deve aprender alguns conceitos básicos, como o
de variável, e conhecer o processo de tradução de um programa
escrito em linguagem de alto nível (a linguagem C, por exemplo) para código
de máquina, ou seja, para a linguagem que o computador trabalha. Além desses
conceitos, este capítulo também aborda a elaborações de expressões
aritméticas em C, bem como alguns de seus comandos básicos.
=== Introdução
Você viu no capítulo anterior que existe uma linguagem que os computadores
são capazes de compreender e que é utilizada na elaboração de algoritmos
para instruí-los a executarem as mais diversas tarefas. Essa linguagem é
chamada de linguagem de programação e consiste no principal assunto deste
capítulo.
Assim como as linguagens naturais (português, inglês, espanhol, etc.), as
linguagens de programação têm o objetivo de prover um meio eficaz de
comunicação. Elas são constituídas de um conjunto de palavras especiais
(vocabulário), que associadas a um conjunto de regras de utilização,
determinam como os algoritmos devem ser especificados para que possam ser
corretamente decodificados pelo computador.
As linguagens de programação diferem das naturais de várias formas.
Primeiramente, apesar de ser possível de serem utilizadas como meio de
comunicação entre pessoas, seu principal propósito é possibilitar a
comunicação entre uma pessoa e um computador. Além disso, as linguagens
naturais são mais tolerantes a erros. Um erro gramatical, por exemplo, não
impossibilita uma conversa entre duas pessoas. Já no caso das linguagens de
programação, a simples omissão de um ponto e vírgula é capaz até de
impedir que a comunicação seja iniciada.
////
TODO - REV.02: Sugestão de melhoria para análise pelo autor
Acho que o nome de área de conhecimento (l.63) como ciência da computação são
sempre iniciadas em maiúscula.
////
O conteúdo da comunicação por meio de uma linguagem de programação tem um
significado especial para a ciência da computação. Enquanto que nos
expressamos nas linguagens naturais através de textos e da emissão de sons,
nas linguagens de programação nos expressamos através de programas, que nada
mais são do que algoritmos escritos em uma linguagem de programação. O
estudo das técnicas para elaboração de programas consiste em um dos pilares
da ciência da computação, conferindo uma importância particular à
disciplina de Introdução à Programação.
Antes que você conheça as peculiaridades de uma linguagem de programação
estruturada, como suas principais instruções e regras para a construção de
um programa, estudaremos os paradigmas de programação existentes e o processo
de tradução de um programa escrito em linguagem de alto nível para um
programa em código de máquina.
=== Níveis das Linguagens de Programação
Os computadores representam as informações através da manipulação de dois
estados. Esse sistema de representação, denominado de sistema binário,
decorre do fato da grande maioria dos componentes eletrônicos poder assumir
apenas dois valores. Por exemplo, uma lâmpada pode estar no estado
"ligado" ou "desligado", um capacitor pode estar "carregado" ou
"descarregado" e um circuito elétrico pode estar energizado ou não.
A representação binária utiliza os algarismos "0" e "1", chamados de
dígitos binários. Eles são os valores que um bit (menor unidade de
informação em um computador) pode assumir e estão associados aos valores de
tensão presentes nos circuitos elétricos do computador. Para representar o
bit zero, por exemplo, normalmente utiliza-se um valor próximo a zero volts.
Para o bit um, utiliza-se um valor um pouco maior, da ordem de poucos volts.
////
TODO - REV.03: Sugestão de melhoria para análise pelo autor
É mais legível no decorrer do texto fazer referência ao identificador da legenda de figuras/quadros/tabelas,
ao invés de mencionar "o quadro abaixo" ou algo parecido.
////
Repare que trabalhar com uma combinação de zeros e uns não é uma tarefa
fácil para um ser humano. Para que você perceba a dificuldade, imagine como
seria escrever um pseudocódigo substituindo comandos como "LEIA",
"ESCREVA" e expressões aritméticas por uma combinação de zeros e uns.
O quadro de código binário hipotético abaixo ilustra tal situação,
apresentando um algoritmo em pseudocódigo
que calcula a média de duas notas lidas da entrada padrão e sua versão
hipotética em código binário.
.Algoritmo em pseudocódigo
-------------
ALGORITMO
DECLARE nota1,
nota2,
M : NUMÉRICO
LEIA nota1
LEIA nota2
M <= (nota1 + nota2) / 2
FIM_ALGORITMO.
-------------
////
TODO - REV.03: Sugestão de melhoria para análise pelo autor
Seria interessante fazer referência cruzada entre os dois exemplos (ex. o código binário
representar hipoteticamente o código acima).
////
.Ilustração de um código binário hipotético referente a um algoritmo escrito em pseudocódigo.
-------------
10100001
10100011 10010001
10010010
10010011 11000001
10100100 10010001
10100100 10010010
10010011 11110011 10010001 11110001 10010010 11110010 00000010
10100010
-------------
IMPORTANT: O código binário acima apresentado tem fins meramente
didáticos, não sendo elaborado com base em nenhuma máquina real.
Com o intuito de tornar menos complicada, mais eficiente e menos sujeita a
erros a tarefa de programar computadores, foram criadas linguagens de
programação mais próximas às linguagens naturais. Elas são compostas de um
conjunto de palavras-chave, normalmente em inglês, e símbolos que estabelecem
os comandos e instruções que podem ser utilizados pelo programador na
construção de seus programas.
As linguagens com essa característica são chamadas de linguagens de alto
nível, ao passo que as mais próximas da linguagem de máquina
(representação binária), são denominadas de linguagens de baixo nível.
São exemplos de linguagens de alto nível: Pascal, C (linguagem abordada neste
livro), Java, C++ e Python. Como exemplo de linguagem de baixo nível, temos a
linguagem Assembly.
////
TODO - REV.04: Sugestão de melhoria para análise pelo autor
Seria interessante colocar aqui link para outras informações, ou curiosidades, e
assim vai.
////
=== Tradutores e Interpretadores
Se por um lado as linguagens de programação facilitam o trabalho dos
programadores, por outro impossibilitam que os programas desenvolvidos nessas
linguagens sejam compreendidos pelos computadores, visto que eles são capazes
de manipular apenas códigos binários. Dessa forma, os cientistas do passado
se depararam com o seguinte desafio: como executar um programa em linguagem de
programação, seja ela de baixo ou alto nível, em um computador que trabalha
apenas como números binários?
À primeira vista, o desafio em questão parece ser complexo demais para ser
solucionado por um iniciante na ciência da computação. Todavia, você vai
perceber que a solução para o problema está mais próxima da sua realidade
do que imagina. Suponha que você recebeu uma proposta milionária para
trabalhar em uma empresa de desenvolvimento de software da China. Seus patrões
pagarão suas passagens, hospedagem, transporte e todo o aparato necessário
para que você possa ter uma vida tranquila no país asiático. Apesar de a
proposta ser atraente, existe um problema: todos os funcionários da empresa
falam somente o chinês, inclusive seus patrões. Além disso, o contrato a ser
assinado também está escrito em chinês. O que você faria em tal situação?
////
TODO - REV.05: Sugestão de melhoria para análise pelo autor
A resposta a pergunta realizada (l.189) pode ser reescrita de uma forma mais
suave, como: "Recusaria a proposta? Certamente não, pois uma solução possível seria utilizar
um tradutor para resolver o problema.
////
Recusaria a proposta? É isso mesmo que você está pensando, um tradutor seria
a solução para seus problemas.
Do mesmo modo que você precisaria de um tradutor para poder lidar com uma
linguagem que não consegue entender, os computadores também necessitam de um
tradutor para traduzir um programa escrito em linguagem de programação para
um programa correspondente em linguagem de máquina. Dois softwares básicos
são responsáveis por realizar a tradução em questão: os tradutores e os
interpretadores.
////
TODO - REV.06: Sugestão de melhoria para análise pelo autor
Sugeriria adicionar figuras para ilustrar o texto apresentado abaixo
também.
////
Os tradutores podem ser classificados como montadores e compiladores. Quando o
processo de tradução converte um programa que se encontra no nível de
linguagem de montagem (representação simbólica da linguagem de máquina,
ex.: linguagem Assembly) para a linguagem de máquina, o tradutor utilizado é
o montador. Já na tradução de programas em linguagem de alto nível para a
linguagem de montagem, o software responsável é o compilador. Perceba que
não há tradução direta da linguagem de alto nível para a linguagem de
máquina. Para que esta seja alcançada, são necessários vários passos
intermediários,
sendo um deles a tradução para a linguagem de montagem.
[[fig_compilacao_passos]]
["graphviz"]
.Passos no processo de compilação
----
digraph automata_0 {
rankdir=LR;
size ="8.5, 11";
node [shape = box];
subgraph clusterCodigos {
label = "Código fonte";
node [style=filled,color=white];
style=filled;
color=lightgrey;
code_assembly [label="Linguagem de Baixo Nível\n(Linguagem de montagem)"];
code_c [label="Linguagem de Alto Nível\n(Ex: C, Pascal, ...)"];
}
subgraph clusterTradutor {
label = "Tradutor";
node [style=filled,color=white,shape="doubleoctagon"];
style=filled;
color=lightgrey;
montador [label="Montador"];
compilador [label="Compilador"];
}
code_gerado [label="Código traduzido\n(Linguagem de montagem)"];
subgraph clusterCodigoObjeto {
label = "Código Objeto\n(binário)";
node [style=filled,color=white];
style=filled;
color=lightgrey;
objeto1 [label="Código Objeto 1"];
objeto2 [label="Código Objeto 2"];
}
ligador [label="Ligador",shape="doubleoctagon"];
programa [label="Código Binário\nExecutável", shape="component", fillcolor="grey", style="filled"];
carregador[label = "Carregador", colorscheme=oranges4, fillcolor=3, color=2];
code_assembly -> montador -> objeto1 [color="forestgreen", style="bold"];
code_c -> compilador -> code_gerado -> montador -> objeto2 [color="blue", style="bold"];
objeto1->ligador [color="red", style="bold"];
objeto2->ligador [color="red", style="bold"];
ligador-> programa [color="red", style="bold"];
programa -> carregador -> memoria
{rank=source; code_c code_assembly }
{rank=same; montador compilador}
{rank=same; objeto1 objeto2}
{rank=sink; programa}
}
----
No processo de compilação, cada parte de um programa (módulo) escrito em
linguagem de alto nível é traduzido para um módulo objeto diferente, que
consiste em sua representação em linguagem de montagem. Esse passo no
processo de compilação corresponde ao passo 1 da <<fig_compilacao_passos>>. Antes de serem
traduzidos para linguagem de máquina pelo montador, é necessário que os
vários módulos objetos sejam integrados de modo a formarem um único código.
Essa tarefa é realizada no passo 2. O passo 3 é o responsável por carregar o
programa na memória, a fim de tornar suas instruções prontas para serem
executadas pelo processador.
Os interpretadores, além de realizar a tradução de um programa para a
linguagem de máquina, ainda executam suas instruções. Assim que traduz uma
instrução, ela é imediatamente executada, gerando assim um ciclo de
tradução e execução que prossegue de instrução a instrução até o fim
do programa (<<fig_interpretacao>>).
[[fig_interpretacao]]
.Processo de Interpretação
["graphviz", scaledwidth="90%"]
----
digraph automata_0 {
rankdir=LR;
node [shape = box];
linguagem [label="Linguagem\nde Alto Nível", shape="polygon"];
interpretador [label="Interpretador"];
traduz [label="Traduz e executa \numa intrução", shape="hexagon"];
linguagem -> interpretador -> traduz
traduz -> interpretador [label="Busca nova intrução"]
{rank=sink; traduz}
}
----
Por não traduzir um programa escrito em linguagem de alto nível diretamente
para linguagem de máquina, o processo de compilação tende a ser mais rápido
que o processo de interpretação. Além disso, uma vez compilado, um programa
pode ser executado várias vezes sem a necessidade de haver uma recompilação.
Já na interpretação, cada vez que um programa tiver que ser reexecutado,
todo o processo de interpretação deverá ser refeito, independente de ter
ocorrido modificações no código fonte do programa desde sua última
execução. A vantagem da interpretação fica por conta da possibilidade de
testar os programas ao mesmo tempo em que são desenvolvidos.
IMPORTANT: A linguagem utilizada neste livro (linguagem C) como ferramenta para iniciá-lo
na programação de computadores é uma linguagem compilada, portanto, os
programas que você irá desenvolver passarão pelos passos explanados
anteriormente.
// XXX Como manter as referencias aos outros livros da coleção consistente?
NOTE: Para saber mais sobre o processo de montagem e compilação, leia a Seção 3
do Capítulo 5 do Livro de Introdução ao Computador.
////
TODO - REV.07: Sugestão de melhoria para análise pelo autor
Não haveria outros materiais que pudessem ser adicionados como links ou curiosidades?
////
=== Paradigmas de Programação
////
TODO - REV.08: Sugestão de melhoria para análise pelo autor
É importante deixar mais suave para o leitor a transição de uma seção para a outra.
Perceba que saímos de uma seção que explica os compiladores e interpretadores e caímos, de repente,
em outra, que explica paradigmas. Que relação temos com os dois assuntos? Por que, eu como aluno, preciso
saber disso mesmo? Que relação há com o ato de programar? Parece blá blá, mas para o aluno que
está lendo e aprendendo sozinho faz uma diferença grande, pode ter certeza.
////
Um paradigma de programação está relacionado com a forma de pensar do
programador na construção de soluções para os problemas com os quais se
depara. Programar seguindo um determinado paradigma de programação significa
representar soluções a partir de uma forma particular de raciocinar na
elaboração dos algoritmos. Como os paradigmas mencionados sustentam a
atividade de programas, eles influenciam todo o processo de desenvolvimento de
software. Alguns dos paradigmas de programação mais utilizados estão
relacionados abaixo:
////
TODO - REV.09: Sugestão de melhoria para análise pelo autor
Foi explicado o que é uma variável?
////
Paradigma imperativo:: Representa a computação como ações, enunciados ou
comandos que alteram o estado (variáveis) de um programa. Consiste na
elaboração de programa a partir de comandos que dizem o que o computador deve fazer
a cada momento.
Paradigma estruturado:: Soluciona problemas a partir de sua quebra em problemas
menores, de mais fácil solução, denominados de sub-rotinas ou subprogramas.
Normalmente, o trabalho de cada sub-rotina consiste em receber dados como
entrada, processar esses dados e retornar o resultado do processamento para o
módulo de software que o executou. Este paradigma ainda defende que todo
processamento pode ser realizado pelo uso de três tipos de estruturas:
sequencial, condicional e de repetição. É o paradigma adotado neste livro.
Paradigma declarativo:: Descreve as características da solução desejada sem
especificar como o algoritmo em si deve agir. Em contraste com o paradigma
imperativo, que informa ao computador como as instruções devem ser
executadas, o paradigma declarativo preocupa-se apenas em definir o que deve
ser feito, deixando a cargo de outros softwares decidirem como alcançar a
solução descrita. É bastante utilizado no desenvolvimento das páginas web
(linguagem html) e na descrição de documentos multimídia através da
linguagem Nested Context Language – NCL, adotada pelo padrão brasileiro de
TV Digital.
Paradigma orientado a objetos:: Enxerga o problema como uma coleção de objetos
que se comunicam por meio da troca de mensagens. Os objetos são estruturas de
dados que possuem estado (variáveis) e comportamento (lógica).
////
TODO - REV.10: Sugestão de melhoria para análise pelo autor
Aqui nesse ponto também seria interessante ter seções "Para saber mais..." ou
links e gravuras que ilustram um pouco mais o que está sendo explicado.
////
=== Linguagem C
////
TODO - REV.11: Sugestão de melhoria para análise pelo autor
Sugeriria que a explicação da linguagem C fosse em um capítulo a parte.
////
A linguagem C foi desenvolvida por Dennis Ritchie, entre os anos 1971 e 1973,
nos laboratórios da AT&T. O objetivo de Ritchie era criar uma linguagem para a
implementação de sistemas operacionais e softwares básicos
que combinasse a
eficiência das linguagens de baixo nível com características das linguagens
de alto nível, como legibilidade, portabilidade e manutenibilidade.
////
TODO - REV.12: Sugestão de melhoria para análise pelo autor
Sugeriria deixar os detalhes do processo evolutivo da linguagem C como parte
de seções "Para saber mais..." ou algo assim. Estamos tratando de um aluno iniciante
a distância e de repente colocamos várias informações que para ele, no momento, seria
sobrecarga.
////
A criação da linguagem C é resultado de um processo evolutivo de linguagens,
iniciado com uma linguagem chamada BCPL, desenvolvida por Martin Richards. Essa
linguagem influenciou a linguagem B, inventada por Ken Thompson, que por sua
vez levou ao desenvolvimento de C.
////
TODO - REV.13: Sugestão de melhoria para análise pelo autor
Caso seja realmente julgado necessário citar o Unix é interessante dizer que se trata
de um sistema operacional e adicionar link para "maiores informações ou para saber mais..".
////
Em 1973, Dennis Ritch não deixou dúvidas que seu objetivo foi alcançado,
desenvolvendo eficientemente parte do sistema Unix na linguagem C. A partir de
meados dos anos 80, C começou a ganhar popularidade e, devido à sua
flexibilidade em atuar com características de linguagens de alto e baixo
nível, foi reconhecida como uma linguagem de propósito geral, sendo utilizada
na implementação de uma grande variedade de sistemas.
Devido à importância auferida na área da programação de computadores, C é
hoje uma das linguagens mais utilizadas em cursos de programação do mundo
inteiro. Sendo assim, ela é a linguagem que guiará você na compreensão das
nuances da arte de programar e servirá como ferramenta para elaboração dos
seus primeiros programas. A linguagem C será apresentada, de forma
conveniente, a partir da próxima seção.
=== Núcleo de um programa
////
TODO - REV.14: Sugestão de melhoria para análise pelo autor
Para ser mais didático não haveria como correlacionar o núcleo do programa
com o que já foi apresentado em linguagem algoritmica? Sinto que Precisamos ser mais
didático nas explicações.
////
A organização da sequência de instruções em um programa obedece a um
conjunto de regras estabelecidas pela linguagem de programação. Um programa
em C é estruturado em funções, que são, basicamente, trechos de código que
podem ser chamados várias vezes para realizar uma certa tarefa. Assim, todas
as instruções pertencentes a um programa em C devem estar contidas em uma
função.
Além de ser um meio de agrupar trechos de um código, uma função em
programação tem características semelhantes a uma função matemática, no
sentido de que recebe parâmetros como entrada (seria o domínio da função) e
retorna um valor como saída (imagem).
Em C existe uma função especial, denominada de `main` (principal), que
determina o início e o fim da execução de um programa. De forma mais
específica, a execução de um programa tem seu início com a execução da
primeira instrução da função `main` e termina com a execução da sua
última instrução. Dessa maneira, todo programa em C deve possuir tal
função.
IMPORTANT: Você conhecerá mais sobre as funções no Capítulo 6 deste
livro. Por enquanto é necessário apenas que você saiba que todo programa em
C deve ter uma função `main` e que a execução de um programa inicia e termina
com a execução de seus comandos.
////
TODO - REV.15: Sugestão de melhoria para análise pelo autor
Diga apenas que iniciaremos nosso estudo com um programa simples... evite
os advérbios, pois haverá aluno que ainda não entenderá o exemplo mesmo sendo simples.
////
Iniciaremos nosso estudo com um programa extremamente simples, que apenas
imprime uma mensagem na tela:
////
TODO - REV.16: Sugestão de melhoria para análise pelo autor
Colocar uma representação mais abstrata de como um programa em c é estruturado, tipo
o que normalmente é apresentado quando quando se inicia o estudo de uma linguagem
algoritmica, informando por exemplo, a estrutura em blocos de um programa c.
Outro ponto é verificar se está realmente aparecendo a numeração das linhas que
apresentam o código. Na versão html não está aparecendo.
////
[source, c]
----------------
#include <stdio.h>
int main() {
printf("Meu primeiro programa!");
return 0;
}
----------------
Analisemos o que ocorre em cada linha de código:
#include <stdio.h>
Esta linha de código solicita ao compilador que inclua no programa a
biblioteca padrão para comandos de entrada e saída da linguagem C. Uma
biblioteca consiste em um conjunto de arquivos que contém funções que podem
ser incorporadas a outros programas. Neste caso, a inclusão da biblioteca
`stdio.h` permite que o programa utilize suas funções para ler dados da entrada
padrão (teclado) e para escrever dados na saída padrão (tela).
(((Entrada padrão))) (((Saída padrão)))
int main(){
Com esta linha de código definimos a função `main` e demarcamos o seu início
com o caractere `{` (abre-chaves). Todo conteúdo de uma função em C fica
delimitado por chaves ( `{}` ).
printf("Meu primeiro programa!");
O programa tem seu início com a execução desta instrução, uma vez que ela
é a primeira instrução da função `main`.
A função `printf` tem a finalidade de escrever na tela os dados recebidos por
parâmetro. Como resultado de sua execução, neste caso, será exibida a
frase "Meu primeiro programa!" no canto superior esquerdo do monitor do
computador.
O ponto-e-vírgula no fim da instrução serve para separar esta instrução da
próxima, dessa maneira, cada instrução deve terminar com sua utilização.
return 0;
Essa instrução encerra a execução do programa, de modo que deve ser sempre
a última da função `main` (antes do fecha-chaves, é claro). O número 0
(zero) serve para indicar ao sistema operacional que o programa terminou com
sucesso (números diferentes de zero indicariam um erro). Você entenderá
melhor como isso funciona quando abordarmos detalhadamente as funções, no
capítulo 6.
=== Memória e Variáveis
A memória principal do computador ou memória RAM (<<fig_memoria_ram>>) é constituída
por componentes eletrônicos capazes de armazenar dados. Cada dígito binário
(0 ou 1) ocupa uma porção de memória chamada de bit, e um conjunto de 8 bits
é denominado de byte. A memória é dividida em células de memória de um
byte de tamanho, que podem ser acessadas a partir de um número único que as
identifica de forma particular. Esse número é chamado de endereço e tem a
mesma função que os endereços de nossas casas, que é identificar de forma
única nossas residências, a fim de possibilitar o envio e o recebimento de
correspondências. No caso do computador, as correspondências são os dados
que serão armazenados nas células de memória.
[[fig_memoria_ram]]
.Figure 2.3: Memória RAM.
image::images/introducao-a-programacao/memoria.png[]
Uma variável em programação representa, através de símbolos, o conteúdo
de uma célula ou posição de memória. Por exemplo, se uma variável de nome
`x` possui o valor 10, significa dizer que a posição de memória, representada
pelo símbolo `x`, armazena o valor 10. Em programação, podemos enxergar a
memória como um conjunto de posições que possuem um endereço e uma
representação simbólica (variável), como ilustrado na <<fig_memoria_representacao>>.
[[fig_memoria_representacao]]
.Representação da memória em função dos endereços, das posições de memória e das variáveis.
image::images/introducao-a-programacao/memoria-representacao.eps[scaledwidth="70%"]
////
TODO - REV.17: Sugestão de melhoria para análise pelo autor
Talvez fosse interessante mencionar que as regras para nomes de variáveis
dependem da linguagem de programação.
////
NOTE: As variáveis podem ter nomes diversos, desde símbolos comuns na
matemática, como é o caso das variáveis `x`, `y` e `z`, até
nomes como `var`,
`endereco`, `cpf`, etc. As regras para dar nome às variáveis serão
apresentadas na próxima seção. Perceba também que os valores que as
variáveis podem armazenar não se limitam apenas a valores numéricos
inteiros. Elas podem armazenar, por exemplo, um conjunto de caracteres, como é
o caso da variável `z`, e valores fracionários, como é o caso das variáveis `y`
e `var`.
==== Identificadores
Os nomes que damos às variáveis, rotinas, constantes e demais componentes num
programa escrito numa dada linguagem de programação são chamados de
identificadores. Na seção anterior, por exemplo, utilizamos os
identificadores `x`, `y`, `z` e var para dar nome às variáveis da <<fig_memoria_representacao>>.
As palavras que possuem significado especial nas linguagens de programação, como
é o caso dos nomes dados às estruturas de controle (`for`, `while`, `if`, etc.),
tipos de variáveis, dentre outros, são chamadas de palavras-chave.
As regras básicas para formação de identificadores são:
* Os caracteres utilizados são os números, letras maiúsculas, minúsculas e o
caractere especial sublinha (_);
* O primeiro caractere deve ser uma letra ou o sublinha;
* Não são permitidos espaços em branco;
* Palavras reservadas não podem ser utilizadas como identificadores.
Abaixo, alguns exemplos de identificadores válidos:
---------------
B
b
X2
computacao
COMPUTACAO
nota1
nota_2
cpf
RG
---------------
Identificadores inválidos:
---------------
3B -> Não pode começar com número.
X 2 -> Não pode conter espaço em branco.
Computaçao -> Não é permitido utilizar o caractere cedilha.
COMPUTACÃO -> Caracteres especiais como o til (~) não são permitidos.
while -> while é uma palavra reservada.
function -> function também é uma palavra reservada.
---------------
////
TODO - REV.17: Sugestão de melhoria para análise pelo autor
Explicar o porquê dessa boa prática, isto é, informar que melhora
a legibilidade do código.
////
Uma boa prática de programação é escolher nomes que indiquem a função de
uma variável, como por exemplo: `soma, ano, idade, media, dataNascimento,
numero_filhos, nota1, nota2, notaFinal, salario,` etc. Também é uma prática
bastante difundida iniciar os identificadores com letras minúsculas e usar
letras maiúsculas ou sublinha para separar palavras. Por exemplo, para
escolher um identificador para uma variável que deve armazenar a data de
nascimento de uma pessoa, as duas opções citadas correspondem à
`dataNascimento` e `data_nascimento`, respectivamente.
////
TODO - REV.18: Sugestão de melhoria para análise pelo autor
Sugeriria fornecer outro exemplo ao invés de var e Var, simplesmente para evitar
confusões.
////
IMPORTANT: A linguagem C faz distinção entre letras maiúsculas e minúsculas, sendo
assim, variáveis de nomes var e Var são consideradas como duas variáveis
diferentes.
==== Tipos de dados primitivos
Vimos anteriormente que as variáveis podem armazenar valores de diversos
tipos, tais como números inteiros, fracionários e um conjunto de caracteres.
Os tipos de dados ou tipo de variáveis são representados de forma diferente
em cada linguagem de programação, algumas dando suporte a mais tipos que
outras. Embora haja certa variação de uma linguagem para outra, a maioria
delas dá suporte a um grupo de tipos básicos, incorporados na própria
linguagem, chamados de tipos primitivos. Em C há a possibilidade da criação,
por parte do programador, de tipos particulares, denominados de tipos
derivados. Estudaremos as formas de definirmos tipos derivados no Capítulo 5.
Existem três tipos primitivos na linguagem C: números inteiros, números de
ponto flutuante (números fracionários) e caracteres. Os números
fracionários são chamados de números de ponto flutuante devido à forma como
eles são armazenados no computador. Portanto, sempre que você ouvir o termo
ponto flutuante, tenha em mente que o tipo de dados em questão diz respeito
aos números fracionários.
////
TODO - REV.19: Sugestão de melhoria para análise pelo autor
Adiciona exemplo dos tipos mencionados.
////
Os tipos de dados primitivos em C estão descritos na tabela abaixo:
.Tipos primitivos da linguagem C
[width="90%",cols="^1m,^2,4",frame="topbot",options="header,footer"]
|======================
|Tipo|Tamanho (em bytes)|Função
| int|4|Armazenar um número inteiro.
| float|4|Armazenar números de ponto flutuante.
| double|8|Armazenar números de ponto flutuante com maior precisão.
| char|1|Armazenar um caractere.
|======================
Como as variáveis de tipos primitivos distintos são representadas na memória
de formas diferentes, elas exigem uma quantidade de bytes distinta para seu
armazenamento. Uma variável do tipo *int*, por exemplo, ocupa normalmente quatro
bytes na memória, ao passo que uma variável do tipo *char* ocupa apenas 1 (um)
byte.
É importante salientar que o tipo *char* na linguagem C, diferentemente de
outras linguagens, pode também armazenar números inteiros que requerem apenas
um byte de memória. O que ocorre é que há uma correspondência entre um
caractere e um número inteiro, conforme uma tabela padrão. Por exemplo,
quando atribuímos a variáveis do tipo *char* valores como a, b e c, na verdade
estamos atribuindo os valores inteiros 97, 98 e 99. Os números inteiros que
correspondem aos caracteres estão todos listados em uma tabela padrão,
conhecida como tabela ASCII.
////
TODO - REV.20: Sugestão de melhoria para análise pelo autor
Adiciona exemplo dos tipos mencionados.
////
O tipo int pode ainda ser qualificado de acordo com as seguintes palavras-chave:
short ou long:: se referem ao tamanho das variáveis;
signed ou unsigned:: indicam, respectivamente, se as variáveis do tipo *int*
poderão ser positivas e negativas (com sinal) ou apenas positivas (sem sinal) .
A qualificação de tipo é realizada quando os qualificadores são antepostos
aos tipos. Por exemplo, uma variável do tipo *unsigned long int* armazena
inteiros positivos de tamanhos grandes, enquanto que uma variável do tipo
*signed short int* armazena inteiros positivos e negativos de tamanhos menores.
A tabela a seguir ilustra os valores que normalmente podem ser armazenados nas
variáveis do tipo *int* e diversas de suas variações.
.Intervalos de valores de tipos inteiros utilizados por grande parte dos compiladores de C.
[width="100%",cols="^2,^2,3",frame="topbot",options="header"]
|======================
|Tipo|Tamanho (em bytes)|Valores que podem ser armazenados
| int|4|-2^31^ a 2^31^ - 1
| short int|2|-2^15^ a 2^15^ - 1
| long int|4|-2^31^ a 2^31^ - 1
| unsigned int|4|0 a 2^32^ - 1
| unsigned short int|2|0 a 2^16^ - 1
| unsigned long int|4|0 a 2^32^ - 1
| signed char|1|-2^7^ a 2^7^ - 1
| unsigned char|1|0 a 2^8^ - 1
| long long int|8|-2^63^ a 2^63^ - 1
| unsigned long long int|8|0 a 2^64^ - 1
|======================
NOTE: Os tamanhos e valores presentes nas tabelas anteriores podem variar de
compilador para compilador. Desse modo, eles servem apenas como um guia de
referência para de norteá-lo na escolha dos tipos adequados aos programas que
você desenvolverá.
==== Declaração de variáveis
Cada variável utilizada na elaboração de um programa precisa ser definida
com antecedência. Para isso, o programador precisa definir o identificador da
variável e o seu tipo por meio do que chamamos de declaração de variáveis.
Sua forma geral é a seguinte:
tipo_da_variável identificador;
O exemplo a seguir declara, na linguagem C, as variáveis x e y como sendo do
tipo *int*.
int x, y;
////
TODO - REV.21: Sugestão de melhoria para análise pelo autor
Não seria interessante ilustrar a explicação abaixo com uma gravura?
////
A declaração de variáveis, além de estabelecer uma interpretação sobre os
bits armazenados na memória, também é responsável por alocar espaço para
armazenamento desses
bits. No exemplo anterior, a declaração das variáveis `x`
e `y` resulta na alocação de 4 bytes (provavelmente) para cada uma delas, bem
como determina que os bits a serem armazenados no espaços alocados deverão
ser interpretados como números inteiros. Seguem abaixo alguns exemplos de
declarações de variáveis:
int idade;
int numeroFilhos;
int dia, mes, ano;
float altura;
float nota, media;
Os tipos das variáveis tem uma relação muito próxima com a função que
elas exercem em um programa. Caso precisemos armazenar e realizar cálculos
sobre a idade de alguém, deveremos declarar a variável `idade` como *int*, visto
que a idade corresponde a um número inteiro positivo. Do mesmo modo, como
sabemos que a altura de uma pessoa é um número fracionário (ex.: 1,80 cm),
devemos declarar a variável `altura` como sendo do tipo *float*. Variáveis do
mesmo tipo podem ser declaradas em uma mesma linha, sendo separadas por
vírgulas, como na declaração das variáveis `dia`, `mes` e `ano` do exemplo
anterior. Já variáveis de tipos diferentes devem ser declaradas
obrigatoriamente de forma separada.
Um programa elaborado com base no paradigma estruturado pode ser visto como uma
sequência de transições de estado do início até o fim de sua execução.
Se pudéssemos tirar uma "foto" da execução de um programa em determinado
momento, o que observaríamos seria o conjunto de suas variáveis e os valores
nelas armazenados no momento, isto é, o estado do programa. Se os programas
podem mudar de estado, então deve existir um comando nas linguagens de
programação que permitam alterar o conteúdo de uma variável. Tal comando é
denominado de *atribuição*, e o exemplo a seguir mostra como ele é utilizado
em pseudocódigo.
////
TODO - REV.22: Sugestão de melhoria para análise pelo autor
Note que agora utilizamos de explicações da linguagem algoritmica para fazer
a correlação com a linguagem de programação C. Este talvez fosse um recurso mais
didático.
////
idade ← 18
Essa instrução deve ser lida como "a variável `idade` recebe o valor 18
(dezoito)". Em C, o comando correspondente é:
idade = 18;
NOTE: Podemos utilizar o comando de atribuição no momento da declaração de uma
variável. Esse procedimento é chamado de inicialização de variáveis.
// FIXME Equação x pertence aos reais
Embora C e outras linguagens de programação utilizem o operador aritmético
da igualdade para representar a atribuição, as semânticas de ambos não
podem ser confundidas. Por exemplo, em matemática a equação
`x=x+1`, onde `x pertece aos Reais`, nunca
pode ser satisfeita, visto que um número real não poder ser igual a ele
próprio mais um. Já em programação, o comando `x = x + 1` quer dizer que a
variável `x` irá receber o conteúdo armazenado nela própria mais um. Supondo
que `x` possuía conteúdo igual a 10 antes da execução da atribuição em
questão, após sua execução `x` seria igual a 11 (`x = 10 + 1 = 11`). A tabela
abaixo ilustra o cenário apresentado.
[width="50%",cols="^1m,^1m",frame="topbot",options="header"]
|======================
|Comando |Valor atual de x
| `int x;` |Indefinido
| `x = 10;` |10
| `x = x + 1;` |11
|======================
Essa forma de atribuição é um artifício bastante empregado na
programação, sendo denominado de *incremento*.
==== Constantes simbólicas
(((Constantes)))
Muitas vezes declaramos algumas variáveis que não devem ser modificadas
durante a execução de um programa. É o caso das variáveis abaixo:
PI = 3.14159;
ACELERACAO_GRAVIDADE = 9.8;
VELOCIDADE_LUZ = 300000;
(((Constantes simbólicas)))
Não faz sentido alterar o valor de uma variável que representa a aceleração
da gravidade, por exemplo, pois o valor da constante gravitacional, como seu
próprio nome já diz, permanece sempre o mesmo. Para casos como esse é
preferível que usemos *constantes simbólicas* no lugar de variáveis. A
linguagem C permite que um identificador seja associado a uma constante
através da diretiva `#define`, cuja sintaxe é descrita abaixo:
#define nome_constante valor_constante;
Dessa forma, a definição da constante `PI` mencionada acima poderia ser
realizada através da linha de código:
#define PI = 3.14159;
Quando o programa que contém essa instrução é compilado, o compilador
substitui todas as ocorrências de `PI` pelo seu valor associado.
Outra utilidade proveniente da definição de constantes diz respeito à
facilidade de modificação de um programa. Imagine que você desenvolveu
um programa para o registro contábil de uma locadora de veículos e que em
vários trechos de código você usou o valor da diária de locação para
realizar diversos cálculos. Suponha agora que o dono da locadora aumentou o
valor da diária de locação e que você foi chamado para modificar o programa
a fim de adequá-lo ao novo valor. Dessa forma, você terá que alterar cada
ocorrência contendo o valor antigo e o seu trabalho será proporcional ao
número de ocorrências desse valor. Utilizando constantes simbólicas, você
precisaria apenas alterar a definição da constante, conforme sugere o quadro
abaixo:
#define VALOR_LOCACAO 80.0
#define VALOR_LOCACAO 100.0
Apesar das regras para definição dos nomes das constantes simbólicas serem
as mesmas daquelas utilizadas para identificadores, é uma boa prática de
programação defini-las com letras maiúsculas, separando as palavras que as
compõem, se houverem, pelo caractere sublinha (`_`).
=== Comentários e indentação
À medida que um programa cresce, ele vai ficando cada vez mais difícil de ser
lido e consequentemente de ser entendido. É comum um programador ter grandes
dificuldades para compreender seus próprios programas após passar alguns dias
sem trabalhar em suas linhas de código. Por isso, algumas medidas devem ser
tomadas no sentido de preparar um código-fonte legível. Existem várias
formas de aprimorar a legibilidade de um programa, contudo nos restringiremos
aos comentários e à indentação.
Explicar o código-fonte em linguagem natural é uma estratégia óbvia para
facilitar sua compreensão. Esse é o papel dos comentários em uma linguagem
de programação. Em C, qualquer sequência de caracteres localizada entre os
delimitadores `/*` e `*/` é um comentário. Por exemplo:
z = x + y; /* z é o resultado da soma entre x e y. */
A explicação da linha de código acima, embora desnecessária devido à
simplicidade da instrução, é um comentário na linguagem C. Outras
linguagens podem usar delimitadores distintos para expressar os comentários. A
linguagem C ainda possui o delimitador //, muitas vezes chamado de delimitador
de comentário de linha. Os caracteres colocados à sua frente e na mesma linha
em que ele se encontra são considerados comentários e ignorados pelo
compilador. Exemplo de sua utilização:
int idade; // Variável inteira para representar
// a idade de uma pessoa.
Perceba que para comentar mais de uma linha com o delimitador //, precisamos
utilizá-lo em cada linha que se deseja comentar. Dessa maneira, quando se
deseja comentar mais de uma linha é mais adequado o uso dos delimitadores
`/*` e `*/`.
Outra forma de tornar um programa mais legível é organizar as instruções de
modo a refletir a hierarquia entre elas. Por exemplo:
[source, c]
-----------------
#include <stdio.h>
/* Código não-indentado */
int main() {
int x, y, z;
x = 10;
y = 2;
z = x / y;
if (x > 5) {
printf("x é maior que cinco.");
}
return 0;
}
-----------------
(((indentação)))
Repare que a declaração de variáveis, os comandos de atribuição e os
comandos `if` (apresentado no Capítulo 3) e `return` estão todos dentro da
função `main`. Dizemos então que eles são hierarquicamente subordinados à
função referida. Da mesma forma, o comando `printf` está subordinado ao
comando `if`. Uma maneira de destacar a hierarquização das instruções é
alinhar os
comandos com o mesmo nível de hierarquia (inserindo espaços nas
instruções de nível inferior), o que chamamos de *indentação*. Para que
você visualize o resultado do processo de indentação, considere o código do
exemplo anterior depois de corretamente indentado:
[source, c]
-----------------
#include <stdio.h>
/* Código indentado */
int main() {
int x, y, z;
x = 10;
y = 2;
z = x / y;
if (x > 5) {
printf("x é maior que cinco.");
}
return 0;
}
-----------------
=== Matemática Básica
O computador foi criado com o intuito inicial de realizar contas. Portanto, é
importante que saibamos como instruí-lo a computar as operações aritméticas
básicas. E essa não vai ser uma tarefa difícil, já que as expressões
aritméticas em programação são bastante semelhantes às expressões
utilizadas na matemática. Em C, os operadores matemáticos utilizados são os
seguintes:
.Operadores aritméticos
[width="50%",cols="^1,^1",frame="topbot",options="header"]
|======================
|Operador |Operação
| `+` |Adição
| `-` |Subtração
| `*` |Multiplicação
| `/` |Divisão
| `%` |Resto da divisão
|======================
A utilização dos operadores em C ocorrem da forma com a qual estamos
acostumados: colocamos um operador entre dois operandos e vamos construindo as
expressões. À medida que as expressões vão ficando mais complexas, podemos
utilizar os parênteses para agrupar operadores e operandos. Diferentemente do
que ocorre na matemática, em C não se utilizam colchetes e chaves para o
agrupamento de expressões que já estão entre parênteses. Estes devem ser os
substitutos dos primeiros quando houver necessidade. Por exemplo, a expressão
matemática `[(x+y)-(a+b)]÷2`, em C se tornaria:
((x+y)-(a+b))/2
Veja alguns exemplos de como os operadores aritméticos devem ser usados em C
(os resultados são apresentados ao lado de cada operação):
////
TODO - REV.23: Sugestão de melhoria para análise pelo autor
Recomendaria colocar comentários mais representativos. Além de facilitar
o entendimento dos exemplos, damos o exemplo em praticar o que estamos
dando como boa prática.
////
[source, c]
-----------------
x = 4 * 5; // 20
x = x / 2; // 10
y = x % 4; // 2
z = x * y – 5; // 15
z = x * (y – 5); // -30
z = ((2 + 3) * 4 – 2)/2; // 9
-----------------
A precedência dos operadores aritméticos, isto é, a ordem em que eles são
avaliados, pode ser alterada do mesmo modo que o fazemos quando tralhamos com
expressões na matemática: utilizamos os parênteses para que algumas
operações sejam realizadas antes que outras. É o que ocorre na expressão
acima na expressão `z=x*(y-5)`. Caso os parênteses fossem omitidosfootnote::[Expressão resultante: `z=x*y-5`], a primeira
operação a ser realizada seria a que multiplica `x` por `y`, depois, do seu
resultado seria subtraído cinco. Com a inserção dos parênteses, ocorre
primeiro a subtração para depois ser realizada a multiplicação.
A linguagem C possui ainda operadores especiais que resultam da combinação de
operadores aritméticos com operadores de atribuição. São eles:
////
TODO - REV.24: Sugestão de melhoria para análise pelo autor
Recomendaria adicionar exemplos das operações apresentadas abaixo.
////
.Operadores aritméticos de atribuição e operadores de incremento.
[width="60%",cols="^1m,^2m",frame="topbot",options="header,footer"]
|======================
|Operador |Operação equivalente
| `x += y` | `x = x + y`
| `x -= y` | `x = x - y`
| `x *= y` | `x = x * y`
| `x /= y` | `x = x / y`
| `x %= y` | `x = x % y`
| `x++` | `x = x + 1`
| `++x` | `x = x + 1`
| `x--` | `x = x - 1`
| `--x` | `x = x - 1`
|======================
(((atribuição)))(((incremento)))
Os primeiros cinco operadores são denominados de *operadores aritméticos de
atribuição*, ao passo que os quatro últimos são chamados de *operadores de
incremento*.
Aqui cabe destacar as diferenças entre os operadores de incremento quanto à
localização dos operadores aritméticos. Considere os exemplos a seguir:
[source, c]
-----------------
x = 0;
y = 6;
z = 2;
(a) x = y / ++z; // incremento antes
// y = 6, z = 3, x = 2
-----------------
[source, c]
-----------------
x = 0;
y = 6;
z = 2;
(b) x = y / z++; // incremento depois
// y = 6, z = 3, x = 3
-----------------
Nos exemplos apresentados, temos dois algoritmos que se diferenciam apenas pela
utilização dos operadores de incremento de adição. Na
expressão (a), a variável `y` é dividida por `++z`, enquanto que na expressão
(b) ela é dividida por `z++`. A diferença é sutil, mas é determinante no
resultado da expressão. No primeiro caso, `z` é incrementada antes da divisão,
logo `x=6÷(2 + 1)=6÷3=2`. Na expressão (b), z é incrementado depois da divisão,
o que resulta em `x=6÷2=3`. Repare que em ambos os casos o valor de `z` é
incrementado, de modo que após as instruções (a) e (b) o valor de `z` é igual a 3.
=== Entrada e saída de dados
Imagine que você desenvolveu um programa para controlar suas finanças
pessoais e com ele intenciona conter seus gastos e ainda guardar uma parte do
que ganha na poupança. Esse programa necessita de interação? Como você
informará suas receitas e despesas? Como ele apresentará o resultado dos seus
cálculos com a finalidade de auxiliá-lo no controle de suas finanças?
As respostas de todas essas perguntas convergem para a seguinte conclusão: um
programa de computador é praticamente inútil se não apresentar algum tipo de
interação com o usuário. No cenário anterior, por exemplo, você precisa
informar ao programa quais são as suas receitas e despesas. Além disso, é
necessário que ele o deixe a par dos resultados dos seus cálculos, caso
contrário ele não terá serventia alguma.
(((stdio)))
Os mecanismos que as linguagens de programação oferecem para interação com
o usuário estão presentes em suas bibliotecas de entrada e saída. Em C, as
funções responsáveis pelas operações básicas de entrada e saída se
encontram na biblioteca *stdio*, que é utilizada por meio da diretiva:
#include <stdio.h>
////
TODO - REV.25: Sugestão de melhoria para análise pelo autor
Rever linhas 1073 e 1074.
////
Vimos não seção 2.6 uma forma de exibir na tela uma sequência de caracteres
através da função `printf()`, que, além de imprimir caracteres, também é
capaz de exibir o conteúdo de variáveis de diversos tipos. Os detalhes de
sua utilização, bem como uma função similar para entrada de dados são
apresentados não nas seções posteriores.
==== Função printf()
A função de saída `printf()` permite que dados sejam escritos na saída
padrão, que normalmente é a tela do computador. Uma chamada da função
`printf` tem o seguinte formato:
int printf(string_de_formato, arg1, arg2, ..., argn)
Isso quer dizer que a função `printf` irá escrever na saída padrão os
argumentos `arg1,arg2,...,argn` de acordo com o que está especificado no
parâmetro `string_de_formato`. Além disso, o tipo *int* indica que a função
retorna um número inteiro, que neste caso corresponde ao número de caracteres
impressos. O exemplo a seguir ilustra a utilização da função `printf()`:
[source, c]
-------------
#include <stdio.h>
int main() {
int idade;
float altura;
idade = 18;
altura = 1.90;
printf("Tenho %d anos e %.2f de altura.", idade, altura);
return 0;
}
-------------
Após a execução do código acima será exibida na tela a seguinte frase:
Tenho 18 anos e 1.90 de altura.
Os caracteres `%d` e `%.2f` são denominados de especificadores de formato e têm o
objetivo de definir o formato das variáveis que serão escritas na saída
padrão. De outro modo, podemos entendê-los como "guardadores de lugar"
para as variáveis a serem exibidas. No exemplo acima, no lugar do `%d` será
colocada a primeira variável passada por parâmetro
(`idade`) e no lugar do `%.2f`
a segunda variável (`altura`). Além disso, elas deverão ser dos tipos `int` e
`float`, respectivamente. O ponto seguido de um número antes do código de
formato indica a quantidade de casas decimais a serem exibidas (quando
aplicados a variáveis do tipo ponto-flutuante) e são denominados de
especificadores de precisão. No exemplo anterior eles foram os responsáveis
pela exibição da variável `altura` com duas casas decimais.
A tabela abaixo lista os especificadores de formato mais comuns utilizados na
função `printf()`.
////
TODO - REV.26: Sugestão de melhoria para análise pelo autor
Recomendaria adicionar exemplos.
////
.Especificadores de formatos mais utilizados na função printf()
[width="65%",cols="^1m,2",frame="topbot",options="header"]
|======================
|Código |Formato
| %d ou %i | Inteiro (int) decimal
| %ld ou %li | Inteiro (long int) decimal
| %u | Inteiro sem sinal
| %c | Caractere
| %s | Cadeira de caracteres
| %f | Número de ponto-flutuante
|======================
==== Função scanf()
A função de entrada `scanf()` possibilita a leitura de dados da entrada
padrão, ou seja, do teclado. O que ela faz é interromper a execução do
programa até que o usuário digite algo e depois pressione a tecla Enter.
Depois que o programa retoma sua execução, o conteúdo digitado é armazenado
em uma ou mais variáveis. Uma chamada da função `scanf` tem o seguinte formato:
int scanf(string_de_formato, arg1, arg2, ..., argn)
O parâmetro `string_de_formato` especifica os tipos de dados que serão lidos e
os parâmetros `arg, arg2, ..., argn` correspondem aos endereços das variáveis
nas quais serão armazenados os valores digitados pelo usuário. A função
`scanf()` retorna um valor inteiro que indica o número de variáveis que tiveram
valores atribuídos, sendo utilizado para verificar algum problema na entrada
de dados. O exemplo a seguir ilustra a utilização da função `scanf()`:
[source, c]
-----------------
#include <stdio.h>
int main() {
int idade;
float altura;
printf("Informe sua idade: ");
scanf("%d", &idade)
printf("Informe sua altura: ");
scanf("%f", &altura);
printf("\nVocê tem %d anos e %.2f de altura.", idade, altura);
return 0;
}
-----------------
Ao contrário do exemplo da seção anterior, os dados a serem exibidos não
estão pré-determinados, isto é, as variáveis não possuem valores a priori.
As atribuições apenas ocorrem quando o usuário entra com valores via
teclado. No exemplo, depois que a sequência de caracteres `"Informe sua
idade: "` é exibida, a execução do programa é interrompida até que o
usuário digite um valor. Quando isso ocorre, ele é armazenado no endereço da
variável `idade`, obtido quando ela é precedida pelo caractere `&`. Por
conseguinte, o `printf()` ao final do código irá exibir os dados informados
pelo usuário e não os dados pré-determinados pelo programador.
IMPORTANT: A tecla Enter também possui um caractere que a representa, a saber, o
caractere especial ‘\n’. Portanto, quando o ’\n’ é escrito na saída
padrão, o efeito gerado é o mesmo da digitação da tecla Enter.
A função `scanf()` também pode ler numa mesma linha diversos dados,
armazenando-os em diferentes variáveis. As leituras do código anterior, por
exemplo, podem ser reescritas da seguinte forma:
[source, c]
-------------
printf("Informe sua idade e sua altura:");
scanf("%d %.2f", &idade)
-------------
Para que esse código funcione como desejado, o usuário precisa digitar um
número inteiro seguido de um espaço e depois um número de ponto-flutuante. O
espaço é requerido porque ele é utilizado na especificação do formato
(entre o `%d` e o `%.2f` há um espaço). Assim como `printf()`, a função `scanf()`
também possui uma lista de especificadores de formato. Os mais utilizados
seguem abaixo:
.Especificadores de formatos mais utilizados da função scanf()
[width="75%",cols="^1m,4",frame="topbot",options="header,footer"]
|======================
|Código |Significado
| `%d ou %i` | Leitura de um inteiro (int) decimal
| `%ld ou %li` | Leitura deum inteiro (long int) decimal
| `%u` | Leitura de um inteiro sem sinal
| `%c` | Leitura de um único caractere
| `%s` | Leitura de uma cadeira de caracteres
| `%f` | Leitura de um número de ponto-flutuante
|======================
////
TODO - REV.27: Sugestão de melhoria para análise pelo autor
Precisamos adicionar exercícios, tanto comentados como propostos.
////
=== Recapitulando
Neste capítulo você pôde conhecer um pouco mais sobre o funcionamento dos computadores, mais especificamente sobre a forma com a qual eles decodificam as instruções que lhes são passadas. Vimos, portanto, que as linguagens de programação podem ser classificadas em linguagens de alto nível ou baixo nível, de acordo com sua proximidade em relação à linguagem que os computadores podem compreender: o código de máquina.
Como programar em linguagem de baixo nível é uma tarefa árdua, foram criadas as linguagens de alto nível para facilitar a vida dos programadores. Desse modo, surgiu a necessidade de um software que fosse capaz de realizar a tradução de programas escritos em linguagem de alto nível para programas equivalente em código de máquina. Esses softwares são os tradutores e interpretadores.
Aprendemos que a maneira de pensar de um programador na resolução de um problema está relacionada com um paradigma de programação. Se um programador utilizar a linguagem C, por exemplo, ele vai raciocinar segundo os paradigmas imperativo e estruturado.
Falando em linguagem C, conhecemos a estrutura de um programa escrito nessa linguagem, seus tipos primitivos, como podem ser elaborados nomes para as variáveis e como estas podem ser declaradas. Enfim, você deu os primeiros passos para a elaboração do seu primeiro programa.
No próximo capítulo você vai estudar as estruturas de controle em C. Elas simplesmente são as instruções mais importantes para o desenvolvimento da lógica de programação. Por isso, estude atentamente o próximo capítulo e tente elaborar o maior número de programas possível para consolidar o aprendizado.
=== Exercícios Propostos
1. Diferencie linguagem de programação de alto nível de linguagem de programação de baixo nível. Dê exemplos de linguagens que se enquadram em ambos os tipos.
2. Qual é o principal objetivo dos tradutores e interpretadores?
3. Defina montador e compilador, enfatizando suas diferenças.
4. Explique como funcionam os interpretadores.
5. Quais as vantagens da compilação em relação à interpretação?
6. O que é um paradigma de programação? Cite exemplos.
7. Quais dos seguintes itens não podem ser utilizados como identificadores na linguagem C? Explique por quê?
+
--
a. `3x`
b. `Inflação`
c. `COMPUTACAO`
d. `nota_1`
e. `nota 2`
f. `prof.`
g. `$4`
h. `RG`
i. `main`
j. `return`
--
8. Descreva os tipos primitivos de C, destacando os valores que eles podem armazenar.
9. Qual a diferença entre os operadores prefixo e sufixo de incremento?
10. Qual é valor de `(x1 + x2)` após a execução dos grupos de comandos abaixo:
+
--
.. `y = 6;`
.. `z = 8;`
.. `c = 2;`
.. `x1 = ((y * z) – z)/c;`
.. `x2 = (z / 2)/ y++;`
--
11. Considerando as atribuições `x = 20` e `y = 2`, calcule o resultado de cada uma das expressões abaixo:
+
--
a. `(x-- + x * (x % y))`
b. `(x-- + x * (x % y))`
c. `(x-- + x * (x % 3))`
d. `(--x + x * (x % 3))`
e. `(--x + x * (x % x))`
--
12. Faça um programa em C que solicite ao usuário que digite o ano de seu nascimento, armazene o valor digitado em uma variável e em seguida imprima na saída padrão a sua idade.
// Sempre termine os arquivos com uma linha em branco.
introducao-a-programacao-livro-1.0.0/livro/capitulos/novo-capitulo.asc
== Novo capítulo
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
*
objetivo 1
* objetivo 2
* objetivo N
____________________
Neste lugar você deve apresentar o conteúdo em forma de diálogo.
NOTE: Para começar a escrever um novo capítulo, copie este arquivo e
salve com outro nome (não utilize espaço no nome do arquivo). Em seguida,
atualize o arquivo *livro.asc* para incluir o novo arquivo criado.
Consulte o manual.
=== Minha primeira seção
Texto da sua seção.
==== Exemplo de subseção
Texto da subseção.
=== Minha segunda seção
Texto da sua seção.
=== Recapitulando
Revisão do que foi aprendido.
Reserve o último parágrafo para realizar uma *ponte* para o próximo capítulo.
=== Atividades
. Texto da atividade.
. Texto da atividade.
. Texto da atividade.
CAUTION: *Sempre termine os arquivos com uma linha em branco*, caso
contrário você poderá encontrar erros inesperados.
introducao-a-programacao-livro-1.0.0/livro/capitulos/prefacio.asc
[[prefacio]]
[preface]
== Prefácio
Este livro é destinado a alunos de cursos como Ciência da Computação, Sistemas de Informação, Engenharia da Computação e, sobretudo, Licenciatura em Computação. Ele tem o objetivo de apresentar os principais conceitos da programação de computadores, de modo que sua utilização é mais adequado a disciplinas introdutórias como a de Introdução à Programação. De forma alguma o presente livro tem a pretensão de cobrir todos os assuntos relacionados à área de programação. Sua principal finalidade é a de servir como guia para os alunos que estão dando os primeiros passos nessa área que é tão importante para a ciência da computação.
A disciplina de Introdução à Programação é a primeira de uma sequência de disciplinas que têm o objetivo de tornar os alunos capazes dominar os fundamentos e as técnicas relacionadas à programação de computadores. Durante o curso de Licenciatura em Computação, especificamente, os alunos terão a chance de conhecer em detalhes algumas das linguagens de programação mais utilizadas atualmente e estarão habilitados a ministrar cursos de programação para diversos públicos.
Mais importante do que conhecer as peculiaridades das linguagens de programação é aprender como funciona a lógica aplicada na elaboração de soluções desenvolvidas a partir de algoritmos computacionais. Sendo assim, o principal objetivo deste livro é ensiná-los a resolver problemas com base nos comandos e mecanismos presentes nas linguagens de programação, desenvolvendo assim o que chamamos de lógica de programação. No decorrer do livro, o leitor aprenderá gradativamente a dar instruções ao computador através de programas que ele próprio será capaz de criar. Para isso, conhecerá a linguagem de programação C, uma das mais utilizadas e mais importantes linguagens na área de Ciência da Computação.
No Capítulo 1 será abordado o conceito de algoritmo, e suas principais formas de representação serão apresentadas. No Capítulo 2, descrevemos todo o processo de tradução de um programa escrito em linguagem de alto nível para um programa equivalente em código de máquina, isto é, a linguagem que os computadores conhecem. Além disso, será apresentado ao leitor a estrutura de um programa na linguagem C, o que o habilitará o leitor a desenvolver seu primeiro programa. O Capítulo 3 consiste no conteúdo do livro que mais desenvolve a lógica de programação. É nele que se encontram as principais instruções de uma linguagem de programação: as estruturas de controle. No Capítulo 4 será apresentado o conceito de arranjos e, por fim, no Capítulo 5, explicaremos como programas complexos podem ser divididos em programas menores, mais fáceis de serem solucionados, através da utilização das funções.
Recomendamos ao aluno, iniciante na programação de computadores, que não se limite à leitura e ao conteúdo deste livro. Pesquise na internet outros materiais, leia outros livros e faça todos os exercícios propostos. Programação, assim como matemática, requer muito exercício, muita prática. Como mencionado anteriormente, a programação de computadores é uma das subáreas mais importantes da carreira que você escolheu seguir. Boa parte das disciplinas do seu curso depende do conhecimento adquirido em Introdução à Programação. Portanto, dedique o máximo que puder ao aprendizado de uma área que vai permiti-lo transformar sonhos em realidade.
=== Público alvo
O público alvo desse livro são os alunos de Licenciatura em Computação, na
modalidade a distância footnote::[Embora ele tenha sido feito para atender aos alunos
da Universidade Federal da Paraíba, o seu uso não se restringe a esta
universidade, podendo ser adotado por outras universidades do sistema UAB.].
Ele foi concebido para ser utilizado numa disciplina de 'Introdução à Programação',
no primeiro semestre do curso.
[[como_estudar]]
=== Como você deve estudar cada capítulo
* Leia a visão geral do capítulo
* Estude os conteúdos das seções
* Realize as atividades no final do capítulo
* Verifique se você atingiu os objetivos do capítulo
.Na sala de aula do curso
* Tire dúvidas e discuta sobre as atividades do livro com outros integrantes do curso
* Leia materiais complementares eventualmente disponibilizados
* Realize as atividades propostas pelo professor da disciplina
[[caixas_de_dialogo]]
=== Caixas de diálogo
Nesta seção apresentamos as caixas de diálogo que poderão ser utilizadas durante o texto.
Confira os significados delas.
[NOTE]
====
Esta caixa é utilizada para realizar alguma reflexão.
====
[TIP]
====
Esta caixa é utilizada quando desejamos remeter a materiais complementares.
====
[IMPORTANT]
====
Esta caixa é utilizada para chamar atenção sobre algo importante.
====
[CAUTION]
====
Esta caixa é utilizada para alertar sobre algo que exige cautela.
====
[WARNING]
====
Esta caixa é utilizada para alertar sobre algo potencialmente perigoso.
====
Os significados das caixas são apenas uma referência, podendo ser adaptados
conforme as intenções dos autores.
// TODO: inclusão de vídeos
=== Compreendendo as referências
As referências são apresentadas conforme o elemento que está sendo referenciado:
Referências a capítulos:: <<prefacio>>
Referências a seções:: <<como_estudar>>, <<caixas_de_dialogo>>.
Referências a imagens e tabelas:: <<fig_issue_contribuicao>> <<tab_metodos_contribuicao_livro>>
NOTE: Na *versão impressa*, o número que aparece entre chaves ``[ ]'' corresponde
ao número da página onde está o conteúdo referenciado. Nas *versões digitais*
do livro você poderá clicar no link da referência.
=== Códigos e comandos
Os códigos ou comandos são apresentados com a seguinte formação:
cc -S main.c teste.c
No exemplo a seguir, temos outra apresentação de código fonte. Desta vez de um
arquivo `main.c`, que se encontra dentro do diretório `code/estruturas-de-controle`.
O diretório `estruturas-de-controle` faz referência ao capítulo onde o código será apresentado.
.Código fonte
{gitrepo}/blob/master/livro/capitulos/code/estruturas-de-controle/for_cont.c[code/estruturas-de-controle/for_cont.c]
[source, c]
.Exemplo do comando for
----
include::code/estruturas-de-controle/for_cont.c[]
----
[[codigo_fonte]]
=== Baixando os códigos fontes
Existem duas formas de acessar os códigos fontes contidos neste livro.
Acesso on-line individual:: Você pode acessar individualmente os arquivos deste
livro pelo endereço: {gitrepo}/tree/master/livro/capitulos/code.
Baixando todos os códigos::
Você também pode baixar o código fonte do livro
inteiro, que contém todos os códigos mencionados no livro. Existem duas formas
de baixar o código inteiro, através de um arquivo zip ou clonando o repositório.
Arquivo zip;; {gitrepo}/archive/master.zip. Depois de baixar o arquivo, descompacte-o.
Clonando o repositório;; Use o comando: git clone {gitrepo}
NOTE: Independente do método utilizado para acessar os arquivos, os códigos
fontes estão organizados por capítulos no diretório `livro/capitulos/code`.
WARNING: Os códigos acessados por estes métodos são referentes à versão mais
nova do livro (em produção). É possível que eles sejam diferentes da versão
do livro que você esteja lendo.
=== Contribuindo com o livro
Você pode contribuir com a atualização e correção deste livro. A tabela a seguir
resume os métodos de contribuições disponíveis:
[[tab_metodos_contribuicao_livro]]
.Métodos para contribuição do livro
[width="100%",cols="1.^,2.^a,4.^",frame="topbot",options="header"]
|====
|Método de contribuição | Habilidades necessárias | Descrição
| Issue track
|
- Inscrição no site do github
- Preenchimento de um formulário
| Consiste em acessar o repositório do livro e submeter um erro, uma sugestão
ou uma crítica -- através da criação de um 'Issue'. Quando providências forem
tomadas você será notificado disso.
| Submissão de correção
|
- Realizar fork de projetos
- Atualizar texto do livro
- Realizar PullRequest
| Consiste em acessar os arquivos fontes do livro, realizar a correção desejada
e submetê-la para avaliação. Este processo é o mesmo utilizado na produção de
softwares livres.
|====
IMPORTANT: Quando for enviar sua contribuição lembre-se de informar qual a versão
e página do livro que está se referindo.
Contribuição através do Issue track:: Para contribuir com um erro, sugestão ou
crítica através de um envio de uma mensagem acesse: {gitrepo}/issues/new
+
[[fig_issue_contribuicao]]
.Exemplo de contribuição através do 'Issue track'
image::images/prefacio/issue.png[scaledwidth="70%"]
=== Baixando a edição mais nova deste livro
Nós estamos constantemente atualizando o nosso material didático.
Todas as versões deste livro encontram-se disponíveis para download.
TIP: Acesse {gitrepo}/releases para baixar a versão mais nova deste livro.
// Sempre manter uma linha em branco no final
introducao-a-programacao-livro-1.0.0/livro/capitulos/registros.asc
== Registros
.Objetivos do capítulo
____________________
Ao final deste capítulo você deverá ser capaz de:
* Criar registros em C
* Analisar problemas e reconhecer os campos necessários para utilizar nos
registros
* Reconhecer campos identificadores de registros
* Criar e manipular listas de registros
____________________
Neste capítulos nós iremos estudar sobre Registros. Vamos conhecer a sua
utilidade e como declará-los em C. Depois vamos analisar diversas situações
para aprender como é o processo de criação de um registro.
Serão apresentados alguns programas demonstrando a utilização dos registros e
por fim, vamos aprender como compor registros a partir de outros registros.
Mas, o que são Registros?
[[sec_registro_definicao]]
=== Definição de registro
(((Registro)))
Definição de Registro::
Um Registro é um *tipo de dado* criado pelo usuário, através da *composição*
de outros tipos de dados.
Nós utilizamos registros quando desejamos criar um tipo de dado para reunir
informações sobre o que desejamos representar. No registro, as informações são
organizadas em *campos*.
Uma analogia de registro pode ser vista quando preenchemos um formulário. Na <<tab_formulario_cliente>>
nós temos um exemplo de formulário para cadastrar *Clientes*. Os *campos* do formulário
são preenchidos com os dados do cliente que nos interessa registrar.
Sabendo as informações que desejamos registrar sobre um Cliente, nós podemos
esquematizar um registro, informando os tipos de dado de cada campo, conforme
descrito na <<tab_registro_cliente>>.
[[tab_formulario_cliente]]
.Formulário para cadastro de Cliente
|====
2+| Nome:
| Data de Nascimento: | Telefone para contato:
| CPF: | RG:
|====
[[tab_registro_cliente]]
.Representação de um registro Cliente
[width="80%",cols="1,2,1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.5+^.^s| Cliente
| Nome | Textual
| Data de Nascimento | Numérico
| Telefone para contato | Textual
| CPF ★ | Numérico
| RG | Numérico
|====
[TIP]
.Relembrando
====
Em nossos programas nós utilizamos *variávies* para manter as informações que
desejamos manipular.
No momento da criação de uma variável precisamos especificar
o *tipo de dado* que desejamos que ela mantenha, através da *declaração da
variável*. Vamos relembrar como declaramos variáveis:
.Em pseudocódigo
----
DECLARE nome_da_variavel: TEXTUAL
DECLARE var1,var2,var3: NUMÉRICO
----
.Em C
[source, c]
----
char[] nome_da_variavel;
double var1,var2,var3;
----
Quando especificamos mais de uma variável separadas por vírgula, assumimos que
todas elas possuem o mesmo tipo.
====
Na próxima seção, veremos como é a sintaxe para criação de registros, em
pseudocódigo e em C.
[IMPORTANT]
====
Embora, na prática, o uso de registro geralmente esteja associado a ((persistência))
de dados, sempre que mencionarmos *cadastrar* neste capítulo, estamos nos
referindo a manter os dados *em memória* para consulta posterior.
Em um sistema real, geralmente existe alguma forma de persistência dos dados
através de arquivos ou banco de dados -- caso contrário, os dados seriam perdidos.
====
=== Sintaxe para criação de registros
(((Tipo de dado))) (((Registro, sintaxe)))
Agora que temos o entendimento que um registro é um *tipo de dado*, vamos conhecer
a sintaxe para especificá-lo:
.Sintaxe em pseudocódigo para criar registro
[source, c]
----
REGISTRO nome_do_registro
// Declarações dos campos
REGISTRO_FIM
----
.Sintaxe em C para criar registro
[source, c]
----
typedef struct {
// Declarações dos campos
} nome_do_registro;
----
Quando criamos um novo tipo de dado precisamos nomeá-lo, para
podermos referenciá-lo mais tarde. Nestas notações, `nome_do_registro` é
o nome do tipo de dado registro que será criado.
As `Declarações dos campos` definem os campos que *compõem* o registro.
Esta *composição* ficará evidente nas próximas seções, onde iremos criar
e manipular vários registros.
Após a definição do novo tipo de dado registro, uma declaração de variável
com este tipo é realizada da forma usual:
.Declaração de variável do tipo criado em pseudocódigo
----
DECLARE variavel_nome: nome_do_registro
----
.Declaração de variável do tipo criado em C
----
nome_do_registro variavel_nome;
----
=== Identificadores de registros
(((Campo Identificador)))
Antes de começarmos a especificar os registros, vamos primeiro entender a
necessidade de identificar unicamente um 'registro'.
[IMPORTANT]
====
A palavra *registro* pode ser empregada em dois contextos diferentes.
Tipo de dado:: É o tipo de dado, conforme apresentado na definição de Registro.
Instância do registro:: Utilizando a analogia do formulário, equivaleria às
fichas dos clientes. Cada ficha preenchida equivale a uma instância ou um
'registro' daquele tipo.
Por conveniência, sempre que utilizarmos a palavra 'registro' para indicar
instância do tipo, ela será grafada em 'itálico'.
====
Identificadores de 'registros' são campos nos registros que os *identificam e
diferenciam* um 'registro' de qualquer outro.
No registro indicado na tabela <<tab_registro_cliente>>, como podemos
*diferenciar* um cliente cadastrado de outro? Qual campo *identifica* um cliente?
Seria o nome? Data de Nascimento? Telefone? CPF ou RG? Neste caso, preferimos
utilizar o CPF, pois sabemos que duas pessoas diferentes não podem possuir o
mesmo número de CPF.
Em nosso livro, os campos *identificadores* estão marcados com ★.
[NOTE]
====
Os identificadores costumam ser do tipo *inteiro*, pois a comparação
de inteiros é mais rápida do que a comparação textual.
====
Na próxima seção, faremos análises em algumas situações, caso você não tenha
compreendido o que são *campos identificadores* terá outra oportunidade.
[[sec_registro_analise]]
=== Análise para criação de Registros
Nesta seção mostramos o processo de criação de um Registro em diversas situações
diferentes.
Em cada situação apresentada faremos a seguinte *análise*:
// TODO glossário análise?
* Determinar o *tipo de registro* que vamos criar;
* Especificar quais serão os *campos* do registro, com os seus respectivos *tipos*;
* Indicar qual o *campo identificador* (★), caso exista;
* Apresentar o *código de criação* do Registro em Pseudocódigo e em C.
[[situacao_aluno]]
==== Situação do cálculo das notas de um aluno
Em uma disciplina onde os alunos possuem *duas notas*, e precisamos registrar e
*calcular as médias* de todos eles, como seria um registro para representar
esta situação?
Nome do Registro:: Aluno
Campos:: Obviamente vamos precisar guardar *duas notas* para cada aluno. 'Vamos
precisar guardar a média também?' Não, uma vez que temos as duas notas
registradas, sempre que desejarmos consultar a média poderemos calculá-las.
'O nome do aluno seria uma informação útil?' Sem dúvidas! Será importante
registrar o *nome* do aluno pois poderíamos imprimir uma lista com os nomes, notas
e médias de cada aluno. 'A matrícula do aluno é importante também?' Nós poderíamos
suprimir a matrícula do aluno, mas qual seria a consequência disso? Por exemplo,
na lista de notas poderia conter apenas os nomes, notas e médias. Mas o que
aconteceria se tivéssemos dois alunos com o mesmo nome? Nós precisamos de um
informação extra para *identificar* e diferenciar um aluno do outro.
Com este intuito, vamos optar por registrar a *matrícula* também. 'O nome
da disciplina é importante?' Neste caso não, pois estamos nos limitando aos alunos
e suas notas.
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.4+^.^s| Aluno
| matricula ★ | Numérico
| nome | Textual
| nota1 | Numérico
| nota2 | Numérico
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_aluno_pseudo.txt[]
----
[[ex_reg_aluno]]
[source, c]
.Registro em C: code/registros/reg_aluno.c
----
include::code/registros/reg_aluno.c[]
----
[NOTE]
Até agora você teve dificuldade para entender esta análise? Você compreendeu
a necessidade da utilização de `matrícula` como campo identificador? Concordou com
os tipos de dados utilizados para cada variável?
[[situacao_pessoa_imc]]
==== Situação do cálculo e consulta do IMC de uma pessoa
Nesta situação desejamos criar um sistema para *cadastrar* pessoas e em seguida
*consultar* o IMC delas.
Nome do Registro:: Pessoa
Campos:: Para o cálculo do IMC são necessárias duas informações: a *altura*
e o *peso*. Novamente, o *nome* da pessoa é uma informação relevante, pois
vamos imprimir o IMC calculado junto com o nome. 'Mas como realizar a consulta?
Após o cadastro realizado de algumas pessoas, qual o parâmetro de busca que
iremos utilizar para encontrar a pessoa certa?' Poderíamos utilizar o nome
completo da pessoa para encontrá-la. Mas digitar o nome todo é enfadonho.
Poderíamos utilizar apenas o primeiro nome para busca, mas então teríamos
que apresentar um lista com todas as pessoas com aquele primeiro nome e selecionar
a pessoa correta entre elas.footnote:[A opção de utilizar o primeiro nome iria complicar o algorítmo da busca.]
Se cadastrarmos o *CPF* da pessoa poderíamos consultá-la
mais tarde informando apenas ele, simplificando a busca. Por último, como
algumas tabelas do IMC apresentam os dados categorizados por *sexo*, vamos
registrá-lo também.
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.5+^.^s| Pessoa
| nome | Textual
| peso | Numérico
| altura | Numérico
| cpf ★| Numérico
| sexo | Textual
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_pessoa_pseudo.txt[]
----
[[ex_reg_pessoa]]
[source, c]
.Registro em C: code/registros/reg_pessoa.c
----
include::code/registros/reg_pessoa.c[]
----
[NOTE]
====
Mais uma vez, embora nosso problema não tenha indicado os campos que necessita,
fomos capazes de deduzir alguns. Aqui não há certo ou errado, cada um pode
realizar sua análise e chegar a resultados diferentes.
Você concorda com os tipos de dados apresentados aqui? Não achou estranho `cpf`
ser do tipo `long long`? Você declararia `sexo` com outro tipo, diferente
de *char*?
cpf:: Declaramos ele como `long long` pois os tipos *long* e *int* não
armazenam números na ordem de *11* dígitos.
sexo:: Optamos por utilizar o tipo *char* para simplificar comparações, caso
sejam necessárias. Poderíamos declará-lo do tipo *int*, fazendo uma correspondência
de valores: 1=Feminino e 2=Masculino. Ou ainda poderíamos utilizar *char[]* e
registrar o texto completo: `Feminino` ou `Masculino`.
====
[[situacao_ponto_matematica]]
==== Situação sobre manipulação de pontos no plano cartesiano
Nesta situação desejamos criar um sistema matemático para manipular pontos no
plano cartesiano.
Nome do Registro:: Ponto
Campos:: Para registrar um ponto no plano cartesiano basta informar os
valores de suas coordenadas (x,y).
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.2+^.^s| Ponto
| x★ | Numérico
| y★ | Numérico
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_ponto_matematica_pseudo.txt[]
----
[[ex_reg_ponto]]
[source, c]
.Registro em C: code/registros/reg_ponto.c
----
include::code/registros/reg_ponto.c[]
----
[NOTE]
====
Neste registro nós temos uma novidade: estamos utilizando dois campos como
*identificadores* simultaneamente. As vezes um único campo não é
suficiente para identificar um 'registro'.
Neste caso, fica evidente que dois pontos são iguais se, e somente se, eles
possuírem os mesmo valores para o par (`x`,`y`).
E em relação ao tipo do dado? Você teria utilizado outro tipo, diferente
de `int`, como `float` ou `double`? Mais uma vez, aqui não há certo ou errado,
nós optamos por `int` apenas por ser mais simples fornecer coordenadas em
inteiro.
====
[[situacao_produto_supermercado]]
==== Situação sobre cadastro de produtos no supermercado
Nesta situação desejamos criar um sistema, para um supermercado, que cadastre
produtos e seus preços.
Nome do Registro:: Produto
Campos:: Para registrar um produto vamos precisar do seu *nome* e o seu
*preço*. 'Mas como identificar um produto cadastrado?' Quando vamos no supermercado
e compramos alguma mercadoria no peso, o caixa do supermercado precisa fornecer
um código para cadastrar o produto pesado.
Geralmente ele utiliza uma tabela, onde há o nome do produto e o seu código. Para a nossa
aplicação vamos utilizar este mesmo *código* para identificar unicamente cada produto.
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.3+^.^s| Produto
| nome | Textual
| preco | Numérico
| codigo ★| Numérico
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_produto_supermercado_pseudo.txt[]
----
[[ex_reg_produto]]
[source, c]
.Registro em C: code/registros/reg_protudo.c
----
include::code/registros/reg_produto.c[]
----
[NOTE]
====
Neste registro tivemos contato com um provável campo *identificador* universal,
o `codigo`. Geralmente, quando nos deparamos com um campo código, ele será
utilizado como o identificador.footnote:[A não ser quando o
código for utilizado para designar uma senha.]
====
[[situacao_conta_bancaria]]
==== Situação sobre gerenciamento de contas bancárias
Nesta situação desejamos criar um sistema bancário para gerenciar clientes e
suas contas bancárias.
Nomes dos Registros:: Cliente e Conta.
Campos:: O *nome* do cliente é uma informação relevante. O *CPF* poderá
ser utilizado para *diferenciar* clientes com o
mesmo nome. 'Como identificar a conta do cliente?' Cada conta poderia ter um
*número de conta único*, que serviria para identificar a conta do cliente.
Cada conta terá um *saldo*, que será gerenciada pelo sistema.
Como cada cliente
pode possuir mais de uma conta bancária, junto com a conta deveremos registrar
qual cliente é o dono dela. Vamos utilizar o *CPF do cliente na conta* para
identificar o seu *dono*.
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.3+^.^s| Conta
| numero_da_conta ★| Numérico
| saldo | Numérico
| cpf_do_cliente | Numérico
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_conta_pseudo.txt[]
----
[[ex_reg_conta]]
[source, c]
.Registro em C: code/registros/reg_conta.c
----
include::code/registros/reg_conta.c[]
----
[width="80%",cols="^1s,^1m,^1m",frame="topbot",options="header"]
|====
^| Novo tipo ^| Campo ^| Tipo do campo
.2+^.^s| Cliente
| nome | Textual
| cpf ★| Numérico
|====
.Registro em Pseudocódigo
----
include::code/registros/reg_cliente_pseudo.txt[]
----
[[ex_reg_cliente]]
[source, c]
.Registro em C: code/registros/reg_cliente.c
----
include::code/registros/reg_cliente.c[]
----
[NOTE]
====
Nesta situação temos outras novidades: a criação de *dois* Registros e utilização
de um campo para registrar o ((*relacionamento*)) entre os dois
'registros'.footnote:[Relacionamento entre registros é um assunto que está
fora do escopo de uma disciplina de Introdução à Programação, você estudará
este tópico numa disciplina de Banco de Dados.]
Percebam que `cpf` é o campo *identificador* de Cliente. Para identificar
que uma conta é de um determinado cliente, utilizamos o campo identificador
de cliente na conta.
Esta é uma estratégia muito importante para especificar *relacionamento*
entre registros, certifique-se que compreendeu-a antes de prosseguir.
====
=== Exemplos de utilização dos Registros
Nesta seção veremos alguns exemplos que demonstram a utilização de registros.
Nestes exemplos você irá aprender:
. Como atribuir e acessar valores aos campos do registro;
. Como atribuir valores de texto aos campos do registro;
. Como ler valores da entrada e atribui-los aos campos;
. Como declarar um arranjo de registros;
. Como acessar um campo num arranjo de registros.
==== Aluno
Exemplo de utilização do registro Aluno.
[source, c]
.code/registros/reg_aluno_exemplo.c
----
include::code/registros/reg_aluno_exemplo.c[]
----
<1> Como atribuir valores aos campos do registro.
<2> Como atribuir valores de texto aos campos do registro. Você já estudou a
função `strcpy` antes.
<3> Como acessar valores atribuídos aos campos do registro.
.Resultado ao simular a execução do programa
----
include::code/registros/reg_aluno_exemplo_simulacao.txt[]
----
==== Produto
Exemplo de utilização do registro Produto.
[source, c]
.code/registros/reg_produto_exemplo.c
----
include::code/registros/reg_produto_exemplo.c[]
----
<1> Como ler da entrada os valores e atribuí-los aos campos. Consulte a
documentação de `scanf` (ou `fscanf`) para conhecer a sintaxe de leitura e
conversão dos dados. Percebam a ausência de `&` antes do campo `nome`.
.Resultado ao simular a execução do programa
----
include::code/registros/reg_produto_exemplo_simulacao.txt[]
----
[WARNING]
====
Percebam que quando atribuímos um valor de texto aos campos do tipo *char[]*,
nós *suprimimos* o `&`. Isto correu com o campo `aluno.nome` em `strncpy` e `p.nome`
no `scanf`.
====
==== Pontos
Exemplo de utilização do registro Ponto com Arranjo.
[source, c]
.code/registros/reg_ponto_exemplo.c
----
include::code/registros/reg_ponto_exemplo.c[]
----
<1> Declaração de *constante* que definirá o tamanho do arranjo.
<2> Como declarar um arranjo de registros do tipo Ponto, com o tamanho definido
pela constante `QUANTIDADE_DE_PONTOS`.
<3> Como acessar um campo em arranjo de registros. Cada posição, do arranjo
contém um registro. Você pode acessar as posições do arranjo com a mesma
sintaxe: `[índice]`.
.Resultado ao simular a execução do programa
----
include::code/registros/reg_ponto_exemplo_simulacao.txt[]
----
////
include::code/registros/reg_aluno_exemplo.txt[]
include::code/registros/reg_aluno_exemplo.c[]
include::code/registros/reg_pessoa_exemplo.txt[]
include::code/registros/reg_pessoa_exemplo.c[]
include::code/registros/reg_ponto_matematica_exemplo.txt[]
include::code/registros/reg_ponto_matematica_exemplo.c[]
include::code/registros/reg_produto_supermercado_exemplo.txt[]
include::code/registros/reg_produto_supermercado_exemplo.c[]
include::code/registros/reg_cliente_exemplo.txt[]
include::code/registros/reg_cliente_exemplo.c[]
include::code/registros/reg_conta_exemplo.txt[]
include::code/registros/reg_conta_exemplo.c[]
////
[[sec_registro_programas]]
=== Exercícios resolvidos
Nesta seção teremos a especificação de diversos problemas. Para cada um deles
iremos escrever um pseudocódigo que resolva o problema descrito, utilizando
o recurso de Registros. Em seguida, iremos implementar um programa em C.
==== Programa do cálculo de médias de alunos
Escrever um programa que cadastre o nome, a matrícula e duas notas de vários alunos.
Em seguida imprima a matrícula, o nome e a média de cada um deles.
.Pseudocódigo do programa
----
include::code/registros/reg_aluno_pseudo.txt[]
include::code/registros/calculo_das_medias_pseudo.txt[]
----
<1> Imprime a média calculada.
.Programa em C: code/registros/calculo_das_medias.c
[source,c]
----
include::code/registros/calculo_das_medias.c[]
----
.Resultado ao simular a execução do programa
----
include::code/registros/calculo_das_medias_simulacao.txt[]
----
==== Problema do cálculo e consulta do IMC de uma pessoa
Escrever um programa que cadastre o nome, a altura, o peso, o cpf e sexo
de algumas pessoas. Com os dados cadastrados, em seguida localizar uma pessoa
através do seu CPF e imprimir o seu IMC.
.Pseudocódigo do programa
----
include::code/registros/reg_pessoa_pseudo.txt[]
include::code/registros/imc_calculo_pseudo.txt[]
----
<1> O ler o campo *identificador* de Pessoa (CPF).
<2> Pesquisa pelo registro Pessoa identificado pelo CPF lido.
.Programa em C: code/registros/imc_calculo.c
[source,c]
----
include::code/registros/imc_calculo.c[]
----
<1> O ler o campo *identificador* de Pessoa (`cpf`).
<2> Pesquisa pelo registro Pessoa identificado pelo CPF lido.
.Resultado ao simular a execução do programa
----
include::code/registros/imc_calculo_simulacao.txt[]
----
==== Problema de pontos no plano cartesiano
Escrever um programa que leia 5 pontos. Em seguida imprima qual o ponto mais
próximo do primeiro ponto lido.
.Pseudocódigo do programa
----
include::code/registros/reg_ponto_matematica_pseudo.txt[]
include::code/registros/ponto_proximo_pseudo.txt[]
----
<1> `MAIOR_INTEIRO` representa o maior número inteiro que podemos armazenar numa
variável. Geralmente atribuímos *o maior* inteiro quando procuramos por
*um menor* valor. No código, comparamos `menor_distancia_ao_quadrado` com
`distancia_ao_quadrado` e salvamos o *menor* deles. Se executarmos isso
sucessivamente, ao final, `menor_distancia_ao_quadrado` conterá o *menor* valor
comparado.footnote:[Caso tivéssemos inicializado a variável `menor_distancia_ao_quadrado`
com `0`, ao compará-lo com outro número, ele seria o *menor*,
impossibilitando encontrar a *menor* distância.]
<2> Esta variável irá guardar a posição do ponto mais próximo. Ela é atualizada,
sempre que encontramos outro ponto com menor distância.
<3> Calculo para encontrar a distância entre dois pontos. Na realizadade, a
distância entre os dois pontos seria a raiz de `distancia_ao_quadrado`. Mas
não há diferença em comparar a distância ao quadrado. Sabemos, por exemplo,
que a *raiz* de `x` é *menor do que a raiz* de `y` se `x` for *menor* do
que `y`.
.Programa em C: code/registros/ponto_proximo.c
[source,c]
----
include::code/registros/ponto_proximo.c[]
----
.Resultado ao simular a execução do programa
----
include::code/registros/ponto_proximo_simulacao.txt[]
----
==== Problema sobre cadastro de produtos no supermercado
Escrever um programa que cadastre vários produtos. Em seguida, imprima uma lista
com o código e nome da cada produto. Por último, consulte o preço de um produto
através de seu código.
.Pseudocódigo do programa
----
include::code/registros/reg_produto_supermercado_pseudo.txt[]
include::code/registros/prob_supermercado_pseudo.txt[]
----
.Programa em C: code/registros/supermercado.c
[source,c]
----
include::code/registros/supermercado.c[]
----
.Resultado ao simular a execução do programa
----
include::code/registros/supermercado_simulacao.txt[]
----
==== Problema sobre gerenciamento de contas bancárias
Escreva um programa que simule contas bancárias, com as seguintes especificações:
* Ao iniciar o programa vamos criar contas bancárias para três clientes.
** Cada conta terá o nome e o CPF do cliente associado a ela.
** No ato da criação da conta o cliente precisará fazer um depósito inicial.
* Após as contas serem criadas, o sistema deverá possibilitar realizações de
saques ou depósitos nas contas.
** Sempre que uma operação de saque ou depósito seja realizada, o sistema deverá
imprimir o nome do titular e o saldo final da conta.
.Pseudocódigo do programa
----
include::code/registros/reg_conta_pseudo.txt[]
include::code/registros/reg_cliente_pseudo.txt[]
include::code/registros/prob_conta_bancaria_pseudo.txt[]
----
.Programa em C: code/registros/conta_bancaria.c
[source,c]
----
include::code/registros/conta_bancaria.c[]
----
.Resultado ao simular a execução do programa
----
include::code/registros/conta_bancaria_simulacao.txt[]
----
Após todos estes programas, agora vamos ver uma técnica que não utilizada ainda,
a inicialização de 'registro' com valores pré-definidos.
[[sec_registro_inicializacao]]
=== Inicializando registros
Quando declaramos uma variável do tipo registro, também podemos realizar uma
atribuição aos valores dos seus campos. O programa a seguir ilustra
esta atribuição.
WARNING: Para a atribuição poder ocorrer, os campos precisam ser inseridos
*na ordem que foram declarados* no tipo do registro.
.Programa em C: code/registros/reg_atribuicao.c
[source,c]
----
include::code/registros/reg_atribuicao.c[]
----
<1> Seguindo a ordem da declaração do registro, `matricula` recebe `15`, `nome`
recebe ``Virgulino da Silva'', `nota1` recebe `9` e `nota2` recebe `10`.
<2> Seguindo a ordem da declaração do registro, `nome` recebe ``Maria Bonita''
e `cpf` recebe `72779162201`.
.Resultado ao simular a execução do programa
----
include::code/registros/reg_atribuicao_simulacao.txt[]
----
NOTE: O *Registro* é um tipo de dado composto por campos com outros tipos. 'Mas
será que é possível declarar um campo do tipo Registro?' Veremos a resposta
na próxima seção.
[[sec_registro_composicao]]
=== Composição de Registros
Na definição de registros (<<sec_registro_definicao>>), vimos que um Registro é
criado pela *composição* de outros tipos de dado. Agora veremos que podemos
compor um Registro utilizando outros Registros *previamente definidos*.
CAUTION: Ao realizar composição de registros, a definição do registro que
será utilizado na composição precisa *aparecer antes* (no código fonte)
da definição do novo registro. Caso contrário, você poderá ter erros de
compilação.
==== Triângulo
Nesta seção vamos definir um Registro triângulo que contém *3* campos do
tipo `Ponto`.
.Composição de registro em Pseudocódigo
----
include::code/registros/reg_triangulo_pseudo.txt[]
----
[source, c]
.Composição de registro em C: code/registros/reg_triangulo.c[]
----
include::code/registros/reg_triangulo.c[]
----
[NOTE]
====
Neste exemplo, o registro do tipo Triângulo foi criado com campos
do tipo `Ponto`, os três campos foram: `p1`, `p2` e `p3`. Para acessar a
coordenada `x` do primeiro ponto do Triângulo `t`, chamamos: `t.p1.x`.
Foram dispostas duas atribuições de coordenadas numa mesma linha apenas para
ficar melhor visualmente, não há necessidade de serem assim.
====
==== Informação Pessoal
Nesta seção vamos definir um Registro `InformacaoPessoal` e utilizá-lo no Registro
`Aluno` e `Cliente`.
.Composição de registro em Pseudocódigo
----
include::code/registros/reg_infopessoal_pseudo.txt[]
----
[source, c]
.Composição de registro em C: code/registros/reg_infopessoal.c[]
----
include::code/registros/reg_infopessoal.c[]
----
[NOTE]
====
A composição de Registro utiliza a sintaxe usual de declaração de
campos. Uma vez que definimos um novo tipo, basta utilizar o tipo na declaração
normal do campo.
O acesso aos campos internos do registro passam pelo campo definido no registro
externo, por exemplo, para acessar o interno `cep`, primeiro precisamos referenciar
o campo externo `info_pessoal`, portanto o acesso fica: `a.info_pessoal.cep`.
====
Para finalizar nossos estudos sobre Registro, na seção seguinte vamos compará-lo
com Arranjo.
[[sec_registro_comparacao]]
=== Comparação entre Arranjo e Registro
A tabela a seguir mostra uma comparação entre Arranjos e Registros.
[cols="1a,1a",frame="topbot",options="header"]
|====
| Arranjo (ou array) | Registro
|
* Estrutura de dados homogênea
** Arranjo de variáveis referenciadas por um mesmo nome e indexada por um inteiro. Ex: `notas[i]`.
* Armazena vários valores, mas *todos do mesmo tipo*.
|
* Estrutura de dados heterogênea
** Coleção de variáveis referencias por um mesmo nome
* Armazena vários valores, e *podem ser de diferentes tipos*
* Cada valor é armazenado num campo com um tipo próprio
|====
=== Recapitulando
Iniciamos este capítulo conhecendo a definição de *Registro* e sua utilidade.
Em seguida aprendemos a sua *sintaxe* de criação. Vimos o que é um
campo *identificador*, e como ele é utilizado para diferenciar
um registro de outro.
Realizamos *análises* em 5 situações demonstrando como criamos registros em
cada uma delas.
Na <<sec_registro_programas>> vimos como implementamos diversos programas em
pseudocódigo e em C.
Por fim, aprendemos como um registro pode ser inicializado
(<<sec_registro_inicializacao>>), comparamos os registros com os arranjos
(<<sec_registro_comparacao>>) e aprendemos como criar um registro através da
composição de outro (<<sec_registro_composicao>>).
No próximo capítulo aprenderemos como reaproveitar código, criando
nossas próprias funções.
=== Exercícios Propostos
1. *Entenda a necessidade dos registros.* Nós poderíamos escrever os programas
sem utilizar registros. Qual a utilidade de se utilizar registros nos programas?
2. O que é um campo *identificador*? Dê exemplos *não* contidos neste capítulo.
3. Na <<sec_registro_analise>> analisamos diversas situações buscando os campos
necessários para criação de Registros. Agora chegou a sua vez de fazer o
mesmo. Para cada situação a seguir:
+
--
* Defina o(s) *nome(s)* do tipo de registro que você criará
* Especifique os *campos* com seus respectivos *tipos*
* Indique quais são os campos *identificadores*, caso exista
* Escreva as *declarações* do(s) Registro(s) em C
--
.. Um programa para registrar os animais e os clientes de um Petshop.
.. Um programa para registrar e consultar filmes.
.. Um programa para uma biblioteca registrar os seus livros.
.. Um programa para agendar e consultar compromissos.
4. *Pratique o uso de registros*. Utilizando os registros definidos no
capítulo, faça pequenos programas e teste com as entradas indicadas.footnote:[Testar
com valores pré-definidos facilita o desenvolvimento dos programas,
faça disso um hábito. Veremos mais adiante como redirecionar a entrada
do programa, facilitando ainda mais os testes.]
+
--
.. Utilizando o <<ex_reg_aluno>>, faça um programa que indique
a situação de cada Aluno: *Aprovado*, se média das notas for maior ou igual 5;
*Reprovado* se a média for inferior a 5; *Aprovado com menção de honra* se
média for superior ou igual a 9.
+
--
.Entrada: nome matrícula nota1 nota2
----
include::code/registros/exercicio_aluno.data[]
----
--
.. Utilizando o <<ex_reg_pessoa>>, escreva um programa que imprima o
IMC das mulheres, depois o dos homens. Quando imprimir o IMC, exiba uma mensagem
indicando a condição em que a pessoa se encontra, segundo a tabela a seguir.
+
[width="85%",cols="2,3",frame="topbot",options="header"]
|====
| IMC | Classificação
| abaixo de 18,5 | Subnutrido ou abaixo do peso
| entre 18,6 e 24,9 | Peso ideal (parabéns)
| entre 25,0 e 29,9 | Levemente acima do peso
| entre 30,0 e 34,9 | Primeiro grau de obesidade
| entre 35,0 e 39,9 | Segundo grau de obesidade
| acima de 40 | Obesidade mórbida
|====
+
--
.Entrada: nome altura peso cpf sexo
----
include::code/registros/exercicio_pessoa.data[]
----
--
.. Utilizando o <<ex_reg_ponto>>, escreva um programa que leia alguns
pontos, indicando em qual quadrante eles estão no plano cartesiano.
+
--
.Entrada: p1.x p1.y p2.x p2.y p3.x p3.y
----
include::code/registros/exercicio_ponto.data[]
----
--
.. Utilizando o <<ex_reg_produto>>, escreva um programa que cadastra
uma lista de produtos. Em seguida imprima os produtos ordenadamente pelo
menor preço.
+
--
.Entrada: codigo nome valor
----
include::code/registros/exercicio_produto.data[]
----
--
--
5. *Criando novos Registros*. Agora que você já praticou a utilização de
Registro está no momento de criar os seus próprios Registros.
+
[IMPORTANT]
====
As questões a seguir *não* especificam os programas minuciosamente, elas foram
elaboradas assim para permitir que você expresse a sua criatividade.
No entanto, você deve:
* Resolver as questões utilizando os conhecimentos adquiridos neste capítulo
* Utilizar composição de registros quando for apropriado
* Preparar valores de entradas fixos para o seu programa, de forma a testá-lo
eficientemente.
====
.. Faça um programa para um Petshop, para cadastrar os clientes da loja e
seus animais. O programa deve possibilitar pesquisa pelo cliente ou pelo
seu animal.
.. Faça um programa para gerenciar os gastos pessoais. O programa deve poder
registrar os gastos por categoria e emitir um relatório para proporcionar
um controle financeiro.
.. Faça um programa para registrar os filmes que você assistiu ou quer assistir.
Os filmes devem ser cadastrados por categorias. O programa deve emitir listas
de filmes com base em dois critérios à sua escolha.
.. Faça um programa para auxiliar a Policia Federal acompanhar as explosões de
caixas eletrônicos ao longo do tempo. Após cadastrar as explosões, o sistema
deve informar as regiões críticas.
.. Faça um programa para simular um dicionário. Ele deve cadastrar algumas
palavras e possibilitar alguma forma de navegação. Consulte um dicionário real
para verificar que além do significado da palavra, outras informações diferentes
também são cadastradas.
// Sempre termine os arquivos com uma linha em branco.
introducao-a-programacao-livro-1.0.0/livro/docinfo.xml
v1.0.0
introducao-a-programacao-livro-1.0.0/livro/editora/editora.odt
Introdução a Programação
UNIVERSIDADE FEDERAL DA PARAÍBA
Reitora
MARGARETH DE FÁTIMA FORMIGA MELO DINIZ
Vice-Reitor
EDUARDO RAMALHO RABENHORST
EDITORA DA UFPB
Diretora
IZABEL FRANÇA DE LIMA
Vice-Diretor
JOSÉ LUIZ DA SILVA
Supervisão de Editoração
ALMIR CORREIA DE VASCONCELLOS JÚNIOR
Supervisão de Produção
JOSÉ AUGUSTO DOS SANTOS FILHO
CONSELHO EDITORIAL
Prof Dr. Lucídio Cabral (UFPB)
Prof Dr. Danielle Rousy (UFPB)
Prof Ms. Eduardo de Santana (UFPB)
Bruno Jefferson de Sousa
José Jorge Lima Dias Júnior
Andrei de Araújo Formiga
Introdução a Programação
Editora da UFPB
João Pessoa
2013
Capa - Projeto gráfico: Renato Arrais e Eduardo Santana
Editoração eletrônica: Eduardo de Santana Medeiros Alexandre
Revisora Ortográfica: Camyle de Araújo Silva
Catalogação na publicação
Universidade Federal da Paraíba
Biblioteca Setorial do CCEN
S725i Sousa, Bruno Jefferson de.
Introdução à programação / Bruno Jefferson de Sousa, José Jorge L. Dias Junior, Andrei de Araújo Formiga; editor: Eduardo de Santana Medeiros Alexandre, revisora: Camyle Araújo. – João Pessoa: Editora da UFPB, 2013. - João Pessoa: Curso de Licenciatura em Computação na Modalidade à Distância / UFPB, Novembro de 2013.
106p. : il. –
ISBN: 978-85-237-0822-1
Curso de Licenciatura em Computação na Modalidade à Distância. Universidade Federal da Paraíba.
1. Programação de computadores. 2 Tipos de Dados básicos. 3. Arranjos. 4. Funções. I. Título.
BS-CCEN CDU 004.42
Todos os direitos e responsabilidades dos autores.
EDITORA DA UFPB
Caixa Postal 5081 – Cidade Universitária
João Pessoa – Paraíba – Brasil
CEP: 58.051 – 970
http://www.editora.ufpb.br
Impresso no Brasil
Printed in Brazil
introducao-a-programacao-livro-1.0.0/livro/editora/editora.pdf
Introdução a Programação
UNIVERSIDADE FEDERAL DA PARAÍBA
Reitora
MARGARETH DE FÁTIMA FORMIGA MELO DINIZ
Vice-Reitor
EDUARDO RAMALHO RABENHORST
EDITORA DA UFPB
Diretora
IZABEL FRANÇA DE LIMA
Vice-Diretor
JOSÉ LUIZ DA SILVA
Supervisão de Editoração
ALMIR CORREIA DE VASCONCELLOS JÚNIOR
Supervisão de Produção
JOSÉ AUGUSTO DOS SANTOS FILHO
CONSELHO EDITORIAL
Prof Dr. Lucídio Cabral ..................................(UFPB)
Prof Dr. Danielle Rousy..................................(UFPB)
Prof Ms. Eduardo de Santana ........................(UFPB)
Bruno Jefferson de Sousa
José Jorge Lima Dias Júnior
Andrei de Araújo Formiga
Introdução a Programação
Editora da UFPB
João Pessoa
2013
Capa - Projeto gráfico: Renato Arrais e Eduardo Santana
Editoração eletrônica: Eduardo de Santana Medeiros Alexandre
Revisora Ortográfica: Camyle de Araújo Silva
Catalogação na publicação
Universidade Federal da Paraíba
Biblioteca Setorial do CCEN
S725i Sousa, Bruno Jefferson de.
Introdução à programação / Bruno Jefferson de Sousa, José Jorge L. Dias
Junior, Andrei de Araújo Formiga; editor: Eduardo de Santana Medeiros
Alexandre, revisora: Camyle Araújo. – João Pessoa: Editora da UFPB, 2013.
- João Pessoa: Curso de Licenciatura em Computação na Modalidade à
Distância / UFPB, Novembro de 2013.
106p. : il. –
ISBN: 978-85-237-0822-1
Curso de Licenciatura em Computação na Modalidade à Distância.
Universidade Federal da Paraíba.
1. Programação de computadores. 2 Tipos de Dados básicos. 3. Arranjos.
4. Funções. I. Título.
BS-CCEN CDU 004.42
Todos os direitos e responsabilidades dos autores.
EDITORA DA UFPB
Caixa Postal 5081 – Cidade Universitária
João Pessoa – Paraíba – Brasil
CEP: 58.051 – 970
http://www.editora.ufpb.br
Impresso no Brasil
Printed in Brazil
introducao-a-programacao-livro-1.0.0/livro/images/LEIAME.txt
A gráfica nos impões diversas restrições em
relação às imagens.
Portanto as imagens devem seguir as seguintes
intruções:
* Utilizar imagens CMYK ao invés de RGB
* Preferir utilizar tons de cinza ao invés de cores nas ilustrações
* Desenhar os gráficos no Inkscape, salvar as imagens em .svg
* Expotar a imagem .svg para .eps (manter as duas no repositório)
* Evitar usar gráficos em jpeg, pois na impressão fica borrado.
* Evitar usar gráficos em png pois são RGB
Em caso de dúvida, contactar a equipe de produção.
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma-calcular-media.eps
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma-calcular-media.png
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma-calcular-media.svg
image/svg+xml
Início
Obter nota1
Obter nota2
M= (nota1 + nota2)
2
M ≥ 7
Aprovado
Reprovado
Fim
Sim
Não
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma.eps
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma.png
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/fluxograma.svg
image/svg+xml
Processamento. Sentido do fluxo de execução do algoritmo. Ponto de decisão.
Início/Fim. Entrada de dados. Saída de dados.
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/ilustracao-dois-passos.eps
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/ilustracao-dois-passos.png
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/ilustracao-dois-passos.svg
image/svg+xml
Capacidade: 9 litros Utilização: 9 litros
Capacidade: 4 litros Utilização: 0 litros
Capacidade: 6 litros Utilização: 0 litros
Capacidade: 9 litros Utilização: 0 litros
Capacidade: 4 litros Utilização: 4 litros
Capacidade: 6 litros Utilização: 5 litros
(a) (b)
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/john-von-neumann.jpg
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria-exercicio.eps
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria-exercicio.png
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria-exercicio.svg
image/svg+xml
x=10
10 5 3
z=x/y+z
y=5
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria32posicoes.eps
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria32posicoes.png
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/memoria32posicoes.svg
image/svg+xml
x = 2 y = 3 z=x.y
2 3 6
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
introducao-a-programacao-livro-1.0.0/livro/images/algoritmos/monty.jpg
introducao-a-programacao-livro-1.0.0/livro/images/cap4/cadeia_caracteres.eps
introducao-a-programacao-livro-1.0.0/livro/images/cap4/cadeia_caracteres.svg
image/svg+xml
B R A S I L \0
0 1 2 3 4 5 6
Vetor
índices
{
valores
introducao-a-programacao-livro-1.0.0/livro/images/cap4/matriz-exemplos.eps
introducao-a-programacao-livro-1.0.0/livro/images/cap4/matriz-exemplos.svg
image/svg+xml
[ ]
4 2 3 8 0 1 9 1 6
Matriz 3x3
[ ]
5 1 0 9 2 1
Matriz 2x3
introducao-a-programacao-livro-1.0.0/livro/images/cap4/matriz.eps
introducao-a-programacao-livro-1.0.0/livro/images/cap4/matriz.svg
image/svg+xml
[ ]
a 1,1 a 1,2 a 1,3 ... a 1,n a 2,1 a 2,2 a 2,3 ... a 2,n a 3,1 a 3,2 a 3,3 ... a 3,n a m,1 a m,2 a m,3 ... a m,n
Linha
Coluna
introducao-a-programacao-livro-1.0.0/livro/images/cap4/vetor5.eps
introducao-a-programacao-livro-1.0.0/livro/images/cap4/vetor5.svg
image/svg+xml
4 2 7 9 3
0 1 2 3 4
Vetor
índices
{
valores
introducao-a-programacao-livro-1.0.0/livro/images/cap4/vetor_apos_leitura.eps
introducao-a-programacao-livro-1.0.0/livro/images/cap4/vetor_apos_leitura.svg
image/svg+xml
v[0] = 4 v[1] = 3 v[2] = 7 v[3] = 2 v[4] = 9
Vetor v
{
introducao-a-programacao-livro-1.0.0/livro/images/estruturas-de-controle/sequencia.eps
introducao-a-programacao-livro-1.0.0/livro/images/estruturas-de-controle/sequencia.png
introducao-a-programacao-livro-1.0.0/livro/images/estruturas-de-controle/sequencia.svg
image/svg+xml
void main(){ int x, y, soma; scanf(&x); scanf(&y); soma = x + y; printf("%d",soma); }
Execução das Intruções
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/Babbage-Analytical-Engine-with-cards.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/ENIAC-2.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/ENIAC.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/IBM_segunda_geracao.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/JacquardCard.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/JacquardLoom.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/abaco.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/ada_lovelace.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/apple-I.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/babbage-maquina-diferencial.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/bug.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/circuito-integrado-comparacao-de-tamanho.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/computador-quarta-geracao.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/computador-quinta-geracao.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/ibm-360-arquitetura-plugavel.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/napier-multiplicacao-46785399x7.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/napier-ossos-tabuleiro.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/pascaline-video.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/pascaline.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/regua-de-calculo.jpg
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/transistor-e-valvula-juntos.png
introducao-a-programacao-livro-1.0.0/livro/images/exemplo-de-capitulo/valvulas.png
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/compilacao-passos.png
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/interpretacao.png
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/memoria-representacao.eps
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/memoria-representacao.png
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/memoria-representacao.svg
image/svg+xml
Endereço
Variável
Valor
100
20,5
aula
...
-1700,23
0 x
1 y
2 z
... ...
n var
introducao-a-programacao-livro-1.0.0/livro/images/introducao-a-programacao/memoria.png
introducao-a-programacao-livro-1.0.0/livro/images/novo-capitulo/bug.png
introducao-a-programacao-livro-1.0.0/livro/images/prefacio/issue.png
introducao-a-programacao-livro-1.0.0/livro/livro.asc
= Introdução à Programação
:doctype: book
:lang: pt-BR
:keywords: Computação
:description: Livro de Nome-da-disciplina
:ascii-ids:
:gitrepo: https://github.com/edusantana/introducao-a-programacao-livro
include::capitulos/prefacio.asc[]
include::capitulos/algoritmos.asc[]
include::capitulos/introducao-a-programacao.asc[]
include::capitulos/estruturas-de-controle.asc[]
include::capitulos/cap4-arranjos.asc[]
//include::capitulos/registros.asc[]
include::capitulos/funcoes.asc[]
////
Sempre termine os arquivos com uma linha em branco.
////
introducao-a-programacao-livro-1.0.0/livro/test.asc
== Arquivo de teste
Teste de aquivo.
Mudando arquivo.
["graphviz"]
----
digraph colorschemes {
node [color=2 style=filled colorscheme=greys9]
edge [colorscheme=greys9]
blue [color=5]
red [color=8]
blue->red [color=8]
red->blue [color=5]
}
----
[[fig_compilacao_passos]]
["graphviz"]
.Passos no processo de compilação
----
digraph automata_0 {
rankdir=LR;
size ="8.5, 11";
node [shape = box];
edge [colorscheme=greys9]
subgraph clusterCodigos {
label = "Código fonte";
node [style=filled,color=white];
style=filled;
color=lightgrey;
code_assembly [label="Linguagem de Baixo Nível\n(Linguagem de montagem)"];
code_c [label="Linguagem de Alto Nível\n(Ex: C, Pascal, ...)"];
}
subgraph clusterTradutor {
label = "Tradutor";
node [style=filled,color=white,shape="doubleoctagon"];
style=filled;
color=lightgrey;
montador [label="Montador"];
compilador [label="Compilador"];
}
code_gerado [label="Código traduzido\n(Linguagem de montagem)"];
subgraph clusterCodigoObjeto {
label = "Código Objeto\n(binário)";
node [style=filled,color=white];
style=filled;
color=lightgrey;
objeto1 [label="Código Objeto 1"];
objeto2 [label="Código Objeto 2"];
}
ligador [label="Ligador",shape="doubleoctagon"];
programa [label="Código Binário\nExecutável", shape="component", fillcolor="grey", style="filled"];
carregador[label = "Carregador", style="filled", shape="folder"];
memoria[label="Memória", style="filled", shape="box3d"]
code_assembly -> montador -> objeto1 [color=8, style="bold"];
code_c -> compilador -> code_gerado -> montador -> objeto2 [color=6, style="bold"];
objeto1->ligador [color=5, style="bold"];
objeto2->ligador [color=5, style="bold"];
ligador-> programa -> carregador -> memoria [color=5, style="bold"];
{rank=source; code_c code_assembly }
{rank=same; montador compilador memoria}
{rank=same; objeto1 objeto2 carregador}
{rank=same; ligador programa}
{rank=sink; programa}
}
----
// Sempre termine os arquivos com uma linha em branco.