100% acharam este documento útil (3 votos)
509 visualizações240 páginas

AprendendoProgramarProgramandoC - 2ed

1. O documento apresenta um livro sobre programação na linguagem C, discutindo seus objetivos de ensinar conceitos básicos de programação e a linguagem C. 2. É dividido em dez capítulos abordando tópicos como introdução à programação, linguagem C, estruturas de seleção e repetição, funções, vetores, ordenação, strings, arquivos e alocação dinâmica. 3. O autor destaca a ajuda recebida de professores e alunos para alcançar o desafio de ensinar programação e C de

Enviado por

romulo
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd
100% acharam este documento útil (3 votos)
509 visualizações240 páginas

AprendendoProgramarProgramandoC - 2ed

1. O documento apresenta um livro sobre programação na linguagem C, discutindo seus objetivos de ensinar conceitos básicos de programação e a linguagem C. 2. É dividido em dez capítulos abordando tópicos como introdução à programação, linguagem C, estruturas de seleção e repetição, funções, vetores, ordenação, strings, arquivos e alocação dinâmica. 3. O autor destaca a ajuda recebida de professores e alunos para alcançar o desafio de ensinar programação e C de

Enviado por

romulo
Direitos autorais
© © All Rights Reserved
Levamos muito a sério os direitos de conteúdo. Se você suspeita que este conteúdo é seu, reivindique-o aqui.
Formatos disponíveis
Baixe no formato PDF, TXT ou leia on-line no Scribd

Aprendendo

a
Programar
Programando
na
Linguagem

Jaime Evaristo
Professor Adjunto
Instituto de Computação
Universidade Federal de Alagoas
Prefácio
Este é o terceiro livro que tive a felicidade de escrever com o objetivo de facilitar
o processo ensino/aprendizagem do desenvolvimento de programas de computadores. O
primeiro deles foi baseado na linguagem Pascal, [Evaristo, J, 1996], e o segundo foi
calcado numa linguagem algorítmica desenvolvida pelo professor Sérgio Crespo, da
Universidade Vale do Rio dos Sinos, que, além de estabelecer sua sintaxe, desenvolveu
um interpretador (denominado ILA) para execução dos algoritmos escritos na referida
linguagem, [Evaristo, J e Crespo, S, 2000]. Tanto a linguagem Pascal, desenvolvida por
Niklaus Wirth, quanto o ILA foram concebidos com o objetivo específico de facilitar o
ensino de programação.
É bem razoável se pensar que Dennis Ritchie ao projetar a linguagem C não a
imaginou sendo utilizada para facilitar o ensino de programação, tendo a concebido para
ser usada por programadores razoavelmente experientes. Esta concepção de linguagem
para programadores faz com que a aprendizagem de C exija, normalmente, a
aprendizagem anterior de alguma outra linguagem de programação com sintaxes mais
simples e menos recursos de programação.
Ao escrever este livro tentei (e tenho uma forte impressão que consegui) vencer
um grande desafio: escrever um livro para aprendizagem inicial de programação
utilizando a linguagem C e que também pudesse ser utilizado com o objetivo específico
da aprendizagem inicial da própria linguagem C. Para atingir o primeiro objetivo, todos
os conceitos básicos de programação são sempre discutidos a partir de exemplos, muitos
algoritmos (para problemas matemáticos e para questões gerais) são desenvolvidos, tudo
isto feito no sentido crescente de dificuldade, tendo sempre presente a preocupação com a
interligação entre os assuntos abordados. Além disto, várias questões práticas de
programação são abordadas, como, por exemplo, determinação do número de palavras de
um texto, totalização das colunas de uma tabela, determinação de medidas estatísticas
(média aritmética, mediana, moda, desvio padrão) de uma série de dados, determinação
de dígitos verificadores de matrículas, pesquisa seqüencial e binária, ordenação de vetores
e de arquivos. Para atingir o segundo objetivo, vários recursos adicionais da linguagem
são apresentados, inclusive uma discussão introdutória de ponteiros e uma apresentação
da recursividade (para não entrar em conflito com o primeiro o objetivo, estes recursos
adicionais não são muito utilizados nas soluções dos exemplos).
O conteúdo do livro está dividido em dez capítulos. O primeiro, intitulado
Introdução à Programação, contém assuntos gerais sobre computação que todo
programador deve conhecer. Nele é apresentada a organização básica de um computador
e são discutidos assuntos como linguagem de máquina, código ASCII, resolução de
problemas utilizando algoritmos, programas fonte, linguagens de alto nível, sistemas de
computação, entre outros.
O segundo capítulo, Introdução à Linguagem C, introduz o importante conceito
geral de variáveis, discute que expressões aritméticas e lógicas o sistema é capaz de
avaliar, apresenta a estrutura de programas em C, mostrando como dados podem ser
fornecidos durante a execução de um programa, como resultados da execução do

ii
programa podem ser mostrados ao usuário e como resultados podem ser gerados pela
própria execução do programa. Neste capítulo já são apresentados os primeiros
programas em C, inclusive programas de aplicação prática como um programa para
gerenciar saques de um caixa eletrônico.
O terceiro capítulo trata das Estruturas de seleção que a linguagem C
disponibiliza para tomadas de decisão em relação à execução ou não de uma seqüência de
instruções enquanto o capítulo quatro discute as Estruturas de repetição que permitem
que seqüências de instruções tenham sua execução repetida. Juntamente com o segundo
capítulo, estes dois capítulos e a compreensão dos exemplos neles apresentados são
fundamentais para o desenvolvimento da aprendizagem de qualquer linguagem de
programação.
O capítulo cinco trata de Funções e ponteiros O estudo das funções é
fundamental para o estudo da linguagem C, pois, como o leitor verá, um programa em C é
um conjunto de funções; a utilização de ponteiros constitui a grande vantagem de C sobre
outras linguagens. Este capítulo também apresenta o importante conceito de
recursividade, recurso que é bastante utilizado em programas de maior complexidade.
Por sua vez, o sexto capítulo apresenta os Vetores que permitem que programas
manipulem quantidades grandes de dados, todos armazenados na memória do
computador, possibilitando o tratamento de tabelas de dupla entrada muito comuns em
estatísticas. Já o capítulo sete, Pesquisa e ordenação, discute estes dois importantes
problemas de computação, apresentando a pesquisa binária para a busca e alguns
algoritmos clássicos para ordenação, como o SelectSorti, BubbleSort e InsertSort.
O capítulo oito estuda as Cadeias de caracteres (strings), discutindo os recursos
que a linguagem oferece para manipulação de textos, enquanto que o capítulo 9,
Estruturas e arquivos, tratam substancialmente da manipulação de arquivos, mostrando
como dados e resultados de processamento podem ser armazenados permanentemente em
discos. O capítulo 10, por seu turno, mostra, de forma introdutória, como a linguagem C
trata a Alocação dinâmica de memória.
Para enfrentar o desafio acima exposto, contei com a ajuda de várias pessoas. Na
verdade, inúmeras pessoas pois ao longo destes dez anos em que lecionei a primeira
disciplina de programação do curso de Ciência da Computação da Universidade Federal
de Alagoas sempre contei com a efetiva participação de todos os alunos e de todos os
colegas do Departamento de Tecnologia da Informação da universidade citada. No caso
deste último livro, recebi ajuda efetiva do Prof. Aílton Cruz e dos alunos Cristiano
Correia Silva Filho, Anderson Pontes Vieira, Rodrigo Albuquerque de Magalhães,
Rosimeire Lima da Silva, Solon Levi Rodrigues da Silva, Ulisses Silva Melo, Glauber
Vinicius Ventura de Melo Ferreira, Thiago Porfífrio de Vasconcelos, Katiane Cintia Melo
Silva, Márcio Robério de Costa Ferro, Sóstenes Leite da Silva Lucena, André Ferreira de
Alécio Gomes, Carlos Albuquerque de Araújo Cordeiro, Cidorvan dos Santos Leite,
Carlos Henrique Correia de Amorim, Nilvan Tavares Salviano, Júlio César de Oliveira
Alves, Romero Medeiros Souto Maior, Clisthenes Freira da Cruz Duarte, Deive Fabian
Valerio Gomes, Demian Nabero Borba, Vitorio Aragão Casaroli, Wallace Franco de
Azevedo Nogueira e Rodrigo de Barros Paes.

iii
Felizmente, os desafios que a vida nos oferece não são apenas desafios
acadêmicos/científicos/profissionais. Há a luta diuturna em busca da felicidade. Para
enfrentar esta peleja, a participação da minha esposa, Salete, e das minhas filhas, Jaiane,
Katiane e Aninha no meu dia a dia tem sido fundamental.
Há um novo desfio a enfrentar: a consolidação deste livro. Para isto conto com a
ajuda do leitor, através do envio de quaisquer críticas, sugestões, comentários e outras
soluções dos exemplos e das questões para [email protected]. Desde já, agradeço
penhoradamente.

iv
Prefácio da segunda edição
Nesta segunda edição, além de revisões gramaticais e de ortografia visando a
minimização de erros, reordenei os capítulos da edição anterior, colocando o estudo da
pesquisa e da ordenação logo após o estudo dos vetores e incluí seções apresentando
exemplos específicos. O primeiro deles tratando de uma questão de Matemática, ainda sem
solução, a Conjectura de Goldbach, o segundo um programa para acompanhamento do
placar de um jogo de voleibol e o terceiro para transforma o computador numa urna
eletrônica. Esses destaques se justificam pelo fato de o primeiro abordar um aspecto da
história da Ciência, o segundo ser uma questão não trivial (para o nível inicial de
programação) e aparecer (os seus efeitos) com relativa freqüência em transmissões
televisivas e o terceiro, além do apelo óbvio, necessita de soluções não triviais para algumas
questões.
Reiterando os agradecimentos a todos que contribuíram para a publicação da
primeira edição, gostaríamos de registrar a participação dos alunos do curso de Ciência da
Computação da Universidade Federal de Alagoas ingressantes no ano de 2006, destacando,
sem demérito para os demais, Anderson Augusto de Oliveira Moura,, Anderson Gustavo
Siqueira, Baltazar Tavares Vanderlei, Débora Cristina de Oliveira, Douglas Veras e Silva,
João Pedro Pontes Lima Matias, Lucas Monteiro Braz da Silva, Marcelo Anderson Batista
dos Santos, Marcelo Medeiros Arruda, Michel Alves dos Santos, Natalia de Melo Franco,
Pedro Bispo da Silva Santos, Rafael Ferreira Leite de Mello, Ronaldo Lisboa da Silva Júnior,
Társis Wanderley Toledo.
Pedindo permissão aos leitores, não posso perder a oportunidade de registrar a
chegada do Mateus que veio para dar alegria a todos os seus, em particular a mim, à Salete, à
Jaiane, à Aninha, ao Júnior e, principalmente, à Katiane que tanto lutou pela sua vinda. Junto
com Rodrigo, estamos todos esperando para muito breve a chegada do Vitor.
Em Maceió,
Março de 2007.

v
Sumário
1 INTRODUÇÃO À PROGRAMAÇÃO.......................................................................................1
1.1 ORGANIZAÇÃO BÁSICA DE UM COMPUTADOR ..........................................................................1
1.2 LINGUAGEM DE MÁQUINA ........................................................................................................1
1.3 PROGRAMAS DE COMPUTADORES.............................................................................................3
1.4 LÓGICA DE PROGRAMAÇÃO .....................................................................................................4
1.5 RESOLUÇÃO DE PROBLEMAS ....................................................................................................4
1.6 PROCESSADOR DE UM ALGORITMO...........................................................................................8
1.7 LINGUAGENS DE ALTO NÍVEL .................................................................................................14
1.8 SINTAXE E SEMÂNTICA DE UMA INSTRUÇÃO ..........................................................................15
1.9 SISTEMAS DE COMPUTAÇÃO ...................................................................................................16
1.10 EXERCÍCIOS PROPOSTOS.......................................................................................................17
2. INTRODUÇÃO À LINGUAGEM C .......................................................................................19
2.1 VARIÁVEIS SIMPLES ...............................................................................................................19
2.2 CONSTANTES .........................................................................................................................21
2.3 EXPRESSÕES ARITMÉTICAS ....................................................................................................22
2.4 RELAÇÕES..............................................................................................................................23
2.5 EXPRESSÕES LÓGICAS ............................................................................................................23
2.6 ESTRUTURA DE UM PROGRAMA EM C.....................................................................................24
2.7 ENTRADA DOS DADOS DE ENTRADA .......................................................................................25
2.8 SAÍDA DE DADOS....................................................................................................................28
2.10 EXEMPLOS PARTE I..............................................................................................................37
2.11 FUNÇÕES DE BIBLIOTECA .....................................................................................................40
2.12 EXERCÍCIOS PROPOSTOS.......................................................................................................42
3 ESTRUTURAS DE SELEÇÃO.................................................................................................44
3.1 O QUE É UMA ESTRUTURA DE SELEÇÃO ..................................................................................44
3.2 O COMANDO IF.......................................................................................................................44
3.3 O COMANDO IF ELSE...............................................................................................................46
3.4 O OPERADOR CONDICIONAL TERNÁRIO ..................................................................................47
3.5 EXEMPLOS PARTE III .............................................................................................................47
3.6 O COMANDO SWITCH .............................................................................................................55
3.7 EXERCÍCIOS PROPOSTOS.........................................................................................................58
4. ESTRUTURAS DE REPETIÇÃO ...........................................................................................60
4.1 PARA QUE SERVEM AS ESTRUTURAS DE REPETIÇÃO ...............................................................60
4.2 O COMANDO FOR ...................................................................................................................61
4.3 O COMANDO WHILE ................................................................................................................64
4.4 O COMANDO DO WHILE...........................................................................................................69
4.5 O COMANDO BREAK EM ESTRUTURAS DE REPETIÇÃO..............................................................71
4.6 EXEMPLOS PARTE III .............................................................................................................72
4.7 EXERCÍCIOS PROPOSTOS.........................................................................................................79
5. FUNÇÕES E PONTEIROS......................................................................................................82
5.1 O QUE SÃO FUNÇÕES ..............................................................................................................82
5.2 PARA QUE SERVEM FUNÇÕES .................................................................................................84
5.3 PASSAGEM DE PARÂMETROS ..................................................................................................86
5.4 PONTEIROS.............................................................................................................................90
5.5 PASSAGEM DE PARÂMETROS POR REFERÊNCIA NO TURBO C 2.01 ..........................................91
5.6 UMA URNA ELETRÔNICA ........................................................................................................92
5.7 RECURSIVIDADE ....................................................................................................................94
5.8 USANDO FUNÇÕES DE OUTROS ARQUIVOS ..............................................................................98
5.9 "TIPOS" DE VARIÁVEIS .........................................................................................................100
5.10 UMA APLICAÇÃO À HISTÓRIA DA MATEMÁTICA ................................................................102
5.11 EXERCÍCIOS PROPOSTOS.....................................................................................................103
6 VETORES.................................................................................................................................105
6.1 O QUE SÃO VETORES ............................................................................................................105
6.2 DECLARAÇÃO DE UM VETOR UNIDIMENSIONAL......................................................................105
6.3 VETORES E PONTEIROS.........................................................................................................106
6.4 LENDO E ESCREVENDO UM VETOR .......................................................................................107
6.5 EXEMPLOS PARTE IV ...........................................................................................................108
6.6 VETORES MULTIDIMENSIONAIS ............................................................................................113
6.7 EXEMPLOS PARTE V ............................................................................................................116
6.8 UMA APLICAÇÃO ESPORTIVA ...............................................................................................118
6.9 EXERCÍCIOS PROPOSTOS.......................................................................................................120
7 PESQUISA E ORDENAÇÃO .................................................................................................125
7.1 INTRODUÇÃO .......................................................................................................................125
7.2 PESQUISA SEQÜENCIAL ........................................................................................................125
7.3 PESQUISA BINÁRIA ...............................................................................................................126
7.4 ORDENAÇÃO ........................................................................................................................127
7.5 EXERCÍCIOS PROPOSTOS.......................................................................................................130
8 CADEIAS DE CARACTERES (STRINGS)...........................................................................131
8.1 INTRODUÇÃO .......................................................................................................................131
8.2 FUNÇÕES DE BIBLIOTECA PARA MANIPULAÇÃO DE CADEIAS DE CARACTERES .....................133
8.3 EXEMPLOS PARTE VI ...........................................................................................................135
8.4 EXERCÍCIOS PROPOSTOS.......................................................................................................140
9 ESTRUTURAS E ARQUIVOS ...............................................................................................142
9.1 O QUE SÃO ESTRUTURAS ......................................................................................................142
9.2 O QUE SÃO ARQUIVOS ..........................................................................................................144
9.3 ARQUIVOS DE REGISTROS ....................................................................................................144
9.4 ARQUIVO TEXTO ..................................................................................................................156
9.5 EXERCÍCIOS PROPOSTOS.......................................................................................................161
10 ALOCAÇÃO DINÂMICA DE MEMÓRIA ........................................................................162
10.1 O QUE É ALOCAÇÃO DINÂMICA ..........................................................................................162
10.2 ARMAZENANDO DINAMICAMENTE UM POLINÔMIO .............................................................164
10.3 LISTAS ...............................................................................................................................165
10.4 EXERCÍCIOS PROPOSTOS.....................................................................................................167
RESPOSTAS DOS EXERCÍCIOS PROPOSTOS ...................................................................168

vii
CAPÍTULO 1 ...............................................................................................................................168
CAPÍTULO 2 ...............................................................................................................................170
CAPÍTULO 3 ...............................................................................................................................174
CAPÍTULO 4 ...............................................................................................................................179
CAPÍTULO 5 ...............................................................................................................................186
CAPÍTULO 6 ...............................................................................................................................189
CAPÍTULO 7 ...............................................................................................................................204
CAPÍTULO 8 ...............................................................................................................................205
CAPÍTULO 9 ...............................................................................................................................215
CAPÍTULO 10 .............................................................................................................................226
BIBLIOGRAFIA.........................................................................................................................228
ÍNDICE REMISSIVO.................................................................................................................229

viii
1 Introdução à Programação
1.1 Organização básica de um computador
Um computador é constituído de quatro unidades básicas: unidade de entrada,
unidade de saída, unidade de processamento central e memória. Como indica sua
denominação, uma unidade de entrada é um dispositivo que permite que o usuário
interaja com o computador, fornecendo-lhe dados e informações que serão processadas,
sendo o teclado o seu exemplo mais trivial. Uma unidade de saída, por seu turno, serve
para que sejam fornecidos ao usuário do computador os resultados do processamento
realizado. O monitor de vídeo e uma impressora são exemplos de unidades de saída. A
unidade central de processamento é responsável por todo o processamento requerido,
sendo muito conhecida por cpu, acrossemia de central processing unit. Já a memória
armazena dados e informações que serão utilizados no processamento, armazenamento
temporário, pois quando o computador é desligado tudo que está armazenado na memória
deixa de sê-lo (dizemos que toda a memória é "apagada").

1.2 Linguagem de máquina

Linguagens de comunicação
Evidentemente, há a necessidade de que as unidades que compõem um computador
se comuniquem umas com as outra. Por exemplo, um dado fornecido pelo teclado deve
ser armazenado na memória; para a cpu realizar uma operação aritmética ela vai buscar
valores que estão armazenados na memória, e assim por diante. Para que haja
comunicação entre as unidades do computador é necessário que se estabeleça uma
linguagem.
Os seres humanos se comunicam basicamente através de duas linguagens: a
linguagem escrita e a fala. Uma comunicação através de uma linguagem escrita é
constituída de parágrafos, os quais contêm períodos, que contêm frases, que são
constituídas de palavras, sendo cada uma das palavras formadas por letras e esta
seqüência termina aí. Assim, uma letra é um ente indivisível da linguagem escrita e, em
função disto, é chamada símbolo básico desta linguagem. Este exemplo foi apresentado
para que se justifique a afirmação de que toda linguagem requer a existência de símbolos
básicos como - e para mais um exemplo - os fonemas para a linguagem falada.

A linguagem de comunicação entre as unidades


Como a comunicação entre as unidades do computador teria que ser obtida através
de fenômenos físicos, os cientistas que conceberam os computadores atuais estabeleceram
dois símbolos básicos para a linguagem. Esta quantidade de símbolos foi escolhida pelo
Capítulo 1 Introdução à Programação

fato de que através de fenômenos físicos é muito fácil obter dois estados distintos e não
confundíveis, como passar corrente elétrica/não passar corrente elétrica, estar
magnetizado/não estar magnetizado, etc., podendo cada um destes estados ser um dos
símbolos. Assim a linguagem utilizada para comunicação interna num computador,
chamada linguagem de máquina, possui apenas dois símbolos. Cada um destes símbolos
é denominado bit (binary digit) e eles são representados por 0 (zero) e 1 (um). Esta forma
de representar os bit's justifica a sua denominação: binary digit, que significa dígito
binário (além disto, bit em inglês significa fragmento). Portanto, as palavras da
linguagem de máquina são seqüências de bits, ou seja, seqüências de dígitos zero e um.

O código ASCII
Para que haja a possibilidade da comunicação do homem com o computador, é
necessário que as palavras da linguagem escrita sejam traduzidas para a linguagem de
máquina e vice-versa. Para que isto seja possível, é necessário que se estabeleça qual a
seqüência de bit's que corresponde a cada caractere usado na linguagem escrita. Ou seja, é
necessário que se estabeleça uma codificação em seqüência de bit's para cada um dos
caracteres. Uma codificação muito utilizada é o código ASCII (American Standard Code
for Information Interchange ou Código Padrão Americano para Intercâmbio de
Informações), estabelecido pelo ANSI (American National Standards Institute). Nesta
codificação, cada caractere é representado por uma seqüência de oito bits (normalmente,
um conjunto de oito bit's é chamado byte). Só para exemplificar (será visto ao longo do
livro que, em geral, não há necessidade de que se conheçam os códigos dos caracteres),
apresentamos a tabela abaixo com os códigos ASCII de alguns caracteres.
Tabela 1 Códigos ASCII de alguns caracteres
Caractere Código ASCII
Espaço em branco 00100000
! 00100001
" 00100010
... ...
0 00110000
1 00110001
... ...
A 01000001
B 01000010
... ...
Z 01011010
... ...
A 01100001
... . ..
Observe a necessidade de se haver codificado o espaço em branco (este
"caractere" é utilizado para separar nossas palavras) e de se haver codificado

2
Capítulo 1 Introdução à Programação

diferentemente as letras maiúsculas e minúsculas, para que se possa considerá-las como


coisas distintas.
Levando em conta que cada seqüência de zeros e uns pode ser vista como a
representação de um número inteiro no sistema binário de numeração [Evaristo, J 2002],
podemos, até para facilitar a sua manipulação, associar a cada código ASCII o inteiro
correspondente, obtendo assim o que se costuma chamar de código ASCII decimal. Por
exemplo, como 1000001 é a representação do número (decimal) 65 no sistema binário de
numeração, dizemos que o código ASCII decimal de A é 65.

1.3 Programas de computadores


Para que um computador tenha alguma utilidade, ele deve executar um programa
que tem uma finalidade específica. Games são programas que têm como objetivo
propiciar entretenimento aos seus usuários. Processadores de texto são programas que
permitem que textos sejam digitados, impressos e armazenados para futuras modificações
ou impressões. Planilhas eletrônicas são programas que oferecem recursos para
manipulação de tabelas de valores numéricos. Navegadores permitem acessos a páginas
da internet, a rede mundial de computadores. Estes programas destinam-se a usuários
finais, aquelas pessoas que vão utilizar o computador com um determinado objetivo
específico, usando para tal um programa que ela aprendeu a usar, não tendo nenhuma
preocupação relativa ao funcionamento interno do sistema computador/programa. Por
exemplo, um usuário de um processador de texto deve aprender o que fazer para que o
processador destaque em negrito alguma parte do texto ou localize uma palavra, não
havendo necessidade de saber como o programa realiza estas ações.
Na verdade, para que um processador de texto propicie ao usuário a possibilidade
de que textos sejam digitados, corrigidos, gravados, inseridos em outros textos e de que
palavras sejam localizadas dentro de um texto, é necessária a execução de muitas
instruções com objetivos bem mais específicos e restritos. Um programa de computador
é, na realidade, um conjunto de instruções que podem ser executadas pelo computador, de
tal forma que a execução de subconjuntos destas instruções permitem a realização de
ações mais genéricas.
É muito grande o número de instruções dos programas citados acima, chegando à
casa dos milhares. Rigorosamente falando, um programa dos acima citados é um conjunto
de programas menores, cada um deles com objetivos mais restritos, e que podem ser
executados de forma integrada. É comum se utilizar a palavra inglesa software para
designar um conjunto de programas com objetivos mais restritos que, sendo executados
de forma integrada, propiciam a execução de ações bem mais genéricas.
A parte da Ciência da Computação que trata do desenvolvimento de softwares é
denominada Engenharia de Software. Naturalmente, o estudo da Engenharia de Software
deve ser precedido da aprendizagem do desenvolvimento de programas “menores”, ação
que comumente é denominada de Programação de Computadores.

3
Capítulo 1 Introdução à Programação

1.4 Lógica de programação


Sendo um conjunto de instruções cujas execuções redundam na realização da tarefa
para a qual o programa foi desenvolvido, o desenvolvimento de programas requer a
utilização de um raciocínio ímpar em relação aos raciocínios utilizados na solução de
problemas de outros campos do saber. Por exemplo, e de forma simplificada, ao se tentar
resolver um problema de Mecânica Newtoniana deve-se procurar capturar da
especificação da questão as grandezas físicas envolvidas e aplicar as fórmulas que
relacionam estas grandezas.
Para se desenvolver um programa que resolva um determinado problema é
necessário que encontremos uma seqüência de instruções que cujas execuções resultem
na solução da questão. É comum se utilizar a termo algoritmo para indicar uma seqüência
de instruções que resolvem um dado problema, ficando, neste caso, o termo programa
para indicar um algoritmo que pode ser executado num computador. A Lógica de
Programação pode ser entendida como o conjunto de raciocínios utilizados para o
desenvolvimento de algoritmos.
Por exemplo, imagine a seguinte questão: um senhor, infelizmente bastante gordo,
está numa das margens de um rio com uma raposa, uma dúzia de galinhas e um saco de
milho. O senhor pretende atravessar o rio com suas cargas, num barco a remo que só
comporta o senhor e uma das cargas. Evidentemente, o senhor não pode deixar em uma
das margens, sozinhos, a raposa e a galinha, nem a galinha e o milho. A questão é
escrever um algoritmo que oriente o senhor a realizar o seu intento. Naturalmente, na
primeira viagem, ele não pode levar a raposa (neste caso, as galinhas comeriam o milho),
nem o milho (caso em que a raposa devoraria as galinhas). Logo, na primeira viagem ele
deve levar as galinhas. Como ele estará presente na chegada, na segunda viagem ele pode
levar a raposa ou o milho. Mas, e a volta para apanhar terceira carga? A solução é ele
voltar com as galinhas e, aí, atravessar o milho, já que não há problema em que a raposa e
o milho fiquem juntos. Escrevendo as instruções na seqüência em que elas devem ser
executadas, teremos o seguinte algoritmo.
1. Atravesse as galinhas.
2. Retorne sozinho.
3. Atravesse a raposa.
4. Retorne com as galinhas.
5. Atravesse o milho.
6. Retorne sozinho.
7. Atravesse as galinhas.

1.5 Resolução de problemas


Uma pergunta que o leitor pode estar se fazendo é: como vou "descobrir" que a
primeira instrução deve ser a travessia das galinhas?
Algumas tarefas para as quais se pretende escrever um algoritmo podem ser vistas
como um problema a ser resolvido. O exemplo anterior é um exemplo claro de uma tarefa

4
Capítulo 1 Introdução à Programação

com esta característica. Existem algumas técnicas que podem ser utilizadas para a
resolução de problemas. No exemplo anterior, para se definir qual seria a primeira
instrução, como existem apenas três possibilidades, verifica-se o que aconteceria ao se
escolher determinada instrução. Foi o que, de passagem, foi feito acima: se o homem
atravessasse primeiro o milho, a raposa devoraria as galinhas; se o homem atravessasse
primeiro a raposa, as galinhas comeriam o milho. Neste caso, podemos dizer que foi
utilizada a técnica da exaustão: como o número de alternativas era pequeno, analisamos
todas elas, uma a uma.
Esta técnica pode ser utilizada também na solução do seguinte problema: dispõe-se
de três esferas idênticas na forma, sendo duas delas de mesmo peso e a terceira de peso
maior. A questão é descobrir qual a esfera de peso diferente, realizando-se apenas uma
pesagem numa balança de dois pratos. Para isto chamemos de A e B as esferas de mesmo
peso e de C a de maior peso. Se optarmos por colocar duas esferas num dos pratos e a
outra esfera no outro, temos as seguintes possibilidades:
a) (A+B, C).
b) (A+C, B).
c) (B+C, A).
No primeiro caso, pode acontecer qualquer coisa: a balança pode ficar equilibrada,
se Peso(C) = Peso(A+B); ficar inclinada para o lado esquerdo, se Peso(C) > Peso(A+B)
ou ficar inclinada para o lado direito se Peso(C) < Peso(A+B). Observe que nada pode
distinguir a esfera C. Nos dois últimos casos, a balança se inclinará para a esquerda, mas,
outra vez, nada distingue a esfera C. Por exaustão, resta então escolhermos duas esferas e
colocarmos cada uma delas num dos pratos da balança. Temos então as seguintes
possibilidades:
a) (A, B).
b) (A, C).
c) (B, C).
No primeiro caso, a balança ficará equilibrada, o que indica que a mais pesada é
aquela não escolhida; nos outros dois casos, a balança se inclinará para a direita,
indicando que a esfera mais pesada é aquela que ocupa o prato respectivo. Temos então o
seguinte algoritmo:
1. Escolha duas esferas.
2. Coloque cada uma das esferas escolhidas num dos pratos da balança.
3. Se a balança ficar equilibrada, forneça como resposta a esfera não escolhida;
caso contrário, forneça como resposta a esfera do prato que está num nível mais baixo.
Uma outra técnica de resolução de problemas consiste em se tentar resolver casos
particulares da questão ou resolver a questão para dados menores do que os dados que
foram fixados. Para exemplificar, consideremos o seguinte problema: como obter
exatamente 4 litros de água dispondo de dois recipientes com capacidades de 3 litros e 5

5
Capítulo 1 Introdução à Programação

litros1? Como 4 = 3 + 1 ou 4 = 5 – 1, conseguiremos resolver a questão se conseguirmos


obter 1 litro. Mas isto é fácil, pois 1 = 3 + 3 – 5! Temos então o seguinte algoritmo:
1. Encha o recipiente de 3 litros.
2. Transfira o conteúdo do recipiente de 3 litros para o recipiente de 5 litros.
3. Encha o recipiente de 3 litros.
4. Com o conteúdo do recipiente de 3 litros, complete o recipiente de 5 litros.
5. Esvazie o recipiente de 5 litros.
6. Transfira o conteúdo do recipiente de três litros para o recipiente de 5 litros.
7. Encha o recipiente de 3 litros.
8. Transfira o conteúdo do recipiente de 3 litros para o recipiente de 5 litros.
Para compreender o algoritmo, sejam A e B os recipientes de 3 litros e de 5 litros,
respectivamente, e indiquemos por (X, n) o fato de o recipiente X conter n litros de água.
No início temos (A, 0) e (B, 0) e, após a execução de cada instrução, teremos:
1. (A, 3), (B, 0).
2. (A, 0), (B, 3).
3. (A, 3), (B, 3).
4. (A, 1), (B, 5).
5. (A, 1), (B, 0).
6. (A, 0), (B, 1).
7. (A, 3), (B, 1).
8. (A, 0), (B, 4).
Outras questões que podem ser levantadas são: há outras soluções? Existe alguma
solução que realize a mesma tarefa com uma quantidade menor de instruções? Para
responder a estas questões talvez seja interessante lembrar que 4 = 5 – 1. Significa que, se
conseguirmos tirar 1 litro do recipiente de 5 litros quando ele estiver cheio, resolveremos
a questão. Para conseguir isto, basta que o recipiente de 3 litros contenha 2 litros. E para
se obter 2 litros? Aí basta ver que 2 = 5 – 3. Podemos então resolver a questão com o
seguinte algoritmo, constituído de apenas seis instruções:
1. Encha o recipiente de 5 litros.
2. Com o conteúdo do recipiente de 5 litros, encha o de 3 litros.
3. Esvazie o recipiente de 3 litros.
4. Transfira o conteúdo do recipiente de 5 litros para o recipiente de 3 litros.
5. Encha o recipiente de 5 litros.
6. Com o conteúdo do recipiente de 5 litros, complete o recipiente de 3 litros.
Após a execução de cada uma das instruções teremos:
1. (A, 0), (B, 5).
2. (A, 3), (B, 2).
3. (A, 0), (B, 2).
1
A solução desta questão foi necessária no filme Duro de Matar 4 para um policial desativar uma
bomba.

6
Capítulo 1 Introdução à Programação

4. (A, 2), (B, 0).


5. (A, 2), (B, 5).
6. (A, 3), (B, 4).
Uma outra técnica bastante utilizada é se tentar raciocinar a partir de uma solução
conhecida de uma outra questão. Para compreender isto considere as duas seguintes
questões: imagine uma relação de n números, os quais podem ser referenciados por ai
com i = 1, 2, ..., n e queiramos somá-los com a restrição de que só sabemos efetuar somas
de duas parcelas. Para resolver esta questão, podemos pensar em casos particulares: se
n = 2, basta somar os dois números; se n = 3, basta somar os dois primeiros e somar esta
soma com o terceiro. Naturalmente este raciocínio pode ser reproduzido para n > 3. A
questão é que a soma dos dois primeiros deve estar "guardada" para que se possa somá-la
com o terceiro, obtendo-se a soma dos três primeiros; esta soma deve ser "guardada" para
que seja somada com o quarto e assim sucessivamente. Para isto podemos estabelecer
uma referência à soma "atual", a qual será alterada quando a soma com o elemento
seguinte for efetuada. Até para somar os dois primeiros, pode-se pensar em somar "a
soma do primeiro" com o segundo.
Temos então o seguinte algoritmo:
1. Faça i = 1.
2. Faça Soma = a1.
3. Repita n – 1 vezes as instruções 3.1 e 3.2.
3.1. Substitua i por i + 1.
3.2. Substitua Soma por Soma + ai.
Por exemplo: se n = 5 e a1 = 8, a2 = 4, a3 = 9, a4 = 13 e a5 = 7, a execução do
algoritmo resultaria nas seguintes ações:
1. i = 1.
2. Soma = 8.
3.1.1. i = 2.
3.2.1. Soma = 8 + 4 = 12
3.1.2. i = 3.
3.2.2. Soma = 12 + 9 = 21.
3.1.3. i = 4.
3.2.3. Soma = 21 + 13 = 34.
3.1.4. i = 5.
3.2.4. Soma = 34 + 7 = 41.
Naturalmente, na execução acima estamos indicando por 3.1.x e 3.2.x a execução
de ordem x das instruções 3.1 e 3.2.
Como veremos ao longo do livro, este algoritmo é bastante utilizado em
programação, sendo que é mais comum que até o primeiro termo da relação seja
"somado" dentro da repetição. Neste caso, para que o primeiro seja somado, é necessário
que Soma seja inicializada com 0 (zero), ficando assim o algoritmo:

7
Capítulo 1 Introdução à Programação

1. Faça i = 0.
2. Faça Soma = 0.
3. Repita n vezes as instruções 3.1 e 3.2.
3.1. Substitua i por i + 1.
3.2. Substitua Soma por Soma + ai.
Conhecendo este algoritmo, é fácil então resolver a questão de se calcular o
produto de n números nas mesmas condições, e aí vemos como utilizar uma solução
conhecida para resolver um problema. Deve-se inicializar uma referência Produto com 1
e, numa repetição, multiplicar os números como foi feito no caso da soma:
1. Faça i = 0.
2. Faça Produto = 1.
3. Repita n vezes as instruções 3.1 e 3.2.
3.1. Substitua i por i + 1.
3.2. Substitua Produto por Produto x ai.

1.6 Processador de um algoritmo


Obviamente, um algoritmo deve ser executado por algum agente. Este agente pode
ser uma pessoa munida de certos equipamentos e utensílios ou por máquinas projetadas
para executar automaticamente algumas instruções básicas. O algoritmo para a travessia
do senhor gordo com as galinhas, sua raposa e seu saco de milho seria executado pelo tal
senhor, que estava para tal munido do barco e de remos. O algoritmo para obtenção de
quatro litros de água a partir de recipientes de conteúdos cinco litros e três litros poderia
ser executado por uma pessoa que dispusesse dos dois recipientes e de água em
abundância. Neste último caso, quem sabe, a pessoa poderia ser substituída por um robô.
O agente que executa um algoritmo é chamado processador e é evidente que para
que o algoritmo seja executado é necessário que o processador seja capaz de executar
cada uma das suas instruções. Se o senhor gordo não souber remar ele não será capaz de
atravessar o rio. Uma pessoa que não seja capaz de esvaziar um recipiente que pese cinco
quilos não será capaz de executar o algoritmo dos quatro litros de água.
Alguns autores de livros com objetivos idênticos a este - facilitar a aprendizagem
da programação de computadores - iniciam seus textos discorrendo exclusivamente sobre
resolução de problemas, encarando o processador como uma "caixa preta" que recebe as
instruções formuladas pelo algoritmo e fornece a solução do problema, não levando em
conta o processador quando da formulação do tal algoritmo. Entendemos que esta não é a
melhor abordagem, visto que o conhecimento do que o processador pode executar pode
ser definidor na elaboração do algoritmo. Por exemplo: imagine que queiramos elaborar
um algoritmo para extrair o algarismo da casa das unidades de um inteiro dado
(apresentaremos posteriormente uma questão bastante prática cuja solução depende deste
algoritmo). Evidentemente, o algoritmo para resolver esta “grande” questão depende do
processador que vai executá-lo. Se o processador for um ser humano que saiba o que é
número inteiro, algarismo e casa das unidades, o algoritmo teria uma única instrução:

8
Capítulo 1 Introdução à Programação

1. Forneça o algarismo das unidades do inteiro dado.


Porém, se o processador for um ser humano que saiba o que é número inteiro e
algarismo, mas não saiba o que é casa das unidades, o algoritmo não poderia ser mais
esse. Neste caso, para resolver a questão, o processador deveria conhecer mais alguma
coisa, como, por exemplo, ter a noção de "mais à direita", ficando o algoritmo agora
como:
1. Forneça o algarismo "mais à direita" do número dado.
E se o processador é uma máquina e não sabe o que é algarismo, casa das unidades,
"mais à direita", etc.? Nesta hipótese, quem está elaborando o algoritmo deveria conhecer
que instruções o processador é capaz de executar para poder escrever o seu algoritmo. Por
exemplo, se a máquina é capaz de determinar o resto de uma divisão inteira, o algoritmo
poderia ser:
1. Chame de n o inteiro dado;
2. Calcule o resto da divisão de n por 10;
3. Forneça este resto como o algarismo pedido.
Algumas das questões anteriores são importantes para se desenvolver o raciocínio,
mas não é este tipo de questão que se pretende discutir ao longo deste livro. Estamos
interessados em algoritmos para:
1. Resolver problemas matemáticos, como, por exemplo, algoritmos para
determinar a média aritmética de vários números dados, determinar as raízes de uma
equação do segundo grau, encontrar o máximo divisor comum de dois números dados,
totalizar as colunas de uma tabela, etc.
2. Resolver questões genéricas, como, por exemplo, algoritmos para colocar em
ordem alfabética uma relação de nomes de pessoas, atualizar o saldo de uma conta
bancária na qual se fez um depósito, corrigir provas de um teste de múltipla escolha,
cadastrar um novo usuário de uma locadora, etc..
Na linguagem coloquial, o algoritmo para o cálculo da média pode ser escrito de
forma muito simples:
1. Determine a quantidade de números;
2. Some os números dados;
3. Divida esta soma pela quantidade de números.
Qualquer pessoa que saiba contar, somar e dividir números é capaz de executar este
algoritmo dispondo apenas de lápis e papel. A questão que se põe é: e se a relação
contiver 13.426 números? A tal pessoa é capaz de executar, porém, quanto tempo levará
para fazê-lo?
Um outro aspecto a ser observado é que nem sempre a linguagem coloquial é
eficiente para se escreverem as instruções. Nessa linguagem o algoritmo para
determinação das raízes de uma equação do segundo grau teria uma instrução difícil de
escrever e difícil de compreender como:

9
Capítulo 1 Introdução à Programação

n. Subtraia do quadrado do segundo coeficiente o produto do número quatro pelo


produto dos dois outros coeficientes.
Isto pode ser parcialmente resolvido utilizando-se uma linguagem próxima da
linguagem matemática que já foi utilizada em exemplos da seção anterior. No caso da
equação do segundo grau teríamos o seguinte algoritmo, que nos é ensinado nas últimas
séries do ensino fundamental:
1. Chame de a, b e c os coeficientes da equação.
2. Calcule d = b² - 4ac.
3. Se d < 0 forneça como resposta a mensagem: A equação não possui raízes reais.
4. Se d ≥ 0
4.1 Calcule x1 = (-b + raiz(d))/2a e x2 = (-b - raiz(d))/2a.
4.2 Forneça x1 e x2 como raízes da equação.
De maneira mais ou menos evidente, raiz(d) está representando a raiz quadrada de
d e a execução deste algoritmo requer que o processador seja capaz de determinar valores
de expressões aritméticas, calcular raízes quadradas, efetuar comparações e que conheça a
linguagem matemática.
Algoritmos para problemas genéricos são mais complicados e a linguagens
utilizadas anteriormente não são adequadas (para o caso da ordenação de uma relação de
nomes, foram desenvolvidos vários algoritmos e teremos oportunidade de discutir alguns
deles ao longo deste livro).

1.7 Exemplos de algoritmos matemáticos


Para uma primeira discussão em termos de aprendizagem de desenvolvimento de
algoritmos e utilizando a linguagem usada no exemplo da equação do segundo grau,
apresentamos a seguir alguns exemplos de algoritmos que objetivam a solução de
questões da matemática. Para eles supomos que o processador seja capaz de efetuar
somas, subtrações e divisões decimais, de realizar comparações, de repetir a execução de
um conjunto de instruções um número determinado de vezes ou enquanto uma condição
seja atendida.
1. No exemplo do algoritmo para obtenção do algarismo da casa das unidades de
um inteiro dado supomos que o processador seria capaz de calcular o resto de uma
divisão inteira. Observando que não está suposto que o nosso processador seja capaz de
determinar restos de divisões inteiras, vamos discutir um algoritmo para a determinação
do quociente e do resto da divisão de dois inteiros positivos dados. Por exemplo: se o
dividendo for 30 e o divisor for 7, o algoritmo deve fornecer os valores 4 para o quociente
e 2 para o resto. Fomos ensinados que, para determinar o quociente, deveríamos, por
tentativa, encontrar o número que multiplicado pelo divisor resultasse no maior número
menor que o dividendo. No exemplo numérico citado, poderíamos tentar o 5 e teríamos
5x7 = 35 que é maior que 30; tentaríamos o 3 obtendo 3x7 = 21 que talvez seja pequeno
demais em relação ao 30; aí tentaríamos o 4 obtendo 4x7 = 28, encontrando então o
quociente 4. Um algoritmo para solucionar esta questão poderia ser:

10
Capítulo 1 Introdução à Programação

1. Chame de D1 e D2 o dividendo e o divisor dados.


2. Faça I = 1.
3. repita 3.1 até IxD2 > D1.
3.1. Substitua I por I + 1.
4. Calcule Q = I – 1.
5. Calcule R = D1 - QxD2.
6. Forneça R para o resto e Q para o quociente pedidos.
No exemplo numérico proposto, teríamos a seguinte tabela com os valores
obtidos durante a execução do algoritmo:
D1 D2 I QxI Q R
30 7
1 7
2 14
3 21
4 28
5 35
4 2
2. O algoritmo abaixo determina o menor divisor maior que 1 de um inteiro dado.
A idéia é verificar se d = 2 é divisor e, não sendo, verificar se 3 ou 4 ou 5, etc é divisor. A
procura por um divisor vai até que um divisor seja encontrado. Naturalmente, utilizando o
algoritmo anterior, o nosso processador agora sabe determinar o resto da divisão inteira
de um inteiro x por outro inteiro y não nulo. Isto será indicado por Resto(x, y). Para
encontrar um divisor de n basta encontrar um inteiro d tal que Resto(n, d) = 0.
1. Chame de N o inteiro dado.
2. Faça D = 2.
3. Repita 3.1 enquanto Resto(N, D) ≠ 0
3.1 Substitua D por D + 1
4. Forneça D para o divisor procurado.
3. Como se depreende facilmente da sua denominação, o máximo divisor comum
(mdc) de dois números dados é o maior número que os divide. Antes o mdc só era
utilizado para simplificações de frações ordinárias; atualmente ele é utilizado na
determinação de chaves públicas para sistemas de criptografia RSA [Evaristo, J, 2002].
Por exemplo, mdc(64, 56) = 8. De maneira óbvia, o algoritmo abaixo determina o mdc de
dois números dados:
1. Chame de x e de y os números.
2. Determine D(x), o conjunto dos divisores de x.
3. Determine D(y), o conjunto dos divisores de y.
4. Determine I, a interseção de D(x) e D(y).
5. Determine M, o maior elemento do conjunto I.
6. Forneça M como o mdc procurado.
O cálculo de mdc(120, 84) com este algoritmo seria:
11
Capítulo 1 Introdução à Programação

1. x = 120, y = 84.
2. D(120) = {1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 24, 30, 40, 60, 120}.
3. D(84) = {1, 2, 3, 4, 6, 7, 12, 14, 21, 28, 42, 84}.
4. I = {1, 2, 3, 4, 6, 12}.
5. M = 12.
Observe que o algoritmo anterior determina o menor divisor de um inteiro não
determinando todos os divisores, como necessário neste exemplo. Observe também que
estamos supondo que o nosso processador é capaz de determinar a interseção de dois
conjuntos.
A matemática fornece uma outra forma de se calcular o mdc de dois inteiros:
determina-se a decomposição em fatores primos dos dois inteiros e o mdc é o produto dos
fatores primos comuns as duas decomposições com as menores multiplicidades.
Para o exemplo dado acima teríamos:
120 2 84 2
60 2 42 2
30 2 21 3
15 3 7 7
5 5 1
1 1
o que nos dá 120 = 23x3x5 e 84 = 22x3x7 e, portanto, mdc(120, 84) = 22x3 = 12.
Vale observar que escrever este algoritmo na linguagem informal que estamos
utilizando é bastante complicado.
Na há dúvida que o primeiro algoritmo para o cálculo do mdc apresentado é de
compreensão bastante simples. Porém, comentaremos posteriormente que ele é
computacionalmente bastante ineficiente no sentido de que sua execução pode,
dependendo dos valores de x e y, demandar um tempo acima do razoável.
Por incrível que possa parecer, um dos algoritmos mais eficientes para o cálculo do
máximo divisor comum de dois números foi desenvolvido pelo matemático grego
Euclides duzentos anos Antes de Cristo. O algoritmo de Euclides nos é apresentado nas
séries intermediárias do ensino fundamental através de um esquema como o diagrama do
exemplo abaixo, cujo objetivo é determinar (de novo!) o máximo divisor comum de 120 e
84.
1 2 3
120 84 36 12
36 12 0
O esquema funciona da seguinte forma: divide-se 120 por 84 obtendo-se resto 36; a
partir daí, repetem-se divisões até que o resto seja zero, sendo o dividendo da divisão
atual o divisor da divisão anterior e o divisor da divisão atual o resto da divisão anterior.
O último divisor é o máximo divisor procurado. Como se pode ver, estas instruções
escritas desta forma não são nada compreensíveis, o que faz com elas sejam transmitidas
oralmente nas salas do ensino fundamental. No capítulo 4 (quatro), teremos a
oportunidade de discutir este algoritmo com detalhes e veremos que ele é um algoritmo
bastante interessante no desenvolvimento da lógica de programação.

12
Capítulo 1 Introdução à Programação

4. Discutiremos agora o algoritmo para o cálculo da média de uma relação


contendo um número grande (digamos, 10 000) de números dados. No caso da equação
do segundo grau, eram três os dados de entrada e, portanto, os chamamos de a, b, e c.
Mas agora são 10 000 os dados de entrada! Uma solução possível é receber os números
um a um, somando-os antes de receber o seguinte, conforme vimos na seção 1.5.
1. Chame de A o primeiro número dado.
2. Faça S = A.
3. Repita 9 999 vezes as instruções 3.1 e 3.2.
3.1 Chame de A o próximo número dado.
3.2 Substitua o valor de S por S + A.
4. Calcule M = S/10 000.
5. Forneça M para o valor da média.
Por exemplo, se a relação de números fosse {5, 3, 8, 11, ...} até a quarta execução
de 3.1 e 3.2 teríamos a seguinte tabela:
A S M
5 5
3 8
8 16
11 27
Está fácil perceber que após 9.999ª execução das instruções 3.1 e 3.2 a variável S
conterá a soma de todos os números da relação, o que justifica a instrução 4.
5. Um outro exemplo que justifica plenamente a necessidade do conhecimento do
que o processador é capaz de executar é a determinação do maior número de uma relação
de números. Se o processador for um aluno do ensino médio e a relação contiver poucos
números, uma simples olhada na relação permitirá se identificar o maior número. Mas, e
se o processador for um aluno das classes iniciais do ensino fundamental? E se a relação
contiver 10 000 números? E se os números estiverem escritos em forma de fração
ordinária?
Uma solução possível é supor que o maior número é o primeiro da relação e
comparar este suposto maior com os demais números, alterando-o quando for encontrado
um número na relação maior do que aquele que até aquele momento era o maior.
1. Chame de A o primeiro número dado.
2. Faça M = A.
3. Repita 9 999 vezes as instruções 3.1 e 3.2.
3.1 Chame de A o próximo número dado.
3.2 Se A > M substitua o valor de M por A.
4. Forneça M para o valor do maior número.
Para exemplificar, suponha que a entrada fosse o conjunto {5, 3, 8, 11, 10...}. Até a
quinta execução das instruções 3.1 e 3.2 teríamos a seguinte tabela:

13
Capítulo 1 Introdução à Programação

A M
5 5
3
8 8
11 11
10

1.7 Linguagens de alto nível


Computadores digitais foram concebidos para executarem instruções escritas em
linguagem de máquina. Isto significa que um computador é capaz de executar um
algoritmo escrito nesta linguagem (um algoritmo escrito em linguagem de máquina é
normalmente chamado de programa objeto). Nos primórdios da computação, os
algoritmos que se pretendiam que fossem executados por um computador eram escritos
em linguagem de máquina, o que tornava a tarefa de desenvolvimento de algoritmos
muito trabalhosa, devido ao fato de era necessário que se conhecesse qual seqüência de
bits correspondia à instrução pretendida. Naturalmente, esta dificuldade acontecia pelo
fato de que o ser humano não está habituado a uma linguagem com apenas dois símbolos
básicos. Um grande avanço ocorreu na computação quando se conseguiu desenvolver
programas que traduzissem instruções escritas originariamente numa linguagem dos seres
humanos para a linguagem de máquina. O surgimento de programas para esta finalidade
permitiu o desenvolvimento de algoritmos em linguagens que utilizam caracteres,
palavras e expressões de um idioma, ou seja, uma linguagem cujos símbolos básicos e
cujas palavras estão no cotidiano do ser humano. Uma linguagem com esta característica
é chamada linguagem de alto nível, onde alto nível aí não se refere à qualidade e sim ao
fato de que ela está mais próxima da linguagem do ser humano do que da linguagem da
máquina (quando alguma coisa está mais próxima da máquina do que do ser humano
dizemos que ela é de baixo nível). Como exemplo de linguagens de alto nível temos
Pascal, C, Delphi, Visual Basic, Java e C++. Um algoritmo escrito numa linguagem de
alto nível é chamado programa fonte ou simplesmente programa
Como foi dito acima, um programa fonte deve ser traduzido para a linguagem de
máquina. Há dois tipos de programas que fazem isto: os interpretadores que traduzem os
comandos para a linguagem de máquina um a um e os compiladores que traduzem todo o
programa para a linguagem de máquina. Um compilador, portanto, ao receber como
entrada um programa fonte fornece como saída um programa escrito em linguagem de
máquina, chamado programa objeto. A compilação do programa, portanto, gera um
programa que pode então ser executado. É comum nos referirmos à execução do
programa fonte quando se está executando o programa objeto.
Já um interpretador traduz para a linguagem de máquina os comandos do programa
um a um, executando-os em seguida. Assim a interpretação de um programa não gera um
programa objeto.

14
Capítulo 1 Introdução à Programação

1.8 Sintaxe e semântica de uma instrução

O que é sintaxe
Dissemos que um programa escrito em linguagem de alto nível é traduzido para a
linguagem de máquina por um compilador ou cada instrução é traduzida por um
interpretador. É natural se admitir que, para que o compilador consiga traduzir uma
instrução escrita com caracteres de algum idioma para instruções escritas como
seqüências de zeros e uns, é necessário que cada instrução seja escrita de acordo com
regras preestabelecidas. Estas regras são chamadas sintaxe da instrução e quando não são
obedecidas dizemos que existe erro de sintaxe.
Se o programa fonte contém algum erro de sintaxe, o compilador não o traduz para
a linguagem de máquina (isto é, o compilador não compila o programa) e indica qual o
tipo de erro cometido e a instrução onde este erro aconteceu. Se o programa fonte for
interpretado, ele é executado até a instrução que contém erro de sintaxe, quando então é
interrompida a sua execução e o erro é indicado.

O que é semântica
Naturalmente, cada instrução tem uma finalidade específica. Ou seja, a execução
de uma instrução resulta na realização de alguma ação, digamos parcial, e é a seqüência
das ações parciais que redunda na realização da tarefa para a qual o programa foi escrito.
A ação resultante da execução de uma instrução é chamada semântica da instrução.
Infelizmente, um programa pode não conter erros de sintaxe (e, portanto, pode ser
executado), mas a sua execução não fornecer como saída o resultado esperado para
alguma entrada. Neste caso, dizemos que o programa contém erros de lógica que, ao
contrário dos erros de sintaxe que são detectados pelo compilador ou pelo interpretador,
são, às vezes, de difícil detecção.
No nosso entendimento, para aprender a programar numa determinada linguagem é
necessário que se aprendam as instruções daquela linguagem (para que se conheça o que
o processador é capaz de fazer), a sintaxe de cada uma destas instruções e as suas
semânticas. Aliado a isto, deve-se ter um bom desenvolvimento de lógica programação
para que se escolham as instruções necessárias e a seqüência segundo a qual estas
instruções devem ser escritas, para que o programa, ao ser executado, execute a tarefa
pretendida. Felizmente ou infelizmente, para cada tarefa que se pretende não existe
apenas uma seqüência de instruções que a realize. Ou seja, dado um problema não existe
apenas um programa que o resolva. Devemos procurar o melhor programa, entendendo-
se como melhor programa um programa que tenha boa legibilidade, cuja execução
demande o menor tempo possível e que necessite, para sua execução, a utilização mínima
da memória.
Existe um conjunto de instruções que é comum a todas as linguagens de alto nível
e cujas semânticas permitem executar a maioria das tarefas. A aprendizagem das
semânticas destas instruções e das suas sintaxes em alguma linguagem de programação
15
Capítulo 1 Introdução à Programação

(aliado ao desenvolvimento da lógica de programação) permite que se aprenda com


facilidade outra linguagem do mesmo paradigma.

1.9 Sistemas de computação


Como foi dito anteriormente, a cpu de um computador é capaz de executar
instruções (escritas em linguagem de máquina, permitam a repetição). Ou seja, um
computador é capaz de executar programas e só para isto é que ele serve. Se um
computador não estiver executando um programa ele para nada está servindo. Como
foram concebidos os computadores atuais, um programa para ser executado deve estar
armazenado na sua memória. O armazenamento dos programas (e todo o gerenciamento
das interações entre as diversas unidades do computador) é feito por um programa
chamado sistema operacional. Um dos primeiros sistemas operacionais para
gerenciamento de microcomputadores foi o DOS (Disk Operating System). Quando um
computador é ligado, de imediato o sistema operacional é armazenado na memória e só a
partir daí o computador está apto a executar outros programas. Estes programas podem
ser um game, que transforma o "computador" num poderoso veículo de entretenimento;
podem ser um processador de texto, que transforma o "computador" num poderoso
veículo de edição de textos; podem ser uma planilha eletrônica, que transforma o
"computador" num poderoso veículo para manipulação de tabelas numéricas, podem ser
programas para gerenciar, por exemplo, o dia a dia comercial de uma farmácia e podem
ser ambientes que permitam o desenvolvimento de games ou de programas para gerenciar
o dia a dia comercial de uma farmácia. Talvez com exceção de um game, os programas
citados acima são, na verdade, conjuntos de programas que podem ser executados de
forma integrada. Um conjunto de programas que podem ser executados de forma
integrada é chamado software. Por seu turno, as unidades do computador, associadas a
outros equipamentos chamados periféricos, como uma impressora, constituem o
hardware. O que nos é útil é um conjunto software + hardware. Um conjunto deste tipo é
chamado de um sistema de computação. De agora em diante, os nossos processadores
serão sistemas de computação. Isto é, queremos escrever programas que sejam executado
por um sistema de computação.
Como foi dito acima, o desenvolvimento de um programa que gerencie o dia a dia
comercial de uma farmácia requer um compilador (ou um interpretador) que o traduza
para a linguagem de máquina. Antigamente, as empresas que desenvolviam compiladores
desenvolviam apenas estes programas, de tal sorte que o programador necessitava utilizar
um processador de texto à parte para edição do programa fonte. Atualmente, os
compiladores são integrados num sistema de computação que contém, entre outros:
1. Processador de texto, para a digitação dos programas fontes;
2. Depurador, que permite que o programa seja executado comando a comando, o
que facilita a descoberta de erros de lógica;
3. Help, que descreve as sintaxes e as semânticas de todas as instruções da
linguagem;
4. Linker, que permite que um programa utilize outros programas.

16
Capítulo 1 Introdução à Programação

Rigorosamente falando, um sistema constituído de um compilador e os softwares


listados acima deveria ser chamado de ambiente de programação; é mais comum,
entretanto, chamá-lo, simplesmente, de compilador.
Os ambientes de programação que utilizamos para desenvolver os programas deste
livro foram Turbo C, versão 2.01, e Turbo C++, versão 3.0, ambos desenvolvidos pela
Borland International, Inc., o primeiro em 1988 e o segundo em 1992. Como se pode ver,
são sistemas desenvolvidos há bastante tempo (as coisas em computação andam muito
mais rápido), já estando disponíveis gratuitamente na internet. Estaremos, portanto,
utilizando um compilador “puro C” e um compilador C++, que é up grade da linguagem
C para a programação orientada a objeto, paradigma que não está no escopo deste livro.

1.10 Exercícios propostos


1. Três índios, conduzindo três brancos, precisam atravessar um rio dispondo para
tal de um barco cuja capacidade é de apenas duas pessoas. Por questões de segurança, os
índios não querem ficar em minoria, em nenhum momento e em nenhuma das margens.
Escreva um algoritmo que oriente os índios para realizarem a travessia nas condições
fixadas. (Cabe observar que, usualmente, este exercício é enunciado envolvendo três
jesuítas e três canibais. A alteração feita é uma modesta contribuição para o resgate da
verdadeira história dos índios).
2. O jogo conhecido como Torre de Hanói consiste de três torres chamadas origem,
destino e auxiliar e um conjunto de n discos de diâmetros diferentes, colocados na torre
origem na ordem decrescente dos seus diâmetros. O objetivo do jogo é, movendo um
único disco de cada vez e não podendo colocar um disco sobre outro de diâmetro menor,
transportar todos os discos para torre destino, podendo usar a torre auxiliar como
passagem intermediária dos discos. Escreva algoritmos para este jogo nos casos n = 2 e
n = 3.
3. Imagine que se disponha de três esferas numeradas 1, 2 e 3 iguais na forma, duas
delas com pesos iguais e diferentes do peso da outra. Escreva um algoritmo que, com
duas pesagens numa balança de dois pratos, determine a esfera de peso diferente e a
relação entre seu peso e o peso das esferas de pesos iguais.
4. A média geométrica de n números positivos é a raiz n-ésima do produto destes
números. Supondo que o processador é capaz de calcular raízes n-ésimas, escreva um
algoritmo para determinar a média geométrica de n números dados.
5. Sabendo que o dia 01/01/1900 foi uma segunda-feira, escreva um algoritmo que
determine o dia da semana correspondente a uma data, posterior a 01/01/1900, dada. Por
exemplo, se a data dada for 23/01/1900, o algoritmo deve fornecer como resposta terça-
feira.
6. O show de uma banda de rock, que será realizado na margem de um rio, deve
começar exatamente às 21 h. Atrasados, às 20 h 43 min, os quatro integrantes da banda
estão na outra margem do rio e necessitam, para chegar ao palco, atravessar uma ponte.
Há somente uma lanterna e só podem passar uma ou duas pessoas juntas pela ponte, e
sempre com a lanterna. Cada integrante possui um tempo diferente para atravessar a

17
Capítulo 1 Introdução à Programação

ponte: o vocal leva 10 minutos, o guitarrista 5 minutos, o baixista 2 minutos e o baterista


1 minuto. Evidentemente, quando dois atravessam juntos, o tempo necessário é o do mais
lento. Escreva um algoritmo que permita que a banda atravesse a ponte de modo que o
show comece na hora marcada.

18
2. Introdução à Linguagem C
2.1 Variáveis simples
O que é uma variável
No capítulo 1 foi dito que uma das unidades básicas de um computador é a
memória, cuja finalidade é armazenar dados e informações que serão manipulados pela
unidade central de processamento; naquele capítulo também foi dito que os programas
para serem executados devem estar armazenados na memória. Ou seja, a memória
armazena programas que serão executados e dados que estes programas vão manipular.
Naturalmente, os dados que o programa vai manipular podem ser dados de entrada ou
dados gerados pela execução do programa.
Para que a memória possa armazenar dados e informações, ela é dividida em
partes, chamadas posições de memória. O sistema operacional que gerencia o sistema de
computação pode acessar cada uma destas posições para armazenar tais dados. Para que o
acesso às posições de memória seja possível, a cada uma delas está associada uma
seqüência de bit’s, chamada endereço da posição de memória. Como uma seqüência de
bit's corresponde a um número inteiro escrito no sistema binário, cada endereço pode ser
visto como um inteiro escrito no sistema decimal. Assim temos posições de memória de
endereço 1209 ou 2114, por exemplo.
Uma variável simples (ou simplesmente variável) é uma posição de memória cujo
conteúdo pode ser modificado durante a execução de um programa. A referência a uma
variável no programa é feita através do seu identificador; os valores que podem ser nela
armazenados dependem do seu tipo de dado.

O Identificador
O identificador é uma seqüência de letras, dígitos e caractere para sublinhamento
escolhida pelo programador e (como foi dito acima) será utilizado no programa para se
fazer referência àquela variável (o primeiro caractere do identificador não pode ser um
dígito). Como um programa deve ser legível por outros programadores (e pelo próprio
programador), é uma boa prática se escolher um identificador de uma variável que tenha
alguma relação com a sua finalidade. Se uma variável deve armazenar uma soma, um
identificador muito bom para ela será Soma. Se uma variável vai receber números, ela
poderia ser identificada por Num ou por Numero. Os compiladores da linguagem C fazem
distinção entre letras maiúsculas e minúsculas e, portanto, Numero e numero são dois
identificadores diferentes. Manteremos, de um modo geral, a seguinte convenção ao
longo do texto: quando um identificador possuir mais de um caractere, iniciá-lo-emos por
letra maiúscula e quando o identificador tiver um único caractere, utilizaremos letra
minúscula.
Capítulo 2 Introdução à Linguagem C
Como veremos ao longo do livro, a linguagem C fixa alguns identificadores para a
sintaxe de suas instruções. Estes identificadores não podem ser utilizados nos programas,
sendo conhecidos por palavras reservadas. Algumas das palavras reservadas em C são:
Tabela 2 Palavras reservadas da linguagem C
Auto Double Int struct
Break Else Long switch
Case Enum Register typedef
Char Extern Return union
Const Float Short unsigned
Continue For Signed void
Default Goto Sizeof volatile
Do If Static while

O tipo de dado
O tipo de dado associado a uma variável é o conjunto dos valores que podem ser
nela armazenados. A linguagem C dispõe dos tipos de dados discriminados na tabela a
seguir.

Tabela 3 Tipos de dados da Linguagem C


Denominação Número de Bytes Conjunto de valores
Char 1 caracteres codificados no código ASCII
Int 2 números inteiros de –32768 a 32767
Long ou long int 4 números inteiros de –65536 a 65535
Float 4 números reais de –3,4x1038 a –3,4x10-38 e
3,4x10-38 a 3,4x1038
Double 8 números reais de –1,7x10308 a -1,7x10-308 Excluído: é
e 1,7x10-308 a 1,7x10308
Void 0 conjunto vazio
A utilização do tipo void será melhor explicada no capítulo 5, quando estudarmos
funções.
Uma observação importante é que os tipos float e double, rigorosamente falando,
não armazenam números reais e sim números de um sistema de ponto flutuante, que não
contém todos os reais entre dois números dados. O estudo de sistemas de ponto flutuante
foge ao escopo deste livro e é feito, normalmente, em disciplinas do tipo Organização e
Arquitetura de Computadores e Cálculo Numérico.
Vale lembrar que, de um modo geral, um byte contém oito bit's e cabe ressaltar
que, em algumas situações, é importante se conhecer a quantidade necessária de bytes
para uma variável de um determinado tipo.

20
Capítulo 2 Introdução à Linguagem C

Declaração de variáveis
Para que o sistema de computação possa reservar as posições de memória que
serão utilizadas pelo programa, associar identificadores aos endereços destas posições de
memória e definir a quantidade de bytes de cada posição de acordo com o tipo de dado
pretendido, um programa escrito em C deve conter a declaração de variáveis, feita
através da seguinte sintaxe:
Tipo de dado Lista de identificadores;
Por exemplo, um programa para determinar a média de uma relação de números
dados pode ter a seguinte declaração:
int Quant;
float Num, Soma, Media;
A ideia é que Quant seja utilizada para armazenar a quantidade de números; Num
para armazenar os números (um de cada vez); Soma para armazenar a soma dos números;
e Media para armazenar a média procurada.
Nas seções 2.7 e 2.9 veremos as instruções em C para o armazenamento em
variáveis de dados de entrada e de dados gerados pela execução do algoritmo. Um valor
armazenado em uma variável é comumente referido como sendo o conteúdo da variável
ou o valor da variável. Também é comum se referir ao identificador da variável como
sendo a própria variável.

2.2 Constantes
Como uma variável, uma constante também é uma posição de memória à qual
devem ser associados um identificador e um tipo de dado. O que caracteriza uma
constante (e daí sua denominação, emprestada da matemática) é o fato de que o conteúdo
de uma constante não pode ser modificado durante a execução do programa. Este
conteúdo é fixado quando da declaração da constante o que deve ser feito de acordo com
a seguinte sintaxe:
const Tipo de Dado Identificador = Valor;
Por exemplo, um programa para processar cálculos químicos poderia ter
uma declaração do tipo
const float NumAvogadro = 6.023E+23;
onde a expressão 6.023E+23 é a forma que os compiladores C utilizam para representar
os valores do tipo float na notação científica, ou seja 6.023E+23 = 6.023 x 1023.
Um programa para cálculos de áreas de figuras planas, perímetros de
polígonos inscritos em circunferências, etc., pode ria ter uma declaração do tipo
const float Pi = 3.1416;

21
Capítulo 2 Introdução à Linguagem C
Esta declaração é desnecessária quando o sistema utilizado é o Turbo C++
3.0, pois esse sistema disponibiliza uma constante pré-definida, identificada por M_PI,
que pode ser utilizada em qualquer parte do programa e cujo "valor" é uma aproximação
do número irracional π.

2.3 Expressões aritméticas


Os compiladores da linguagem C, como era de se esperar, são capazes de
avaliar expressões aritméticas que envolvam as operações binárias de multiplicação,
divisão, soma e subtração e a operação unária de troca de sinal. Para isto são usados os
seguintes operadores aritméticos binários:
Tabela 4 Operadores aritméticos
Operador Operação
+ adição
- subtração
* multiplicação
/ divisão

e o operador aritmético unário (-) para a troca de sinal.


Esses operadores atuam com operandos do tipo int ou do tipo float. Se um
dos operandos for do tipo float o resultado da operação será do tipo float; se os dois
operandos forem do tipo int o resultado é também deste tipo. No caso do operador de
divisão /, se os dois operandos forem do tipo int o resultado da operação é do tipo int e
igual ao quociente da divisão do primeiro operando pelo segundo. Por exemplo, o
resultado de 30/4 é 7. Se quisermos a divisão decimal teremos de escrever 30.0 / 7.0 ou
30.0 / 7 ou 30 / 7.0. Ou seja cada uma destas divisões é igual a 7.5 e este valor, tal como
ele é, pode ser armazenado numa variável do tipo float. O que acontece é que no
armazenamento de um valor do tipo float numa variável do tipo int a parte decimal do
valor é desprezada, só sendo armazenada a parte inteira do número.
Uma expressão que envolva diversas operações é avaliada de acordo com as regras
de prioridade da matemática: em primeiro lugar é realizada a operação troca de sinal, em
seguida são realizadas as multiplicações e divisões e, finalmente, as somas e subtrações.
Por exemplo, a expressão 8 + 2*-3 é avaliada como 8 + (-6) = 2. Naturalmente, a
prioridade pode ser alterada com a utilização de parênteses: a expressão (8 + 2)*-3 resulta
em 10*(-3) = -30. Embora, o sistema não exija, vamos sempre utilizar parênteses para
separar o operador unário para troca de sinal de algum operador binário. Assim, 8 + 2*-3
será indicada por 8 + 2*(-3). Uma expressão sem parênteses contendo operadores de
mesma prioridade é avaliada da esquerda para direita. Por exemplo, 10/2*3 é igual a
(10/2)*3 = 5*3 = 15.
Operandos podem ser conteúdos de variáveis. Neste caso, o operando é indicado
pelo identificador da variável (é para isto que serve o identificador, para se fazer
referência aos valores que na variável estão armazenados).

22
Capítulo 2 Introdução à Linguagem C
Além dos operadores aritméticos usuais, os compiladores C disponibilizam o
operador módulo, indicado por %, que calcula o resto da divisão do primeiro operando
pelo segundo. Por exemplo, 30 % 4 = 2 e 5 % 7 = 5. Este operador atua apenas em
operandos do tipo int, resultando um valor deste mesmo tipo. Por exemplo, se S é uma
variável do tipo float, a expressão S % 5 gerará um erro de compilação. Uma expressão
do tipo 30.0 % 7 também gerará erro de compilação, pelo fato de que um dos operandos
não é inteiro. Este erro é indicado pelo sistema pela mensagem Illegal use of floating
point in function ... (Uso ilegal de de tipo float na função ...), onde as reticências estão
substituindo o identificador da função, como será discutido posteriormente.

2.4 Relações
Os ambientes que implementam a linguagem C realizam comparações entre
valores numéricos, realizadas no sentido usual da matemática. Essas comparações são
chamadas relações e são obtidas através dos operadores relacionais > (maior do que), >=
(maior do que ou igual a), < (menor do que), <= (menor do que ou igual a), == (igual) e
!= (diferente). O resultado da avaliação de uma relação é 1 (um), se a relação for
matematicamente verdadeira, ou 0 (zero), se a relação for matematicamente falsa. Assim,
3 > 5 resulta no valor 0 (zero), enquanto que 7 <= 7 resulta no valor 1 (um). Sendo um
valor 1 (um) ou 0 (zero), o resultado da avaliação de uma relação pode ser armazenado
numa variável do tipo int.
Os operandos de uma relação podem ser expressões aritméticas. Nestes
casos, as expressões aritméticas são avaliadas em primeiro lugar para, em seguida, ser
avaliada a relação. Por exemplo, a relação 3*4 - 5 < 2*3 - 4 resulta no valor 0 (zero), pois
3*4 - 5 = 7 e 2*3 - 4 = 2. Isto significa que os operadores relacionais têm prioridade mais
baixa que os aritméticos.

2.5 Expressões lógicas


Os compiladores C também avaliam expressões lógicas obtidas através da
aplicação dos operadores lógicos binários &&, || e ^ a duas relações ou da aplicação do
operador lógico unário ! a uma relação.
Se R1 e R2 são duas relações, os resultados da aplicação dos operadores
lógicos binários, de acordo com os valores de R1 e R2, são dados na tabela abaixo.
Tabela 5 Avaliação de expressões lógicas
R1 R2 (R1)&&(R2) (R1)||(R2) (R1) ^ (R2)
1 1 1 1 0
1 0 0 1 1
0 1 0 1 1
0 0 0 0 0
Ou seja, uma expressão lógica do tipo (R1)&&(R2) só recebe o valor 1 (um) se os
valores de R1 e de R2 forem iguais a 1 (um); uma expressão lógica do tipo (R1)||(R2) só

23
Capítulo 2 Introdução à Linguagem C
recebe o valor 0 (zero) se os valores de R1 e de R2 forem iguais a 0 (zero); uma expressão
lógica do tipo (R1) ^ (R2) só recebe o valor 1 (um) se apenas um dos valores de R1 e R2
for igual a 1. O leitor já deve ter percebido que o operador && age como o conectivo e da
nossa linguagem; o operador || atua como o nosso e/ou e o operador ^ como o conectivo
ou.
A aplicação do operador unário ! simplesmente inverte o valor original da
relação:
Tabela 6 Operador unário !
R1 !R1
1 0
0 1
Considerando que os operadores &&, || e ^ possuem o mesmo grau de
prioridade, se uma expressão sem parênteses possuir mais de uma relação, ela será
avaliada da esquerda para direita. O operador unário ! tem prioridade em relação aos
operadores binários. Assim, ! (5 > 3) || (5 < 3) tem valor 0 (zero), pois ! (5 > 3) é uma
relação falsa e 5 < 3 também é. Considerando que os operadores lógicos têm prioridade
mais baixa que os operadores relacionais, os parênteses nas expressões acima são
desnecessários; porém entendemos que a colocação deles facilita a leitura da expressão.
Os sistemas C 2.01 e C++ 3.0 também disponibilizam os operadores lógicos
& e | cujas aplicações são idênticas às aplicações de && e ||, respectivamente. A diferença
entre & e &&, por exemplo, é a seguinte: se em R1 && R2 o valor de R1 for 0 (zero) o
valor R2 não é mais avaliado, enquanto que em R1 & R2 o valor de R2 é avaliado,
independentemente do valor de R1.

2.6 Estrutura de um programa em C


Estamos tentando aprender a escrever programas na linguagem C. Já vimos
que se o programa necessitar manipular variáveis, estas devem ser declaradas. Veremos
no capítulo 5 que um programa pode conter funções. Na verdade, veremos que um
programa em C é um conjunto de funções definidas pelo programador, funções que
utilizarão outras funções definidas pelo programador e algumas funções oferecidas pelo
sistema (as funções oferecidas pelo sistema são chamadas funções de biblioteca ou
funções pré-definidas). Veremos no citado capítulo que uma função deve ser definida
com a seguinte estrutura.
Tipo de Dado Identificador da função(Lista de parâmetros)
{
Declaração de variáveis
Seqüência de instruções
}
onde o significado de Lista de parâmetros será explicado no capítulo já referido e a
Seqüência de instruções contém comandos, ativações de funções pré-definidas e

24
Capítulo 2 Introdução à Linguagem C
ativações de funções definidas pelo usuário no próprio programa ou em outros
programas.
Todo programa em C deve conter uma função identificada por main (cuja
tradução é principal), com lista de parâmetros vazia e tipo de dado não obrigatório. Esta
será sempre a primeira função do programa a ser executada. Desta forma, o menor
programa em C é
main()
{
}
Este programa pode ser executado, mas nada realiza, devido ao fato de que ele não
contém nenhuma instrução. Observe que todo o corpo da função deve estar disposto entre
chaves. As chaves são utilizadas em outros pontos de um programa e são chamadas
delimitadores. Os delimitadores, o identificador main e os parênteses, dispostos ao lado
do identificador main, são os únicos elementos obrigatórios de um programa. Como os
compiladores C ignoram espaços em branco, caracteres de tabulação e caracteres de
mudança de linha, não existe um estilo obrigatório de se editar programas em C. Por
exemplo, o programa acima poderia ser escrito de uma das seguintes maneiras.
main
(
)
{
}
ou
main(){}
ou
main(
)
{}
Evidentemente, um programador em C deve procurar escrever seus programas num
estilo que ofereça uma boa legibilidade, o que vai facilitar a sua compreensão por outra
pessoa e a descoberta de possíveis erros de lógica.

2.7 Entrada dos dados de entrada


A função scanf()
Vimos anteriormente que a maioria dos programas manipula dados que são
fornecidos pelo usuário durante a execução do programa. Estes dados, repetindo,
constituem a entrada do programa e, naturalmente, devem ser armazenados em variáveis.
Por exemplo, um programa para determinação das raízes de uma equação do segundo
25
Capítulo 2 Introdução à Linguagem C
grau deve receber como entrada os valores dos três coeficientes da equação: são estes
valores que identificam a equação.
De um modo geral, os compiladores C permitem a recepção de dados de entrada (e
o conseqüente armazenamento em variáveis) através da função de biblioteca scanf() que
possui a seguinte sintaxe:
scanf(Expressão de controle, Lista de variáveis);
Aí, Expressão de controle deve ser escrita entre aspas e contém os códigos de
conversão que indicam como o sistema deve armazenar os dados digitados no teclado e
caracteres que o usuário deve digitar separando a digitação destes dados. Na Lista de
variáveis as variáveis são separadas por vírgulas e cada uma delas deve ser precedida do
operador de endereço &. Este operador indica o endereço da posição de memória
definida para a variável identificada na lista de variáveis.
Quando da execução desta função, a janela de edição é substituída pela janela do
usuário e o sistema fica aguardando que o usuário digite um número de valores igual ao
número de variáveis da lista de variáveis (à medida que são digitados, os valores
aparecem na tela do usuário). A conclusão da entrada dos dados é feita com a digitação da
tecla <enter> e quando isto é feito, o sistema armazena os dados digitados na variável
respectiva (no sentido da ordem da colocação da variável na lista e da digitação do valor),
de acordo com o código de conversão.
Por exemplo,
#include <stdio.h>
main()
{
int Anos
scanf("%d", &Anos)
}
é um programa em C que armazena um valor inteiro digitado no teclado na variável Anos
(ou seja, para nada serve, pois o inteiro armazenado naquela posição de memória
“esvanece” quando a execução do programa é encerrada).
Quando se vai dar entrada em mais de um dado através de uma mesma ativação da
função scanf(), pode-se fixar caracteres que deverão ser digitados quando da entrada dos
dados. Por exemplo, se pretendemos dar entrada numa data, podemos ter o seguinte
trecho de programa:
#include <stdio.h>
main()
{
int Dia, Mes, Ano;
scanf("%d/%d/%d", &Dia, &Mes, &Ano);
. . .
}
e a data pretendida deve ser digitada no formato dd/mm/aaaa, devendo o usuário digitar

26
Capítulo 2 Introdução à Linguagem C
as barras entre as digitações do dia e do mês e deste e do ano. A conclusão se dá com a
digitação da tecla <enter>, como se estivesse digitando um único dado. A digitação das
barras é necessária pelo fato de que elas estão separando os códigos de formatação na
expressão de controle. Se após a digitação do valor da variável Dia for acionada a tecla
<enter>, a execução da função é encerrada e os valores de Mes e Ano não podem ser
digitados.
Se não há nenhum caractere separando os códigos de formatação, a digitação dos
dados pode ser intercalada pela digitação da tecla <enter> ou da <barra de espaços>. É o
caso, por exemplo, do trecho de programa
main()
{
int i;
char c;
scanf("%c %d", &c, &i);
. . .
}
onde as digitações do caractere que se pretende armazenar na variável c e do inteiro a ser
armazenado em i devem ser separadas pelo acionamento da tecla <enter> ou da <barra de
espaço>. É necessário notar que a digitação de um valor de um tipo diferente do tipo da
variável não provoca erro de execução, mas, evidentemente, pode provocar erro de lógica
do programa. Por exemplo, se na execução do comando scanf("%c %d", &c, &i);
digitarmos w<enter>5.9<enter>, o caractere w é armazenado na variável c e o inteiro 5 é
armazenado na variável i. Portanto, se o dado de entrada fosse realmente 5.9, o resultado
do processamento poderia ser fornecido com erros. Se o dado de entrada poderia ser 5.9,
a variável para seu armazenamento deveria ter sido definida com float.

Os códigos de conversão e a instrução #include


<stdio.h>
Os códigos de conversão de acordo com o tipo de dado da variável onde os valores
digitados serão armazenados são apresentados na tabela a seguir.
Tabela 7 Códigos de conversão da função scanf()
Código Elemento armazenado
%c um único caractere
%d ou %i um inteiro do sistema decimal
%o um inteiro do sistema octal
%x um inteiro do sistema hexadecimal
%ld um valor do tipo long
%e um número na notação científica
%f um número em ponto flutuante
%s uma cadeia de caracteres

27
Capítulo 2 Introdução à Linguagem C
A instrução #include <stdio.h> que precede a função main() é necessária pelos
seguintes fatos. Como dissemos acima, para se definir uma função é necessário fixar o
tipo de dado que ela retorna, o identificador da função e a lista de parâmetros, com seus
identificadores e seus tipos de dados; este conjunto de elementos é chamado protótipo da
função. Para que a função main() ative uma outra função (seja uma função definida pelo
usuário ou uma função de biblioteca), o seu protótipo deve ser definido antes ou no
interior da função main(). Os protótipos das funções do sistema encontram-se reunidos,
de acordo com objetivos semelhantes, em arquivos chamados arquivos de cabeçalhos
(header files) (o cabeçalho de uma função inclui o seu protótipo, as variáveis declaradas
dentro da função e outras declarações e definições que não são instruções propriamente
ditas). A instrução #include <stdio.h> "anexa" à função main() os protótipos das funções
de biblioteca que executam ações padrões de entrada e de saída (stdio vem de standard
input output, entrada e saída padrão e h é a extensão padrão dos arquivos de cabeçalhos).
A não inclusão de um include provoca erro de compilação no sistema C++ 3.01. Isto não
acontece no C 2.01, porém, há casos em que esta não inclusão gera erros de lógica (a
entrada de dados não é feita do modo que se esperava).

2.8 Saída de dados

A função printf()
A exibição dos resultados do processamento e de mensagens é feita através da
função pré-definida printf(), cujo protótipo está contido também no arquivo stdio.h. Sua
sintaxe é a seguinte:
printf(Expressão de controle, Lista de argumentos);
onde Expressão de controle contém mensagens que se pretende que sejam exibidas,
códigos de formatação (idênticos aos códigos de conversão da função scanf()) que
indicam como o conteúdo de uma variável deve ser exibido e códigos especiais para a
exibição de alguns caracteres especiais e realização de ações que permitam formatar a
saída do programa. A Lista de argumentos pode conter identificadores de variáveis,
expressões aritméticas ou lógicas e valores constantes. No primeiro caso, o conteúdo da
variável é exibido; no segundo caso, a expressão é avaliada e o seu resultado é exibido;
no terceiro caso o valor constante é exibido. A ordem de exibição dos conteúdos de
variáveis, dos resultados das expressões e dos valores constantes relacionados na lista de
argumentos é dada pela ordem em que estes elementos estão listados; a posição dentro da
mensagem contida na expressão de controle é fixada pela posição do código de
formatação respectivo. Quando, na expressão de controle, um código de formatação é
encontrado o conteúdo da variável, o resultado da expressão ou o valor constante
respectivo (no sentido da ordem da colocação da variável na lista e da colocação do
código de formatação na expressão de controle) é exibido.

28
Capítulo 2 Introdução à Linguagem C
Por exemplo, a função printf() no programa abaixo contém uma expressão de
controle que não possui códigos de formatação. Isto significa que apenas a mensagem
será exibida. Assim, o programa
#include <stdio.h>
main()
{
printf("Estou aprendendo a programar em C");
}
é um programa em C que faz com que seja exibida na tela a mensagem Estou aprendendo
a programar em C.
Já o programa abaixo, contém uma função printf() que possui quatro caracteres de
controle
#include <stdio.h>
main()
{
float a, b, c;
scanf("%f %f %f", &a, &b, &c);
printf("%f , %f e %f %f", a, b , c, (a + b + c)/3);
}
Quando da execução deste programa, o sistema, para execução da função scanf(),
aguarda que sejam digitados três valores numéricos. Quando isto é feito, o sistema
armazena estes três valores nas variáveis a, b e c, respectivamente. Para execução do
último comando, o sistema exibe os valores armazenados nas variáveis a, b e c, em
seguida avalia a expressão (a + b + c)/3 e exibe o seu valor na tela. Assim, o programa
fornece a média aritmética de três números dados.
Como um outro exemplo e considerando que o resultado de uma expressão lógica é
um inteiro, o programa
#include <stdio.h>
main()
{
printf("%d", 5 > 3);
}
exibe na tela o valor 1, pois a relação 5 > 3 é verdadeira.
Nos dois exemplos anteriores, utilizamos expressões, uma aritmética e uma lógica,
como argumentos de uma função printf(). No nosso entendimento, não é uma boa prática
de programação se utilizar expressões como argumentos de uma função printf(). Se o
valor de uma expressão é útil para alguma coisa, ele deve ser armazenado em alguma
variável (veremos isto na próxima seção) e esta deve ser utilizada para o fornecimento de
resultados.

29
Capítulo 2 Introdução à Linguagem C

Facilitando a execução de um programa


A possibilidade de que mensagens possam ser exibidas permite que o próprio
programa facilite a sua execução e que torne compreensíveis os resultados fornecidos. Da
forma em que está escrito, a execução do programa acima que fornece a média de três
números dados é dificultada pelo fato de que a execução da função scanf() faz com que o
sistema aguarde a digitação dos números pretendidos (o cursor fica simplesmente
piscando na tela do usuário) e o usuário pode não saber o que está se passando. Além
disto, a execução da função printf() exibe apenas o resultado da expressão, sem indicação
a que aquele valor se refere. Assim, o programa referido ficaria muito melhor da seguinte
forma.
#include <stdio.h>
main()
{
float a, b, c;
printf("Digite três números");
scanf("%f %f %f", &a, &b, &c);
printf("A media dos numeros %f , %f e %f é igual a %f", a, b, c, (a + b + c)/3);
}
A exibição de uma mensagem pode ser também obtida através da função puts(),
cujo protótipo esta no arquivo stdio.h. Por exemplo, o comando printf(“Digite três
números”) pode ser substituído pelo comando puts(“Digite três números”).

Fixando o número de casas decimais


O padrão utilizado pela maioria dos compiladores C é exibir os números de ponto
flutuante com seis casas decimais. O número de casas decimais com as quais os números
de ponto flutuante serão exibidos pode ser alterado pelo programa. Para isso deve-se
acrescentar .n ao código de formatação da saída, sendo n o número de casas decimais
pretendido. Por exemplo, se o programa que determina a média de três números fosse
executado para a entrada 6.2, 8.45 e 7 seria exibido na tela o seguinte resultado
A media dos numeros 6.200000, 8.550000 e 7.000000 é igual a 7.250000
Se o comando de saída do programa fosse
printf("A media dos numeros %.2f , %.2f e %.2f é igual a %.1f", a, b, c, (a + b +
c)/3);
a saída, para a entrada acima, seria
Excluído: m
A media dos números 6.20, 8.55 e 7.00 é igual a 7.3
Observe que a média dos números dados, de fato, é igual a 7.26. Como o
código da formatação da saída da média foi %.1f, ela foi exibida com uma casa decimal e

30
Capítulo 2 Introdução à Linguagem C
o sistema efetua os arredondamentos necessários. Observe também a utilização do ponto
(e não da vírgula) como separador das partes inteiras e fracionárias. Isto é sempre
necessário quando o ambiente de programação que se está utilizando foi desenvolvido
nos Estados Unidos, o que é o mais freqüente.

Alinhando a saída
O programa pode fixar a coluna da tela a partir da qual o conteúdo de uma
variável, ou o valor de uma constante ou o valor de uma expressão será exibido. Isto é
obtido acrescentado-se um inteiro m ao código de formatação. Neste caso, m indicará o
número de colunas que serão utilizadas para exibição do conteúdo da variável ou do valor
da constante. Por exemplo, levando-se em conta que a frase "Estou aprendendo a
programar" contém vinte e oito caracteres, o programa abaixo
#include <stdio.h>
main()
{
printf("%38s", "Estou aprendendo a programar");
}
exibe na tela a frase referida a partir da décima coluna.
Observe que este programa também exemplifica a utilização de uma constante (no
caso, uma cadeia de caracteres) como um argumento da função printf(). Observe também
que referências a constantes do tipo cadeia de caracteres devem ser feitas com a cadeia
escrita entre aspas. As aspas distinguem para o sistema uma cadeia de caracteres
constante de um identificador de variável. O mesmo efeito poderia ser obtido com o
programa
#include <stdio.h>
main()
{
printf(" Estou aprendendo a programar");
}
onde existem dez espaços em branco entre o abre aspas e a letra E.
Para se fixar a coluna a partir da qual e o número de casa decimais com que um
número de ponto flutuante será exibido, deve-se utilizar dois parâmetros separados por
um ponto. Por exemplo, considerando que se exibirmos o número 3.1416 com duas casas
decimais ele ficará com quatro caracteres, o programa
#include <stdio.h>
main()
{
printf("%14.2f", 3.1416);
}
exibirá na tela 3.14 a partir da décima coluna.

31
Capítulo 2 Introdução à Linguagem C
O recurso da exibição de valores utilizando um número pré-definido de colunas
pode ser utilizado para alinhar à direita a saída do programa. Por exemplo, se os
conteúdos das variáveis do tipo float x, y e z são 103.45, 5.3678 e 45.0, a seqüência de
comandos
printf("%13.2f", x);
printf("%13.2f", y);
printf("%13.2f", z);
exibe na tela
103.45
5.37
45.00
Vale observar que é possível obter o mesmo efeito com uma única ativação (ou
chamada) da função printf(), como veremos na seção seguinte:
printf("%13.2f \n %13.2f \n %13.2f ", x, y, z);

Exibindo números "como caracteres" e vice-versa


Uma variável do tipo char armazena os códigos ASCII dos caracteres suportados
pelo sistema. Como este armazenamento é feito através de cadeias de bit's, na verdade, o
que é armazenado são números inteiros compreendidos entre –128 e 127. Aos números de
0 a 127 correspondem os caracteres de código ASCII iguais ao próprio número e aos
números de -1 a -128 correspondem os caracteres de código ASCII iguais aos números
situados no intervalo de 128 a 255, respectivamente.
O código de formatação da saída é que indicará a forma como o conteúdo de uma
variável do tipo char será exibido. Se c é uma variável do tipo char, pode-se associar a
sua saída com os códigos %d, %o, %x, %c. No primeiro caso o número armazenado em c
é exibido; no segundo caso este número será exibido no sistema octal; no terceiro, o
número será exibido no sistema hexadecimal e no último caso será exibido o caractere
como comentado acima. Por exemplo, se o conteúdo da variável char c é 67, o comando
printf("%c %d %o %x", c, c, c, c);
exibirá na tela
C 67 103 43
A razão é disto é que 67 é o código ASCII de C no sistema decimal, 103 é este
código no sistema octal e 43 é o código ASCII de C no sistema hexadecimal.
Quando um argumento é uma constante, ele será exibido de forma que depende do
código de formatação. Quando a constante é uma cadeia de caracteres, não há muito o
que fazer: a execução do comando
printf("Este comando exibirá a palavra %s", "paz");

32
Capítulo 2 Introdução à Linguagem C
exibirá na tela a frase
Este comando exibirá a palavra paz
da mesma maneira que o comando
printf("Este comando exibirá a palavra paz");
que é muito mais simples.
Porém, quando a constante é um caractere ou um número inteiro, o que será
exibido depende do código de formatação. Por exemplo, o comando
printf("%c", 'A');
exibe o caractere A, enquanto que o comando
printf("%d", 'A');
exibirá o número 65. Por sua vez, o comando
printf("%d", 65);
exibe o número 65, enquanto que o comando
printf("%c", 65);
exibe o caractere A.
Observe que referências a constantes caracteres é feita com o caractere escrito
entre apóstrofos, enquanto que referências a cadeias de caracteres são feitas com as
cadeias escritas entre aspas, como já foi observado antes.

Os códigos especiais
De acordo com a tabela abaixo, os códigos especiais, como foi dito acima,
permitem a exibição de alguns caracteres, como %, \, dentro de uma mensagem e a
realização de ações que permitem a formatação da saída de modo que esta seja elegante e
agradável para o usuário.
Tabela 9 Códigos especiais da função printf()
Código Ação
\n leva o cursor para a próxima linha
\t executa uma tabulação
\b executa um retrocesso
\f leva o cursor para a próxima página
\a emite um sinal sonoro (beep)
\" exibe o caractere "
\\ exibe o caractere \
\% exibe o caractere %

33
Capítulo 2 Introdução à Linguagem C
Uma observação interessante é que o código \a pode ser obtido através do caractere
de código ASCII igual a 7. Assim, a execução dos comandos printf("\a"); e printf("%c",
7); realizam a mesma ação de emissão de um sinal sonoro.
A utilização do código \n permite que a exibição de constantes ou de conteúdos de
variáveis através da função printf() possa ser feita em linhas diferentes. No exemplo dado
na seção anterior sobre o alinhamento da saída dos dados, a saída poderia ser feita com
uma única chamada da função printf(). Repetindo o referido exemplo, se os conteúdos das
variáveis do tipo float x, y e z são 103.45, 5.3678 e 45.0, o comando
printf("%13.2f"/n %13.2f \n %13.2f", x, y, z);
exibe na tela
103.45
5.37
45.00

2.9 Comando de atribuição

Armazenando dados gerados pelo programa


A seção 2.7 apresentou o comando que permite que se armazene em variáveis a
entrada do programa. Agora veremos como armazenar dados gerados durante a execução
de um programa. Considere um programa para o cálculo da média de uma relação de
números. Naturalmente, a quantidade de números da relação (se não foi fornecida a
priori) deve ser de alguma forma determinada e armazenada em alguma variável para que
possa ser utilizada no cálculo final da média pretendida.
O armazenamento de dados gerados pelo próprio programa, alterações no conteúdo
de variáveis e determinações de resultados finais de um processamento são realizados
através do comando de atribuição, que deve ser escrito com a seguinte sintaxe.
Identificador de variável = expressão;
A expressão do segundo membro pode se resumir a um valor constante pertencente
ao tipo de dado da variável do primeiro membro, caso em que o valor é armazenado
naquela variável. Se não for este o caso, a expressão é avaliada e, se for do mesmo tipo da
variável do primeiro membro, o resultado é armazenado na variável.
A expressão do segundo membro pode envolver a própria variável do primeiro
membro. Neste caso, o conteúdo anterior da variável será utilizado para a avaliação da
expressão e será substituído pelo valor desta expressão. Por exemplo, se i é uma variável
do tipo int ou do tipo float o comando i = i + 1; faz com que o seu conteúdo seja
incrementado de uma unidade.
Veremos ao longo do livro que comandos do tipo i = i + 1; aparecem com muita
freqüência. A linguagem C oferece uma forma simplificada de escrever este comando:
i++;. Esta sintaxe se tornou tão característica da linguagem C que sua "ampliação" para

34
Capítulo 2 Introdução à Linguagem C
incorporar recursos de programação orientada a objetos foi denominada C++ (de forma
semelhante, o comando i = i – 1 pode ser escrito i--;). O incremento de uma variável de
uma unidade também pode ser obtido através do comando ++i e estas expressões podem
figurar em expressões aritméticas. A diferença entre i++ e ++i pode ser entendida no
seguinte exemplo.Aa seqüência de comandos
i = 2;
j = i++;
k = ++i;
realiza as seguintes ações:
i = 2, armazena em i o valor 2;
j = i++, armazena em j o valor 2 e armazena em i o valor 3 (incrementa o valor de
i);
k = ++i, armazena em i o valor 4 (incrementa o valor de i) e armazena o valor 4 na
variável j.

Um exemplo simples: determinando a parte


fracionária de um número
Como dissemos na seção 2.3, o armazenamento de um valor de ponto flutuante
numa variável do tipo int faz com que seja armazenada na variável a parte inteira do valor
de ponto flutuante. Isto permite que se extraia facilmente a parte fracionária de um
número. Por exemplo, o programa a seguir fornece a parte fracionária de um número
dado, calculada como a diferença entre ele e a sua parte inteira.
/* Programa que fornece a parte fracionária de um número dado */
#include <stdio.h>
main()
{
float Num, Frac;
int Inteiro;
printf("Digite um numero ");
scanf("%f", &Num);
Inteiro = Num;
Frac = Num - Inteiro;
printf("A parte fracionaria de %f e' %f ", Num, Frac);
}
Há que se ter cuidado com números fracionários. Já foi dito que o sistema (e
qualquer ambiente para programação) não armazena exatamente todos os números reais,
armazenando, de fato, aproximações da maioria deles. Por exemplo, se modificássemos o
comando de saída do programa anterior para
printf("A parte fracionaria de %f e' %.9f ", Num, Frac);
35
Capítulo 2 Introdução à Linguagem C
e o executássemos para a entrada 2.41381 teríamos como saída a frase
A parte fracionaria de 2.41381 e' 0.413810015!
O ponto de exclamação (que não faz parte da saída do programa) foi posto pelo
fato de que a saída esperada para esta entrada seria 0.41381.

Combinando comandos de atribuição com operadores


aritméticos
O comando de atribuição pode ser combinado com operadores aritméticos para
substituir atribuições cuja expressão do segundo membro contenha a variável do primeiro
membro. Se x for o identificador da variável e $ for um operador aritmético, a atribuição
x = x $ (expressão);
pode ser indicada, simplesmente, por
x $= expressão;
Por exemplo,
x *= 4; equivale a x = x*4;
x += 5; equivale a x = x + 5;
x %= y + 1; equivale a x = x % (y + 1);
x -= 5; equivale a x = x – 5;
x /= 2; equivale a x = x/2;.
De acordo com o objetivo do livro, evitaremos a utilização destas opções
oferecidas pela linguagem C, por entendermos que elas podem dificultar a legibilidade do
comando. No nosso entendimento, só programadores mais experientes devem usar estes
recursos.

Lendo caracteres
Alem da possibilidade de se dar entrada em caracteres através da função scanf()
com código de conversão "%c", pode-se dar entrada em caracteres utilizando-se as
funções getch() e getche(), cujos cabeçalhos encontram-se no arquivo conio.h. Para a
execução destas funções é necessário que se acione uma tecla; quando isto é feito, o
caractere correspondente é retornado pela função e pode então ser armazenado numa
variável do tipo char através de um comando de atribuição. A diferença entre estas
funções é que na primeira o caractere digitado não aparece na tela de trabalho, o que
acontece com a segunda função. Por exemplo, a execução do programa
#include <stdio.h>
#include <conio.h>
main()
{

36
Capítulo 2 Introdução à Linguagem C
char c;
c = getch();
printf("Voce digitou a letra %c \n", c);
}
digitando-se a letra A deixa a tela de trabalho da seguinte forma
Voce digitou a letra A
enquanto que a execução do programa
#include <stdio.h>
#include <conio.h>
main()
{
char c;
c = getche();
printf("Voce digitou a letra %c \n", c);
}
deixa a tela de trabalho assim:
A
Você digitou a letra A

2.10 Exemplos Parte I


1. Voltando ao programa do cálculo da média de três números dados, observe que a
média foi calculada e exibida, mas não foi armazenada. Se este programa fizesse parte de
um programa maior (e isto normalmente acontece! Não se usa computação para uma
questão tão simples!) e esta média fosse necessária em outra parte do programa, aquele
trecho teria que ser rescrito. É uma boa prática, portanto, que resultados finais de
processamento sejam armazenados em variáveis, sendo então os conteúdos destas
variáveis exibidos através da função printf(). Assim, o programa referido ficaria melhor
escrito da seguinte forma.
/* Programa que determina a média de três números dados */
#include <stdio.h>
main()
{
float a, b, c, Media;
puts("Digite três números");
scanf("%f %f %f", &a, &b, &c);
Media = (a + b + c)/3;
printf("A media dos números %f , %f e %f é igual a %f ", a, b, c, Media);
}

37
Capítulo 2 Introdução à Linguagem C
2. Agora apresentaremos um programa que recebendo um número inteiro como
entrada fornece o algarismo da casa das unidades deste número, questão discutida no
capítulo 1. Como vimos naquele capítulo, o algarismo procurado é o resto da divisão do
número dado por 10. Temos então o seguinte programa (no capítulo 6 veremos um
programa que necessita da solução desta questão).
/* Programa que determina o algarismo da casa das unidades de um inteiro dado */
#include <stdio.h>
main()
{
int Num, Unidades;
printf("Digite um inteiro");
scanf("%d", &Num);
Unidades = Num % 10;
printf("O algarismo da casa das unidades de %d e' %d ", Num, Unidades);
}
3. Se quiséssemos um programa para inverter um número com dois algarismos (por
exemplo, se a entrada fosse 74, a saída deveria ser 47) poderíamos utilizar o seguinte fato:
se x e y são os algarismos de um número (casa das dezenas e casa das unidades,
respectivamente), então este número é x . 10 + y. Assim, a inversão seria y . 10 + x e
bastaria extrair os dois algarismos do número dado e utilizar a expressão acima. A
extração do algarismo da casa das unidades foi mostrada no exemplo anterior. E o
algarismo da casa das dezenas? Basta ver que ele é o quociente da divisão do número por
10 e este quociente é obtido através do operador / com operandos inteiros. Temos então o
seguinte programa.
/* Programa que inverte um número com dois algarismos */
#include <stdio.h>
main()
{
int Num, Unidades, Dezenas, Invertido;
printf("Digite um inteiro com dois algarismos");
scanf("%d", &Num);
Unidades = Num % 10;
Dezenas = Num/10;
Invertido = Unidades * 10 + Dezenas;
printf("O invertido de %d e' %d ", Num, Invertido);
}
Dificilmente o caro leitor vai escrever um programa com este objetivo (para que
serve inverter um número com dois algarismos?). Esta questão e algumas outras estão
sendo discutidas aqui apenas como exemplos para o desenvolvimento da lógica de
programação e pelo fato de que podem ser trechos de programas maiores.

38
Capítulo 2 Introdução à Linguagem C
4. Imagine agora que queiramos um programa que determine o menor múltiplo de
um inteiro dado maior do que um outro inteiro dado. Por exemplo, se a entrada fosse 13 e
100, a saída deveria ser 104 (104 é o menor múltiplo de 13 que é maior que 100). Como
dividendo = divisor x quociente + resto e resto < divisor,
temos que o valor da expressão
dividendo - resto + divisor
é o múltiplo procurado.
/*Programa que determina o menor múltiplo de um inteiro maior que outro
inteiro*/
#include <stdio.h>
main()
{
int Num, Divisor, MenMultiplo;
printf("Digite o inteiro do qual o número procurado deve ser múltiplo");
scanf("%d", &Divisor);
printf("Digite o inteiro que deve ser menor que o múltiplo \n");
scanf("%d", &Num);
MenMultiplo = Num - Num % Divisor + Divisor;
printf("O menor multiplo de %d maior do que %d e' %d \n", Divisor, Num,
MenMultiplo);
}
5. O programa a seguir, além de ser muito interessante no sentido do
desenvolvimento da lógica de programação, será utilizado (a seqüência de comandos da
função main()) em outros programas. O objetivo dele é permutar os conteúdos de duas
variáveis. Ou seja, suponhamos que, através de comandos de entrada o programa
armazenou nas variáveis x e y os valores 7 e 18 e pretendamos que o programa faça com
que o conteúdo de x passe a ser 18 e o de y passe a ser igual a 7. À primeira vista, bastaria
a seqüência de comandos
x=y
y=x
Ocorre que, quando o segundo comando fosse executado, o primeiro já teria sido e
o conteúdo de x não seria mais o original. No nosso exemplo, teríamos a seguinte situação

x y
17 8
8
8
e a permuta não teria sido feita, além do fato de que o conteúdo original de x teria sido
perdido. Uma alternativa é considerar uma variável auxiliar que "guarde" o conteúdo de x
antes que este seja substituído pelo conteúdo de y. Teríamos assim o seguinte programa.

39
Capítulo 2 Introdução à Linguagem C
/* Programa que permuta os conteúdos de duas variáveis */
#include <stdio.h>
main()
{
float x, y, Aux;
printf("Digite os dois numeros ");
scanf("%f %f", &x, &y);
printf("Entrada x = %0.1f, y = %0.1f \n", x, y);
Aux = x;
x = y;
y = Aux;
printf("Saida x = %0.2f, y = %0.2f \n", x, y);
}
Cabe observar que a permuta dos conteúdos pode ser obtida sem a utilização da
variável Aux. Isto, porém, é deixado para que o leitor descubra a solução, sendo
apresentado como exercício proposto.

2.11 Funções de biblioteca


Como dissemos na seção 2.5, os compiladores C oferecem diversas funções com
objetivos pré-determinados e que podem ser executadas durante a execução de um
programa. Para isto a execução da função deve ser solicitada no programa como uma
instrução, como operando de uma expressão ou como argumento de outra função (a
solicitação da execução de uma função é normalmente chamada de ativação ou chamada
da função). Para que o programador possa colocar no seu programa uma instrução que
ative uma função, é necessário que ele conheça o identificador da função, quantos e de
que tipo são os argumentos com que elas devem ser ativadas e o tipo de valor que ela
retorna ao programa quando termina sua execução (como já foi dito, este conjunto
constitui o protótipo da função). A definição de uma função pré-definida se faz através da
seguinte sintaxe.
Identificador da função(Lista de argumentos)
sendo que a lista de argumentos pode ser vazia. A tabela a seguir apresenta algumas das
funções pré-definidas dos compiladores C, indicando o tipo dos seus argumentos e
comentando o seu valor de retorno.

40
Capítulo 2 Introdução à Linguagem C
Tabela 12 Algumas funções de biblioteca
Identificador Argumentos O que retorna
fabs(x) double Valor absoluto do argumento x
acos(x) doublé Arco cujo valor do co-seno é o argumento x
asin(x) double Arco cujo valor do seno é o argumento x
atan(x) double Arco cujo valor da tangente é o argumento x
cos(x) double Co-seno do argumento x
log(x) double Logaritmo natural do argumento x
log10(x) double Logaritmo decimal do argumento x
pow(x, y) doublé, double Argumento x elevado ao argumento y
pow10(x) Int 10 elevado ao argumento x
random(x) Int Um número aleatório entre 0 e x - 1
sin(x) double Seno do argumento x
sqrt(x) double Raiz quadrada do argumento x
tan(x) doublé Tangente do argumento x
tolower(x) char Converte o caractere x para minúsculo
toupper(x) char Converte o caractere x para maiúsculo
O protótipo da função random() se encontra no arquivo stdlib.h e os protótipos das
funções tolower() e toupper() estao no arquivo ctype.h. Os protótipos das outras funções
estão no arquivo math.h que, como seu nome indica, contém os protótipos das funções
matemáticas. Para que a função random() seja ativada é necessário que sua ativação seja
precedida pela ativação da função randomize() que ativa o gerador de número aleatório.
Por exemplo, o programa abaixo exibirá um número aleatório entre 0 e 99.
/* programa que exibe, aleatoriamente, um número entre 0 e 99 */
#include <stdio.h>
#include <stdlib.h>
main()
{
int x;
randomize();
x = random(100);
printf("%d \n", x);
}
O exemplo a seguir, além de pretender motivar o próximo capítulo, objetiva
também ressaltar algo mais ou menos óbvio, mas que deve ser destacado: um
programador só é capaz de escrever um programa que resolva um determinado problema
se ele souber resolver o tal problema "na mão", ou seja, com a utilização apenas de lápis e
papel. Trata-se de um programa que calcule a área de um triângulo, dados os
comprimentos dos seus lados. Naturalmente, só é capaz de escrever este programa aquele
que conhecer a fórmula abaixo, que dá a área do triângulo cujos lados têm comprimentos
a, b e c:

41
Capítulo 2 Introdução à Linguagem C

S= p . ( p − a ) . ( p − b) . ( p − c )
onde

a+b+c
p=
2
é o semiperímetro do triângulo. Com isto, temos o seguinte programa.
/*Programa que determina a área de um triângulo de lados de comprimentos
dados*/
#include <stdio.h>
#include <math.h>
main()
{
float x, y, z, Area, SemiPer;
printf("Digite os comprimentos dos lados do triangulo");
scanf("%f %f %f", &x, &y, &z);
SemiPer = (x + y + z)/2;
Area = sqrt(SemiPer * (SemiPer - x) * (SemiPer - y) * (SemiPer - z));
printf("A area do triangulo de lados %f , %f e %f e' igual a %f \n", x, y, z, Area);
}
Se este programa for executado com entrada 3, 4 e 5 temos SemiPer = 6 e

S = 6 . (6 − 3) . (6 − 4) . (6 − 5) = 36
e, como era de se esperar, a área do triângulo cujos lados têm comprimento 3, 4 e 5
unidades de comprimento é igual a 6 unidades de área.
S = 4 . ( 4 − 1 ) . ( 4 − 2 ) . ( 4 − 5 ) = − 24

Agora, se este programa fosse executado para entrada 1, 2 e 5 teríamos


SemiPer = 4, S = 4 . ( 4 − 1) . (4 − 2) . (4 − 5) = − 24 e ocorreria erro de execução pois
o sistema, como era de se esperar, não calcula raiz quadrada de número negativo.
O que acontece é que nem sempre três números podem ser comprimentos dos lados
de um triângulo (a matemática prova que isto só acontece se cada um deles for menor do
que a soma dos outros dois). Assim, o comando que calcula a Area só deveria ser
executado se os valores digitados para x, y, e z pudessem ser comprimentos dos lados de
um triângulo.

2.12 Exercícios propostos


1. Avalie cada uma das expressões abaixo.
a) (-(-9) + sqrt((-9)*(-9) - 4*3*6))/(2*3).
b) ((pow(3, 2) == 9) && (acos(0) == 0)) || (4 % 8 == 3).

42
Capítulo 2 Introdução à Linguagem C
2. Escreva programas para
a) Converter uma temperatura dada em graus Farenheit para graus Celsius.
b) Gerar o invertido de um número com três algarismos (exemplo: o invertido
de 498 é 894).
c) Somar duas frações ordinárias, fornecendo o resultado em forma de fração.
d) Determinar o maior múltiplo de um inteiro dado menor do que ou igual a um
outro inteiro dado (exemplo: o maior múltiplo de 7 menor que 50 é 49).
e) Determinar o perímetro de um polígono regular inscrito numa circunferência,
dados o número de lados do polígono e o raio da circunferência.
3. Escreva um programa que permute o conteúdo de duas variáveis sem utilizar
uma variável auxiliar (ver exemplo 5 da seção 2.9).
4. Uma loja vende seus produtos no sistema entrada mais duas prestações, sendo a
entrada maior do que ou igual às duas prestações; estas devem ser iguais, inteiras e as
maiores possíveis. Por exemplo, se o valor da mercadoria for R$ 270,00, a entrada e as
duas prestações são iguais a R$ 90,00; se o valor da mercadoria for R$ 302,75, a entrada
é de R$ 102,75 e as duas prestações são a iguais a R$ 100,00. Escreva um programa que
receba o valor da mercadoria e forneça o valor da entrada e das duas prestações, de
acordo com as regras acima. Observe que uma justificativa para a adoção desta regra é
que ela facilita a confecção e o conseqüente pagamento dos boletos das duas prestações.
5. Um intervalo de tempo pode ser dado em dias, horas, minutos, segundos ou
seqüências "decrescentes" destas unidades (em dias e horas; em horas e minutos; em
horas, minutos e segundos), de acordo com o interesse de quem o está manipulando.
Escreva um programa que converta um intervalo de tempo dado em segundos para horas,
minutos e segundos. Por exemplo, se o tempo dado for 3 850 segundos, o programa deve
fornecer 1 h 4 min 10 s.
6. Escreva um programa que converta um intervalo de tempo dado em minutos
para horas, minutos e segundos. Por exemplo, se o tempo dado for 145.87 min, o
programa deve fornecer 2 h 25 min 52.2 s (vale lembrar que o ponto é o separador da
parte inteira).
7. Um programa para gerenciar os saques de um caixa eletrônico deve possuir
algum mecanismo para decidir o número de notas de cada valor que deve ser
disponibilizado para o cliente que realizou o saque. Um possível critério seria o da
"distribuição ótima" no sentido de que as notas de menor valor disponíveis fossem
distribuídas em número mínimo possível. Por exemplo, se a máquina só dispõe de notas
de R$ 50, de R$ 10, de R$ 5 e de R4 1, para uma quantia solicitada de R$ 87, o programa
deveria indicar uma nota de R$ 50, três notas de R$ 10, uma nota de R$ 5 e duas notas de
R$ 1. Escreva um programa que receba o valor da quantia solicitada e retorne a
distribuição das notas de acordo com o critério da distribuição ótima.
8. De acordo com a Matemática Financeira, o cálculo das prestações para
amortização de um financiamento de valor F em n prestações e a uma taxa de juros i é
dada pela fórmula P = F/an¬i, onde an¬i = ((1 + i)n – 1)/(i . (1 + i)n). Escreva um
programa que determine o valor das prestações para amortização de um financiamento,
dados o valor do financiamento, o número de prestações para amortização e a taxa de
juros.

43
CAPÍTUL
3 Estruturas de Seleção
3.1 O que é uma estrutura de seleção
O último exemplo do capítulo anterior apresentava um programa para calcular a
área de um triângulo, dados os comprimentos dos seus lados. Foi visto que o comando
que calculava a área solicitada só devia ser executado com a certeza anterior de que os
valores dados como entrada poderiam ser comprimentos dos lados de um triângulo. Ou
seja, o tal comando só deveria ser executado se x < y + z e y < x + z e z < x + y, condição
que garante que os valores armazenados nas variáveis x, y e z são comprimentos dos
lados de um triângulo. Assim, em algumas situações, alguns comandos só devem ser
executados se alguma condição for satisfeita.
É muito fácil encontrar situações em que a execução de uma ou mais instruções
deve estar condicionada à satisfabilidade de uma condição. Por exemplo, veremos
algoritmos para ordenar uma relação de números que necessitam colocar em ordem
crescente os conteúdos de duas variáveis. É óbvio que para ordenar em ordem crescente
os conteúdos de duas variáveis x e y, só é necessário se fazer alguma coisa se o conteúdo
de x for maior que o conteúdo de y, ou seja, se x > y.
Há situações também em que há necessidade de que se faça uma escolha entre duas
ou mais seqüências de instruções qual a seqüência deve ser executada. Por exemplo, se
pretendemos verificar se um número n é par podemos determinar o resto da divisão de n
por 2. Se este resto for zero, então o número é par. Se este resto for 1, o número é ímpar.
Vale lembrar que os algoritmos que o viver exige que executemos diuturnamente
são pontuados de escolhas e decisões: se não chover, iremos para a praia, se chover,
iremos para o shopping; se estiver fazendo frio, vista um casaco.
A verificação de que uma condição é satisfeita e, a partir daí, uma determinada
seqüência de comandos deve ser executada é chamada de estrutura de seleção, estrutura
de decisão ou comando de seleção.

3.2 O comando if
O comando if é uma estrutura de decisão que decide se uma seqüência de
comandos será ou não executada. Sua sintaxe é
if (Expressão)
{
seqüência de comandos
}
sendo os delimitadores opcionais se a seqüência de comandos contém um único comando.
A semântica deste comando é muito simples: se o valor da Expressão for diferente
de zero, o sistema executará a seqüência de comandos; caso contrário o sistema não
Capítulo 3 Estruturas de Seleção

executará a seqüência de comandos e a instrução após o comando if passa a ser


executada.
Por exemplo, se queremos um programa que determine o maior de dois números
dados, podemos supor que o primeiro deles é o maior, armazenando-o numa variável
Maior e depois, através de um comando if, verificar se o maior procurado é o segundo
dos números dados; neste caso o conteúdo da variável Maior deve ser alterado.
/*Programa para determinar o maior de dois números dados */
#include <stdio.h>
main()
{
float a, b, Maior;
printf("Digite os dois numeros");
scanf("%f %f", &a, &b);
Maior = a;
if (b > a)
Maior = b;
printf("O maior dos numeros %f , %f e' %f ", a, b, Maior);
}
Um outro exemplo de utilização do comando if aparece num programa que
pretenda ordenar os conteúdos de variáveis x e y. Para isto só há de necessidade de se
realizar alguma ação se o conteúdo de y for maior do que o conteúdo de x. Neste caso, o
que deve ser feito é a permuta dos conteúdos de x e de y. Temos então o seguinte
programa.
/* Programa para ordenar os conteúdos de duas variáveis */
#include <stdio.h>
main()
{
float x, y, Aux;
printf("Digite os dois numeros");
scanf("%f %f", &x, &y);
printf("Conteudos originais de x e de y: %f , %f \n: ", x, y);
if (y < x)
{
Aux = x;
x = y;
y = Aux;
}
printf("Conteudos de x e de y ordenados: %f , %f: \n", x, y);
}
Observe que a seqüência de comandos
Aux =x; x = y; y = Aux;

45
Capítulo 3 Estruturas de Seleção

realiza a permuta dos conteúdos das variáveis x e y, como discutido em exemplo do


capítulo anterior.

3.3 O comando if else


O comando if else é uma estrutura de decisão que decide entre duas seqüências de
comandos qual vai ser executada, sendo definido através da seguinte sintaxe:
if (Expressão)
{
Seqüência de comandos 1
}
else
{
Seqüência de comandos 2
}
A semântica deste comando é a seguinte: se o valor de Expressão for diferente de
zero, o sistema executará a seqüência de comandos 1; caso contrário o sistema executará
a seqüência de comandos 2.
Por exemplo, se queremos um programa que verifique a paridade de um número
dado, poderíamos verificar se o resto da divisão do número por dois é igual a 1. Se isto
for verdade, o número é ímpar; se não for verdade, o número dado par.
/* Programa para verificar se um número e' par */
#include <stdio.h>
main()
{
int x, y;
printf("Digite o numero");
scanf("%d", &x);
if (x % 2 == 0)
printf("%d e' par \n", x);
else
printf("%d e' impar \n", x);
}
Mesmo considerando que os compiladores da linguagem C não consideram
espaços nem mudanças de linha, observe que estamos procurando escrever cada instrução
em uma linha e a seqüência vinculada à estrutura de decisão com uma tabulação diferente
da tabulação em que estão postos o if e o else. Esta forma de se editar um programa,
chamada endentação, deve ser praticada por todo programador pois ela facilita
sobremaneira a legibilidade dos programas. Se o programa acima fosse digitado da forma
seguinte
/* Programa para verificar se um número é par*/

46
Capítulo 3 Estruturas de Seleção

#include <stdio.h>
main(){
int x, y; printf("Digite o numero"); scanf("%d", &x);
if (x % 2 == 0) printf("%d e' par \n", x); else
printf("%d e' impar \n", x);
}
ele seria executado da mesma forma, porém a sua legibilidade estaria prejudicada.

3.4 O operador condicional ternário


Quando as duas opções de um comando if else contêm apenas uma atribuição a
uma mesma variável, pode-se utilizar o operador condicional ternário que possui a
seguinte sintaxe:
Variável = Expressão lógica ? Expressão 1 : Expressão 2;
Na execução deste comando a Expressão lógica é avaliada e se for diferente de
zero o valor da Expressão 1 é atribuído à Variável; caso contrário, o valor da Expressão 2
é atribuído. Por exemplo, se x, y e Maior são três variáveis do tipo float o armazenamento
do maior dos conteúdos de x e de y na variável Maior poderia ser obtido com a seguinte
atribuição:
Maior = (x > y) ? x : y;
Como um outro exemplo, para se armazenar na variável AbsNum o valor absoluto
do conteúdo de uma variável Num (sem utilizar a função fabs()) bastaria o comando:
AbsNum = (Num >= 0) ? Num : -Num;

3.5 Exemplos Parte III


0. De um modo geral, as ligações telefônicas são cobradas pelas suas durações. O
sistema registra os instantes em que a ligação foi iniciada e concluída e é acionado um
programa que determina o intervalo de tempo decorrido entre aqueles dois instantes
dados. O programa abaixo recebe dois instantes dados em horas e minutos e determina o
intervalo de tempo (em horas e minutos) decorrido entre eles.
/*Programa que determina o intervalo de tempo decorrido entre dois instantes*/
main()
{
int h1, min1, h2, min2, h, min;
puts("Digite o instante inicial (horas e minutos)");
scanf("%d %d", &h1, &min1);
puts("Digite o instante final");
scanf("%d %d", &h2, &min2);

47
Capítulo 3 Estruturas de Seleção

h = h2 -h1;
min = min2 - min1;
if ((h < 0) || ((h == 0) && (min < 0)))
puts("Dados invalidos! O segundo instante é anterior ao primeiro");
else
{
if (min < 0)
{
h = h - 1;
min = min + 60;
}
printf( "Entre os instantes %dh %dmin e %dh %dmin passaram-se %dh
%dmin", h1, min1, h2, min2, h, min);
}
}
1. No último exemplo do capítulo 2, apresentamos um programa que calculava a
área de um triângulo, dados os comprimentos dos seus lados. No final dele, mostramos
que o mesmo não fornecia respostas satisfatórias para todas as entradas e comentamos
que o cálculo da área deveria ser precedido da verificação de que os dados de entrada são
de fato comprimentos dos lados de um triângulo. O programa referido, escrito agora de
forma completa e correta, seria o seguinte.
/* Programa para calcular a área de um triângulo*/
#include <stdio.h>
#include <math.h>
main()
{
float x, y, z, Area, SemiP;
printf("Digite os comprimentos dos lados do triangulo");
scanf("%f %f %f", &x, &y, &z);
if ((x < y + z) && (y < x + z) && (z < x + y))
{
SemiP = (x + y + z)/2;
Area = sqrt(SemiP * (SemiP - x) * (SemiP - y) * (SemiP - z));
printf("A area do triangulo de lados %f , %f e %f e' igual a %f \n", x, y, z,
Area);
}
else
printf("Os números %f , %f %f não podem ser comprimentos dos lados de um
triângulo \n", x, y, z);
}
2. Programas que manipulam datas (por exemplo, um programa que determine o
número de dias entre duas datas dadas) contêm trechos que verificam se um ano dado é

48
Capítulo 3 Estruturas de Seleção

bissexto. Sabendo que um ano é bissexto se ele é múltiplo de quatro, teríamos o seguinte
programa.
/*Programa que verifica se um dado ano é bissexto */
#include <stdio.h>
main()
{
int Ano;
printf("Digite o ano");
scanf("%d", &Ano);
if (Ano % 4 == 0)
printf("%d e' bissexto %d \n", Ano);
else
printf("%d não e' bissexto %d \n", Ano);
}
Rigorosamente falando, há anos múltiplos de quatro que não são bissextos. São
aqueles múltiplos de 100 que não são múltiplos de 400. Por exemplo, o ano 2000 foi um
ano bissexto, mas o ano de 2100 não será. Para que o programa detecte estas exceções, a
expressão lógica que controla o comando if deve ser ampliada e talvez seja mais fácil
considerar a condição para que um ano não seja bissexto: não deve ser múltiplo de quatro
ou se for múltiplo de 100 não deve ser múltiplo de 400. Observe que agora optamos por
uma expressão lógica que garantisse o fato de que o ano dado não é bissexto.
/* Programa que verifica se um dado ano é bissexto */
#include <stdio.h>
main()
{
int Ano;
printf("Digite o ano");
scanf("%d", &Ano);
if ((Ano % 4 != 0) || ((Ano % 100 == 0) && (Ano % 400 != 0)))
printf("%d nao e' bissexto \n", Ano);
else
printf("%d e' bissexto \n", Ano);
}
3. O programa para ordenar os conteúdos de duas variáveis, visto na seção 3.2, é
um caso muito particular da questão mais geral da ordenação de uma relação de números
ou de nomes, problema que tem vasta aplicação na vida prática, principalmente na
ordenação de uma lista de nomes (este problema também é conhecido como
classificação). Para a solução geral existem diversos algoritmos com este objetivo. No
capítulo 7 teremos oportunidade de discutir programas baseados em alguns destes
algoritmos. Por enquanto, vejamos um programa que ordene três números dados. Além de
exemplificar o comando if, o programa abaixo mostra como se pode (e se deve) utilizar
raciocínios anteriores para se escrever programas.

49
Capítulo 3 Estruturas de Seleção

Seja então um programa que receba três números inteiros, armazene-os nas
variáveis x, y e z e que ao final da sua execução deixe os conteúdos de x, de y e de z na
ordem crescente. Uma idéia bem interessante é armazenar na variável x o menor dos
números e em seguida ordenar os conteúdos de y e de z, que é exatamente o problema de
ordenar os conteúdos de duas variáveis, que foi referido acima. Obviamente, para se
executar a primeira ação pretendida (armazenar na variável x o menor dos números) só é
necessário se fazer alguma coisa se o valor de x já não for o menor dos números dados, ou
seja, se x > y ou x > z. Nesta hipótese, o menor deles é y ou z e este menor deve ser
permutado com x. Temos então o seguinte programa.
/* Programa para ordenar três números dados*/
#include <stdio.h>
main()
{
float x, y, z, Aux;
printf("Digite os tres numeros");
scanf("%f %f %f", &x, &y, &z);
printf("Numeros dados: %f , %f , %f \n", x, y, z);
if ((x > y) || (x > z)) /* verifica se x não é o menor */
if (y < z) /* neste caso y é o menor */
{
Aux = x; /* troca os conteúdos de x e de y */
x = y;
y = Aux;
}
else /* neste caso z é o menor */
{
Aux = x; /* troca os conteúdos de x e de z */
x = z;
z = Aux;
}
if (y > z) /* verifica se z e y ainda não estão ordenados */
{
Aux = y; /* troca o conteúdo de y e de z */
y = z;
z = Aux;
}
printf("Numeros ordenados: %f , %f , %f \n", x, y, z);
}
Observe que se a expressão lógica do primeiro comando if for verdadeira, o sistema
executará outro comando if. Neste caso, dizemos que os comandos estão aninhados.
Observe também que escrevemos no programa algumas frases explicativas das ações
pretendidas. Estas frases são chamadas comentários e devem ser escritas entre os pares de
caracteres /* e */. Quando o compilador encontra o par de caracteres /* procura um outro

50
Capítulo 3 Estruturas de Seleção

par */ e desconsidera tudo o que vem entre os dois pares. Isto permite que o programador
deixe registrado no próprio programa as observações que ele achar conveniente. Como a
edição dos programas com endentação, a prática de se colocar comentários nos programas
é muito importante. Como os programas discutidos neste livro serão precedidos de
explicações prévias, a utilização de comentários aqui vai se restringir à indicação do
objetivo do programa (como já víamos fazendo).
A ação realizada pela primeira estrutura de decisão do programa acima pode ser
obtida através de outro algoritmo. A idéia é a seguinte: coloca-se na variável x o menor
dos valores inicialmente armazenados nas variáveis x e y. Em seguida, repete-se o
raciocínio com os valores armazenados (agora) em x e em z.
/* Programa para ordenar três números dados (versão 2)*/
#include <stdio.h>
main()
{
float x, y, z, Aux;
printf("Digite os tres numeros");
scanf("%f %f %f", &x, &y, &z);
printf("Numeros dados: %f , %f , %f \n", x, y, z);
if (x > y)
{
Aux = x;
x = y;
y = Aux;
}
if (x > z)
{
Aux = x;
x = z;
z = Aux;
}
if (y > z)
{
Aux = y;
y = z;
z = Aux;
}
printf("Numeros ordenados: %f , %f , %f \n", x, y, z);
}
4. Um outro exemplo que ilustra muito bem a utilização do comando if é um
programa para determinar as raízes de uma equação do segundo grau. Sabemos da
matemática que uma equação ax2 + bx + c = 0 só tem raízes reais se b2 - 4ac < 0. Assim,
um programa para encontrar as raízes reais (deixaremos o caso completo da determinação
das raízes reais e complexas como exercício proposto) poderia ser o seguinte.

51
Capítulo 3 Estruturas de Seleção

/*Programa que calcula as raízes de uma equação do segundo grau */


#include <stdio.h>
#include <math.h>
main()
{
float a, b, c, Delta, x1, x2;
printf("Digite os coeficientes");
scanf("%f %f %f", &a, &b, &c);
if (a != 0)
{
Delta = b*b - 4*a*c;
if (Delta >= 0)
{
x1 = (-b + sqrt(Delta))/(2*a);
x2 = (-b - sqrt(Delta))/(2*a);
printf("As raizes da equacao de coeficientes %f , %f e %f sao %f e %f
", a, b, c, x1, x2);
}
else
printf("A equacao nao tem raizes reais");
}
else
printf("A equacao nao e do segundo grau");
}
5. Imaginemos agora uma escola que adote no seu processo de avaliação a
realização de quatro avaliações bimestrais e que o regime de aprovação dos alunos seja o
seguinte:
i) Se a média das avaliações bimestrais for superior ou igual a 7,0, o aluno está
aprovado, com média final igual à média das avaliações bimestrais.
ii) Se a média das avaliações bimestrais for inferior a 5,0, o aluno está reprovado,
com média final igual à média das avaliações bimestrais.
iii) Não ocorrendo nenhum dos casos anteriores, o aluno se submete a uma prova
final e a sua média final será a média ponderada desta prova final (com peso 4) e a média
das avaliações bimestrais (com peso 6). Neste caso, o aluno estará aprovado se a sua
média final for superior ou igual a 5,5.
O programa abaixo recebendo as notas das avaliações bimestrais e, se for o caso, a
nota da prova final, fornece a média final do aluno e a sua condição em relação à
aprovação.
/* Programa para verificar aprovação de um aluno*/
#include <stdio.h>
main()
{
float Bim1, Bim2, Bim3, Bim4, MedBim, PrFinal, MedFinal;

52
Capítulo 3 Estruturas de Seleção

printf("Digite as quatro notas bimestrais");


scanf("%f %f %f %f", &Bim1, &Bim2, &Bim3, &Bim4);
MedBim = (Bim1 + Bim2 + Bim3 + Bim4)/4;
MedFinal = MedBim;
if ((MedBim < 7) && (MedBim >= 5))
{
printf("Digite a nota da prova final");
scanf("%f", &PrFinal);
MedFinal = (MedBim * 6 + PrFinal * 4)/10;
}
if (MedFinal > 5.5)
printf("Aluno aprovado com media final %.2f \n", MedFinal);
else
printf("Aluno reprovado com media final %0.2f \n", MedFinal);
}
6. Para um exemplo de um programa que utiliza vários comandos if aninhados,
suponhamos que uma empresa decidiu dar um aumento escalonado a seus funcionários de
acordo com a seguinte regra: 13% para os salários inferiores ou iguais a R$ 200,00; 11%
para os salários situados entre R$ 200,0 e R$ 400,00 (inclusive); 9 % para os salários
entre R$ 400,00 e R$ 800,00 (inclusive) e 7% para os demais salários. Um programa que
receba o salário atual de um funcionário e forneça o valor do seu novo salário poderia ser
o seguinte.
/*Programa para atualizar salários*/
#include <stdio.h>
main()
{
float SAtual, SNovo, Indice;
printf("Digite o salário atual");
scanf("%f", &SAtual);
if (SAtual <= 200)
Indice = 1.13;
else
if (SAtual <= 400)
Indice = 1.11;
else
if (SAtual <= 800)
Indice = 1.09;
else
Indice = 1.07;
SNovo = SAtual*Indice;
printf("Atual = %.2f \n Novo = %.2f \n" , SAtual, SNovo);
}

53
Capítulo 3 Estruturas de Seleção

Observe que a seqüência associada à opção else é iniciada com um outro comando
if. Alguns autores preferem destacar um fato como este definindo um "novo comando"
denominando-o else if.
7. Um outro exemplo que utiliza comandos de seleção aninhados e em que a
escolha da expressão lógica que controlará o comando if é importante é um programa que
determine o número de dias de um mês (um programa como este seria parte integrante de
um programa que manipulasse datas). Como os meses de trinta dias são quatro e os de
trinta e um dias são sete, usamos os primeiros para o controle do comando de seleção.
/* Programa que determina o número de dias de um mês dado */
#include <stdio.h>
main()
{
int Mes, Ano, NumDias;
printf("Digite o mes");
scanf("%d", &Mes);
if ((Mes == 4 ) || (Mes == 6) || (Mes == 9) || (Mes == 11))
NumDias = 30;
else
if (Mes == 2)
{
printf("Digite o ano");
scanf("%d", &Ano);
if (Ano % 4 != 0)
NumDias = 28;
else
NumDias = 29;
}
else
NumDias = 31;
printf("O mes %d tem %d dias", Mes, NumDias);
}
No capítulo 6 veremos que o programa acima pode ser bastante simplificado.

54
Capítulo 3 Estruturas de Seleção

3.6 O comando switch


Muitos programas são desenvolvidos de modo que eles podem realizar várias
tarefas de forma independente. Por exemplo, um programa que gerencie um caixa
eletrônico de um banco deve oferecer ao usuário algumas opções em relação à ação que
ele pretende realizar na sua conta como a emissão do saldo atual, a emissão de um
extrato, a realização de um saque e a realização de um depósito. É comum que um
programa que permita a realização de várias tarefas inicie apresentando ao usuário um
menu de opções com a indicação das diversas tarefas que o programa pode executar e a
permissão de que o usuário escolha a tarefa pretendida. Como, em geral, são várias as
opções disponíveis (cada uma delas com uma seqüência específica de comandos) e só
uma das opções será a escolhida, é necessária uma estrutura que decide entre várias
seqüências de comandos, qual vai ser executada ou quais vão ser executadas.
O comando switch tem este objetivo e deve ser escrito com a seguinte sintaxe:
switch(Expressão)
{
case constante1 :
Seqüência de instruções 1
case constante2 :
Seqüência de instruções 2
...
case constante n :
Seqüência de instruções n
default :
Seqüência de comando x
}
Aí, a Expressão argumento do comando deve resultar num valor do tipo int ou num
valor do tipo char e, opcionalmente, a ultima instrução de cada uma das seqüências
Seqüência de instruções i é break. A semântica deste comando é bem simples: a
Expressão é avaliada e as seqüências de instruções situadas entre o valor da expressão
apresentado nos cases e um comando break ou o delimitador do comando são executadas.
Se o valor da Expressão for diferente de todas as opções dadas pelas constantes
associadas aos cases, a seqüência de instruções vinculada ao default será executada. Por
exemplo, o programa
#include <stdio.h>
main()
{
int x;
printf("Digite um número inteiro entre 1 e 5 \n");
scanf("%d", &x);

55
Capítulo 3 Estruturas de Seleção

switch (x)
{
case 1 : printf("Valor de x: %d \n", x);
case 2 : printf("Valor do dobro de %d: %d \n", x, 2*x);
case 3 : printf("Valor do triplo de %d: %d \n", x, 3*x);
case 4 : printf("Valor do quadruplo de %d: %d \n", x, 4*x);
default : printf("Valor digitado: %d \n", x);
}
}
executado para x = 1 executa todas as seqüências vinculadas aos cases fornecendo a
seguinte saída:
Valor de x: 1
Valor do dobro de 1: 2
Valor do triplo de 1: 3
Valor do quadruplo de 1: 4
Valor digitado: 1
Se for executado para x = 3, só as seqüências a partir do case 3 serão executadas e
a saída será:
Valor do triplo de 3: 9
Valor do quadruplo de 3: 12
Valor digitado: 3
e se for executado x = 10 apenas a seqüência vinculada à condição default será a
executada e a saída será:
Valor digitado : 10
Duas observações: a seqüência de instruções vinculada a uma opção case pode ser
vazia, caso em que, evidentemente, nada é executado; se apenas uma seqüência de
comandos deve ser executada, deve-se encerrá-la com um break.
Para exemplificar, vamos reapresentar o exemplo 7 da seção anterior utilizando o
comando switch:
/* Programa para determinar o numero de dias de um mes*/
#include <stdio.h>
main()
{
int Mes, Ano, NumDias;
printf("Digite o mes \n");
scanf("%d", &Mes);
switch (Mes)
{
case 2 :
printf("Digite o ano");
scanf("%d", &Ano);
56
Capítulo 3 Estruturas de Seleção

if (Ano % 4 != 0)
NumDias = 28;
else
NumDias = 29;
break;
case 4 :
case 6 :
case 9 :
case 11 : NumDias = 30; break;
default : NumDias = 31;
}
printf("O mes de numero %d tem %d dias \n", Mes, NumDias);
}
Observe que se o mês de entrada for 2, o programa pede o ano para determinar se
ele é bissexto. Aí, determina o número de dias e a instrução break encerra o comando
switch. Se a entrada for 4, com a seqüência de comandos vinculada ao case 4 é vazia (e,
portanto, não contém break) as seqüências vinculadas aos cases seguintes são executadas
até o break do case 11 (para os meses 4, 6, 9 e 11 o número de dias é igual a 30!). Se a
entrada não for 2, 4, 6, 9 e 11 a opção default será executada e, portanto, o mês terá 31
dias. Evidentemente, fica faltando discutir a possibilidade de uma entrada inválida como,
por exemplo, 13. Isto será discutido num dos capítulos seguintes.
A opção default é opcional: se ela não aparece no comando e o valor da Expressão
for diferente de todos os valores disponíveis, nada é executado e a instrução logo após o
comando switch passa a ser executada.
Um outro exemplo interessante de utilização do comando aqui focado é um
programa que determine o dia da semana de uma data dada. Tomando como base o ano
de 1600 (em 1582 o Papa Gregorio III instituiu mudanças no calendário então vigente) e
sabendo que o dia primeiro deste ano foi um sábado, para se determinar o dia da semana
de uma data dada basta se calcular o número de dias decorridos entre a data dada e o dia
01/01/1600. Como a associação do dia da semana a uma data é periódica, de período 7, o
resto da divisão do número de dias referido acima por 7 indica a relação entre o dia da
semana procurado e o sábado: se o tal resto for 1, o dia da semana é sábado; se o resto for
2 o dia da semana é domingo, e assim sucessivamente.
Para se calcular o número de dias entre uma data dada e 01/01/1600, basta
multiplicar o número de anos por 365, acrescentar a quantidade de anos bissextos e o
número de dias decorridos no ano corrente.
/* Programa para determinar o dia da semana de uma data dada */
#include <stdio.h>
main()
{
int Dia, Mes, Ano, DiasDoAno, Dias31, AnosBiss;
long int Anos, NumDias;
printf("Digite dia, mes e ano \n");

57
Capítulo 3 Estruturas de Seleção

scanf("%d %d %d", &Dia, &Mes, &Ano);


Anos = Ano - 1600;
/* Numero de meses com 31 dias ate o mês dado */
if (Mes < 9)
Dias31 = Mes/2;
else
Dias31 = (Mes + 1)/2;
/*Numero de dias do ano dado, considerando fevereiro com tendo 30 dias*/
DiasDoAno = 30*(Mes - 1) + Dia + Dias31;
/*Retifica o numero de dias de fevereiro*/
if (Mes >= 2)
if ((Ano % 4 != 0) || ((Ano % 100 == 0) && (Ano % 400 != 0)))
DiasDoAno = DiasDoAno - 2;
else
DiasDoAno = DiasDoAno - 1;
/*Numero de anos bissextos entre o ano dado e 1600*/
AnosBiss = (Ano/4 – 400) - (Ano/100 - 16) + (Ano/400 - 4) - 1;
/*Numero de dias entre a data dada e 01/01/1600*/
NumDias = Anos*365 + DiasDoAno + AnosBiss;
/*Dia da semana*/
switch(NumDias % 7)
{
case 0 : printf("\n Sabado"); break;
case 1 : printf("\n Domingo"); break;
case 2 : printf("\n Segunda"); break;
case 3 : printf("\n Terca"); break;
case 4 : printf("\n Quarta"); break;
case 5 : printf("\n Quinta"); break;
case 6 : printf("\n Sexta"); break;
}
}

3.7 Exercícios propostos


1. Reescreva o programa do exemplo zero da seção 3.5 de modo que os instantes
sejam dados (e o intervalo de tempo fornecido) em horas minutos e segundos.
2. Escreva um programa que realize arredondamentos de números utilizando a
regra usual da matemática: se a parte fracionária for maior do que ou igual a 0,5, o
número é arredondado para o inteiro imediatamente superior, caso contrário, é
arredondado para o inteiro imediatamente inferior.
3. Escreva um programa para verificar se um inteiro dado é um quadrado perfeito,
exibindo, nos casos afirmativos, sua raiz quadrada.
4. Escreva um programa para determinar o maior de três números dados.

58
Capítulo 3 Estruturas de Seleção

5. Escreva um programa para classificar um triângulo de lados de comprimentos


dados em escaleno (os três lados de comprimentos diferentes), isósceles (dois lados de
comprimentos iguais) ou eqüilátero (os três lados de comprimentos iguais).
6. Escreva um programa para verificar se um triângulo de lados de comprimentos
dados é retângulo, exibindo, nos casos afirmativos, sua hipotenusa e seus catetos.
7. Escreva um programa para determinar as raízes reais ou complexas de uma
equação do segundo grau, dados os seus coeficientes.
8. Escreva um programa para determinar a idade de uma pessoa, em anos meses e
dias, dadas a data (dia, mês e ano) do seu nascimento e a data (dia, mês e ano) atual.
9. Escreva um programa que, recebendo as quatro notas bimestrais de um aluno da
escola referida no exemplo 5 da seção 3.5, forneça a nota mínima que ele deve obter na
prova final para que ele seja aprovado.

59
4. Estruturas de Repetição
4.1 Para que servem as estruturas de repetição
Um locutor brasileiro ao narrar um jogo de futebol americano nos Estados Unidos
recebe a informação do placar eletrônico sobre a temperatura do estádio medida em graus
Fahrenheit. Naturalmente, ele deve fornecer aos telespectadores brasileiros a temperatura
em graus Celsius. Para isto, o locutor, de posse de um computador, poderia utilizar o
programa abaixo, que foi solicitado no primeiro item do segundo exercício da seção 2.12.
/*Programa que converte uma temperatura dada em graus Fahrenheit para graus
Celsius*/
#include <stdio.h>
main()
{
float Fahrenheit, Celsius;
printf("Digite a temperatura em Fahrenheit");
scanf("%f", &Fahrenheit);
Celsius = 5 * (Fahrenheit - 32)/9;
printf("A temperatura de %.2f Fahrenheit corresponde a %.2f Celsius ",
Fahrenheit, Celsius);
}
Se o placar eletrônico indicasse uma temperatura de 60o F, o narrador executaria o
programa com a entrada 60 e receberia a saída
A temperatura de 60 graus Fahrenheit corresponde a 15.55 graus Celsius
Certamente, seria mais prático a produção da transmissão do evento disponibilizar
para o locutor uma tabela contendo as temperaturas possíveis em graus Fahrenheit e as
correspondentes em graus Celsius. A confecção desta tabela poderia ser feita através de
um programa que contivesse vários comandos que calculassem para cada temperatura em
graus Fahrenheit pretendida a correspondente temperatura em graus Celsius e exibissem
estas temperaturas. Neste caso, não haveria necessidade de comando de entrada; porém,
para cada temperatura em graus Fahrenheit pretendida, haveria, pelo menos, um comando
de atribuição e a chamada da função printf(). Se a faixa de temperatura em graus
Fahrenheit a ser coberta pela tabela fosse de vinte a oitenta graus, teríamos um programa
como o programa abaixo.
/*Programa (muito ruim) que gera uma tabela de conversão de temperaturas em
graus Fahrenheit para graus Celsius */
#include <stdio.h>
main()
{
Capítulo 4 Estruturas de Repetição

int Fahrenheit;
printf("Tabela de conversao graus Fahrenheit/graus Celsius \n");
printf("-------------------------------------------------\n");
printf("\t Fahrenheit \t | \t Celsius\n");
printf("-------------------------------------------------\n");
Fahrenheit = 10;
printf("\t %f \t | \t %f \n", Fahrenheit, 5.0*(Fahrenheit - 32)/9);
Fahrenheit = 11;
printf("\t %f \t | \t %f \n", Fahrenheit, 5.0*(Fahrenheit - 32)/9);
...
/*Mais "uma porção" de comandos! */
Fahrenheit = 80;
printf("\t %f \t | \t %f \n", Fahrenheit, 5.0*(Fahrenheit - 32)/9);
}
Isto seria contornado se pudéssemos repetir a execução dos comandos que
gerariam as temperaturas em graus Fahrenheit e as correspondentes em graus Celsius. A
linguagem C possui os comandos for; while e do while, chamados estruturas de repetição
ou laços, cujas execuções redundam em repetições da execução de uma determinada
seqüência de comandos.

4.2 O comando for


O comando for é uma estrutura de repetição que repete a execução de uma dada
seqüência de comandos um número de vezes que pode ser determinado pelo próprio
programa, devendo ser escrito com a seguinte sintaxe:
for (inicializações; condições de manutenção da repetição; incrementos)
{
seqüência de comandos
}
Como os nomes indicam, em inicializações, são atribuídos valores iniciais a
variáveis; em condições de manutenção da repetição, estabelecem-se, através de uma
expressão, as condições nas quais a execução da seqüência de comandos será repetida;
em incrementos, incrementam-se variáveis. Quando um comando for é executado, a
seqüência de comandos da área das inicializações é executada. Em seguida, a expressão
que fixa as condições de manutenção da repetição é avaliada. Se o valor desta expressão
não for nulo, a seqüência de comandos é executada, sendo em seguida executada a
seqüência de comandos da área dos incrementos. Novamente a expressão das condições
de manutenção da repetição é avaliada e tudo se repete até que o seu valor seja igual a
zero.
Por exemplo, o programa
#include <stdio.h>
main()

61
Capítulo 4 Estruturas de Repetição

{
int i;
for (i = 1; i <= 10; i = i + 1)
printf("%d ", i);
}
exibe na tela os números 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. Por seu turno, o programa
#include <stdio.h>
main()
{
int i;
for (i = 10; i >= 0; i = i - 2)
printf("%d ", i);
}
exibe na tela os números 10, 8, 6, 4, 2, 0. Já o programa
#include <stdio.h>
main()
{
int i;
for (i = 1; i <= 10; i = i + 20)
printf("%d ", i);
}
exibe, apenas, o número 1.
A semântica do comando for implica que a seqüência de comandos pode não ser
executada nem uma única vez. Basta que na "primeira" execução do comando for a
expressão que controla a repetição assuma o valor zero. Por exemplo, o programa abaixo
não exibe nenhum valor na tela.
#include <stdio.h>
main()
{
int i;
for (i = 11; i <= 10; i = i + 20)
printf("%d ", i);
}
Com o comando for, a questão da geração de uma tabela de conversão de
temperaturas em graus Fahrenheit para graus Celsius seria simples.
#include <stdio.h>
#include <conio.h>
main()
{
int Fahrenheit;
float Celsius;
62
Capítulo 4 Estruturas de Repetição

clrscr();
printf("Tabela de conversão graus Fahrenheit/graus Celsius \n");
printf("-------------------------------------------------\n");
printf("\t Fahrenheit \t | \t Celsius\n");
printf("-------------------------------------------------\n");
for (Fahrenheit = 20; Fahrenheit <= 80; Fahrenheit = Fahrenheit + 1)
{
Celsius = 5.0*(Fahrenheit - 32)/9;
printf("\t %.2f \t | \t %.2f \n", Fahrenheit, Celsius);
}
}
Na execução do comando for, a variável Fahrenheit é inicializada com o valor 20,
este valor é comparado com 80, a correspondente temperatura em graus Celsius é
calculada e os dois valores são exibidos. Em seguida, o conteúdo de Fahrenheit é
incrementado de uma unidade e tudo se repete até que Fahrenheit atinja o valor 81. Desta
forma, a execução deste programa gera a seguinte tabela
Tabela de conversão graus Fahrenheit/graus Celsius
Fahrenheit Celsius
20 -6.67
21 -5.11
22 -5.56
23 -5.00
... ...
79 26,11
80 26,67
Observe que toda a repetição é controlada pela variável Fahrenheit. Num caso
como este, a variável em foco é chamada variável de controle da estrutura de repetição.
Vale observar também que, ao contrário de outras linguagens (Pascal, por exemplo), a
variável de controle não tem que ser necessariamente do tipo int. Por exemplo, se
quiséssemos que a tabela também fornecesse temperaturas em graus Fahrenheit
fracionárias (meio em meio grau, por exemplo), poderíamos executar o seguinte
programa.
#include <stdio.h>
#include <conio.h>
main()
{
float Celsius, Fahrenheit;
clrscr();
printf("Tabela de conversão graus Fahrenheit/graus Celsius \n");
printf("-------------------------------------------------\n");
printf("\t Fahrenheit \t | \t Celsius\n");
printf("-------------------------------------------------\n");

63
Capítulo 4 Estruturas de Repetição

for (Fahrenheit = 20; Fahrenheit <= 80; Fahrenheit = Fahrenheit + 0.5)


{
Celsius = 5.0*(Fahrenheit - 32)/9;
printf("\t %.2f \t | \t %.2f \n", Fahrenheit, Celsius);
}
}
Cabe observar que a seqüência de comandos cuja execução se pretende repetir
pode ser colocada na área dos incrementos. O programa acima ficaria então com a
seguinte forma:
#include <stdio.h>
#include <conio.h>
main()
{
float Celsius, Fahrenheit;
clrscr();
printf("Tabela de conversão graus Fahrenheit/graus Celsius \n");
printf("-------------------------------------------------\n");
printf("\t Fahrenheit \t | \t Celsius\n");
printf("-------------------------------------------------\n");
for (Fahrenheit = 20; Fahrenheit <= 80; Celsius = 5.0*(Fahrenheit – 32)/9,
printf("\t %.2f \t | \t %.2f \n", Fahrenheit, Celsius), Fahrenheit = Fahrenheit + 0.5);
}
Observe que, neste caso, o comando for é concluído com um ponto-e-vírgula e que
a leitura do programa fica bastante dificultada. Este fato fará com que esta prática não
seja incentivada ao longo do livro.
Observe também que aproveitamos a oportunidade para apresentar mais uma
função de biblioteca do sistema; trata-se de clrscr() cuja execução resulta na limpeza da
janela do usuário, o que evita que resultados da execução de um programa sejam
confundidos com resultados de execuções anteriores (clrscr vem de clear screen que
significa "limpa tela"). Como indicado na segunda instrução do programa, o cabeçalho da
função clrscr() encontra-se no arquivo conio.h.

4.3 O comando while


Para introduzir uma nova estrutura de repetição e cotejá-la com o comando for,
considere um programa para encontrar um divisor próprio de um inteiro dado (um divisor
próprio de um inteiro n é um divisor de n diferente dele e de 1). Esta questão é importante
na verificação do fato de um inteiro ser ou não primo: um número que não tem divisores
próprios é dito primo. Com a utilização do comando for teríamos a seguinte solução para
esta questão.
/*Programa que determina um divisor próprio de um inteiro */
#include <stdio.h>

64
Capítulo 4 Estruturas de Repetição

main()
{
int Num, i, Divisor;
printf("Digite um numero: ");
scanf("%d", &Num);
Divisor = 0;
for (i = 2; i < Num; i = i + 1)
if (Num % i == 0)
Divisor = i;
if (Divisor != 0)
printf("%d é divisor próprio de %d \n", Divisor, Num);
else
printf("%d não tem divisores próprios \n", Num);
}
Um problema com este programa é que ele retorna sempre, se existir, o maior
divisor próprio. Isto significa que se a entrada for um número par, a estrutura de repetição
não é interrompida quando o divisor 2 é encontrado, o que vai prejudicar a performance
do programa. Isto pode ser contornado, pois os compiladores C permitem que uma
variável de controle de um comando for tenha o seu conteúdo alterado dentro do próprio
comando. Com isto, o programa acima ficaria da seguinte forma.
#include <stdio.h>
main()
{
int Num, i, Divisor;
printf("Digite um número inteiro: ");
scanf("%d", &Num);
Divisor = 0;
for (i = 2; i < Num; i = i + 1)
if (Num % i == 0)
{
Divisor = i;
i = Num;
}
if (Divisor != 0)
printf("%d e' divisor próprio de %d \n", Divisor, Num);
else
printf("%d não tem divisores próprios \n", Num);
}
Nesta versão, quando o primeiro divisor próprio é encontrado, o comando i = Num;
faz com que a execução do comando for seja interrompida. A prática de encerrar um
comando for através da alteração do conteúdo da variável de controle não será aqui
incentivada pelo fato de que isto desestrutura o programa, dificultando sua legibilidade.
Na verdade, a questão central é que o comando for deve ser utilizado quando o número de

65
Capítulo 4 Estruturas de Repetição

repetições de execução de uma seqüência de comandos é conhecido a priori. Quando isto


não acontece (que é o caso do exemplo anterior: não se sabe a priori se e quando um
divisor próprio vai ser encontrado), deve-se usar o comando while, que possui a seguinte
sintaxe:
while (Expressão)
{
Seqüência de comandos
}
sendo os delimitadores opcionais se a seqüência possui um só comando (como acontece
nas outras estruturas de repetição).
A semântica deste comando é óbvia: a seqüência de comandos é executada
enquanto o valor da Expressão for diferente de zero. Naturalmente, pode ocorrer que a
seqüência de comandos não seja executada nenhuma vez, isto ocorrendo se o valor da
Expressão for igual a zero quando da "primeira" execução do comando (o teste é feito
antes da execução da seqüência de comandos). Por outro lado, é necessário que um dos
comandos da seqüência de comandos altere conteúdos de variáveis que aparecem na
Expressão de modo que em algum instante ela se torne igual a zero. Do contrário, a
seqüência de comandos terá sua execução repetida indefinidamente, o programa nunca
termina e, evidentemente, não executa a tarefa para a qual foi desenvolvido. Quando isto
acontece é comum se dizer que o programa está em loop infinito.
Com o comando while as questões levantadas acima sobre o programa para
determinar um divisor próprio de um inteiro dado são resolvidas e temos o seguinte
programa:
/*Programa que determina o menor divisor próprio de um inteiro */
#include <stdio.h>
#include <conio.h>
main()
{
int Num, d, Met;
printf("Digite o numero: ");
scanf("%d", &Num);
Met = Num/2;
d = 2;
while (Num % d != 0 && d < Met)
d++;
if (d <= Met)
printf("%d é divisor de %d \n", d, Num);
else
printf("%d não tem divisores próprios", Num);
getch();
}

66
Capítulo 4 Estruturas de Repetição

Observe que, ao contrário dos exemplos anteriores, a estrutura também seria


interrompida quando a variável com a qual se procura um divisor atingisse a "metade" do
inteiro; isto se explica pelo fato de que se um inteiro não possui um divisor próprio menor
do que sua "metade", então ele é primo. Esta versão ainda pode ser melhorada utilizando-
se o fato discutido em [Evaristo, J 2002] de que se um inteiro não possui um divisor
próprio menor do que ou igual a sua raiz quadrada, ele não tem divisores próprios.
Levando isso em conta, teríamos o seguinte programa.
/*Programa que determina o menor divisor próprio de um inteiro*/
#include <stdio.h>
#include <conio.h>
#include <math.h>
main()
{
int Num, d;
float r;
printf("Digite o numero: ");
scanf("%d", &Num);
r = sqrt(Num);
d = 2;
while (Num % d != 0 && d <= r)
d++;
if (d <= r)
printf("%d é divisor de %d \n", d, Num);
else
printf("%d não tem divisores próprios", Num);
getch();
}
Como já foi dito, um número inteiro que não tem divisores próprios é chamado
número primo. Assim, o comando de saída vinculado à opção else poderia ser
printf("%d é primo", Num);
Vale observar que o comando d = 0; dos programas acima atribuiu um valor inicial
à variável d. Este valor é incrementado enquanto um divisor não for encontrado. Um
comando de atribuição de um valor inicial a uma variável é chamado inicialização da
variável e os compiladores da linguagem C permitem que inicializações de variáveis
sejam feitas no instante em que elas são declaradas. Assim, as declarações de variáveis
dos programas acima poderiam ter sido feitas da seguinte forma:
int Num, i, d = 2;
Neste livro, às vezes vamos optar por inicializar as variáveis imediatamente antes
da necessidade. A razão desta opção é que há situações, como mostraremos no próximo
exemplo, em que não se pode simplesmente inicializar uma variável quando da sua
declaração.

67
Capítulo 4 Estruturas de Repetição

Observe que o último comando dos últimos dois programas foi uma chamada da
função getch(). Como já foi dito, a execução desta função requer a digitação de alguma
tecla. Isto faz com a janela do usuário (que exibe o resultado do processamento)
permaneça ativa até que uma tecla seja acionada.

Repetindo a execução de um programa


Uma outra aplicação importante do comando while diz respeito a execuções
sucessivas de um programa. O leitor deve ter observado que os programas anteriores são
executados apenas para uma entrada. Se quisermos a sua execução para outra entrada
precisamos executar o programa de novo.
Pode-se repetir a execução de um programa quantas vezes se queira, colocando-o
numa estrutura definida por um comando while, controlada pelo valor de algum dado de
entrada. Neste caso, o valor que encerra a execução pode ser informado dentro da
mensagem que indica a necessidade da digitação da entrada. O programa anterior poderia
ser então escrito da seguinte forma.
/*Programa que determina o menor divisor próprio de um inteiro */
#include <stdio.h>
#include <conio.h>
#include <math.h>
main()
{
int Num, d;
float r;
printf("Digite o numero (zero para encerrar): ");
Num = 1;
while (Num != 0)
{
scanf("%d", &Num);
r = sqrt(Num);
d = 2;
while (Num % d != 0 && d <= r)
d++;
if (d <= r)
printf("%d é divisor de %d \n", d, Num);
else
printf("%d é primo", Num);
}
}
Observe que, neste caso, a variável d não pode ser inicializada quando da sua
declaração. Observe também que não há necessidade da função getch(), pois a própria
repetição da execução deixa a janela do usuário aberta.

68
Capítulo 4 Estruturas de Repetição

Alguns programadores preferem que a repetição da execução de um programa seja


determinada por uma pergunta ao usuário do tipo “Deseja continuar (S/N)?”. Neste caso,
há necessidade de uma variável do tipo char para receber a resposta e controlar a
repetição da execução do programa.
#include <stdio.h>
#include <ctype.h>
#include <conio.h>
#include <math.h>
main()
{
int Num, d;
float r;
char c;
c = 'S';
while (toupper(c) == 'S')
{
printf("Digite o numero: ");
scanf("%d", &Num);
r = sqrt(Num);
d = 2;
while (Num % d != 0 && d <= r)
d++;
if (d <= r)
printf("%d é divisor de %d \n", d, Num);
else
printf("%d é primo", Num);
puts("Deseja continuar (S/N)?");
c = getch();
}
}
Vale lembrar que a função toupper() retorna o argumento no formato maiúsculo.
Esta função foi ativada aqui para que o usuário não se preocupe em digitar como resposta
letras maiúsculas. Qualquer letra que for digitada, a função a torna maiúscula e o sistema
a compara com S (maiúsculo).

4.4 O comando do while


Como dissemos na seção anterior, o número de execuções da seqüência de
comandos associada a um comando while pode ser zero. Há situações onde é importante
se garantir a execução de uma seqüência de comandos pelo menos uma vez. Uma
situação onde isto é importante é a verificação da consistência dos dados de entrada. Esta
ação consiste em se dotar o programa de recursos para recusar dados incompatíveis com a
entrada do programa, só "recebendo" dados que satisfaçam às especificações (lógicas ou

69
Capítulo 4 Estruturas de Repetição

estabelecidas) dos dados de entrada. Por exemplo, se a entrada é um número


correspondente a um mês do ano, o programa não deve aceitar uma entrada que seja
menor do que 1 nem maior do que 12. Uma solução para esta questão utilizando o
comando while poderia ser a seguinte:
int Mes;
printf("Digite o mês: ");
scanf("%d", &Mes);
while ((Mes < 1) || (Mes > 12))
{
printf("\a Digitacao errada! Digite de novo \n");
printf("Digite o mês: ");
scanf("%d", &Mes);
}
Observe que, como a verificação da condição de repetição é feita no "início" do
comando, há a necessidade de uma leitura antes da estrutura e outra dentro dela (só para
lembrar, \a emite um beep).
O comando do while define uma estrutura de repetição que garante que uma
seqüência de comandos seja executada pelo menos uma vez. Sua sintaxe é:
do
{
Seqüência de comandos;
}
while (Expressao);
e sua semântica é a seguinte: a seqüência de comandos é executada e a Expressão é
avaliada; se o valor da Expressão for diferente de zero, a seqüência de comandos é
novamente executada e tudo se repete; do contrário, o comando que segue a estrutura é
executado. É importante observar a necessidade do ponto-e-vírgula encerrado o do while.
A consistência da entrada de um dado relativo a um mês utilizando um comando do
while poderia ser a seguinte.
int Mes;
do
{
printf("Digite mes: ");
scanf("%d", &Mes);
if ((Mes < 1) || (Mes > 12))
printf("\a Digitacao errada! Digite de novo \n");
}
while ((Mes < 1) || (Mes > 12));
A utilização do comando do while para execuções sucessivas de um programa é
mais natural, quando a repetição da execução é feita através da resposta à pergunta
Deseja continuar (S/N)? . Teríamos algo como:

70
Capítulo 4 Estruturas de Repetição

#include <stdio.h>
main()
{
char Resp;
do
Seqüência dec comandos do programa propriamente dito;
printf("Deseja continuar (S/N)?");
scanf("%c", &Resp);
while (toupper(Resp) == 'S');
}

4.5 O comando break em estruturas de repetição


Da mesma forma que sua ativação num case interrompe a execução de uma
estrutura switch, a execução de um comando break dentro de uma estrutura de repetição
interrompe as execuções da seqüência de comandos da estrutura, mesmo que a condição
de manutenção da repetição não tenha sido negada. Com o uso do break, o programa
acima que determinava o menor divisor próprio de um inteiro poderia ter a seguinte
forma:
#include <stdio.h>
#include <math.h>
main()
{
float r;
int Num, d;
printf("Digite um numero : ");
scanf("%d", &Num);
d = 2;
r = sqrt(Num);
while (d <= r)
if (Num % d == 0)
break;
else
d = d + 1;
if (d <= r)
printf("%d e' divisor proprio de %d \n", d, Num);
else
printf("%d e' primo \n", Num);
}
Neste livro, o uso do break em estruturas de repetição será desestimulado visto que
sua utilização pode trazer problemas de legibilidade aos programas.

71
Capítulo 4 Estruturas de Repetição

4.6 Exemplos Parte III


1. Consideremos um programa para determinar a soma dos n primeiros números
ímpares, n dado. Por exemplo, se for fornecido para n o valor 6, o programa deve retornar
36, pois 1 + 3 + 5 + 7 + 9 + 11 = 36. Naturalmente, o sistema pode gerar os números
impares que se pretende somar, através do comando Impar = 1 e da repetição do
comando Impar = Impar + 2. Naturalmente, também, para que o sistema gere o próximo
ímpar, o anterior já deve ter sido somado. Isto pode ser feito através do comando
Soma = 0 e da repetição do comando Soma = Soma + Impar. Temos então o seguinte
programa.
/*Programa que soma os n primeiros números ímpar, n dado*/
#include <stdio.h>
main()
{
int Soma, Impar, n, i;
printf("Digite o valor de n: ");
scanf("%d", &n);
Impar = 1;
Soma = 0;
for (i = 1; i <= n; i = i + 1)
{
Soma = Soma + Impar;
Impar = Impar + 2;
}
printf("Soma dos %d primeiros números impares: %d \n", n, Soma);
}
Observe que os comandos Impar = 1 e Soma = 0 atribuem um valor inicial às
variáveis para que estes valores iniciais possam ser utilizados nas primeiras execuções
dos comandos Soma = Soma + Impar e Impar = Impar + 2. Como já dissemos, nos
referimos a comandos que atribuem valores iniciais a variáveis para que estes valores
possam ser utilizados na primeira execução de um comando que terá sua execução
repetida como inicialização da variável.
Uma outra observação interessante é que, como existe uma fórmula que dá o i-
ésimo número ímpar (ai = 2i - 1), o programa acima poderia ser escrito de uma forma
mais elegante, prescindindo, inclusive, da variável Impar.
/*Programa que soma os n primeiros números impar, n dado*/
#include <stdio.h>
main()
{
int Soma, n, i;
printf("Digite o valor de n: ");
scanf("%d", &n);

72
Capítulo 4 Estruturas de Repetição

Soma = 0;
for (i = 1; i <= n; i = i + 1)
Soma = Soma + 2*i - 1;
printf("Soma dos %d primeiros números impares: %d \n", n, Soma);
}
Optamos por apresentar a primeira versão pelo fato de que nem sempre a fórmula
para gerar os termos da seqüência que se pretende somar é tão simples ou é muito
conhecida. Por exemplo, o exercício número 2 da seção 4.7 pede para somar os quadrados
dos n primeiros números naturais e, neste caso, embora a fórmula exista, ela não é tão
conhecida.
2. Um dos exemplos da seção anterior apresentava um programa que determinava,
se existisse, um divisor próprio de um inteiro dado. Imaginemos agora que queiramos um
programa que apresente a lista de todos os divisores de um inteiro n dado. Neste caso, o
programa pode percorrer todos os inteiros desde um até a metade de n verificando se cada
um deles é um seu divisor. Temos então o seguinte programa.
#include <stdio.h>
main()
{
int Num, i;
printf("Digite o numero: ");
scanf("%d", &Num);
printf("Divisores próprios de %d: \n", Num);
for (i = 2; i <= Num/2; i = i + 1)
if (Num % i == 0)
printf("%d \n", i);
}
Vale observar que, ao contrário do que foi dito na seção 2.9, os valores de saída
deste programa não estão sendo armazenados. O que acontece é que ainda não temos
condições de armazenar uma quantidade indefinida de elementos. Este problema será
resolvido no capítulo 6.
3. Na seção 1.5 discutimos um algoritmo que determinava o quociente e o resto da
divisão entre dois inteiros positivos dados. Embora os compiladores de C possuam o
operador % que calcula o resto de uma divisão inteira entre dois inteiros positivos, vamos
apresentar, por ser interessante, a implementação do algoritmo referido.
/*Programa que determina o quociente e o resto da divisão entre dois inteiros
positivos*/
#include <stdio.h>
main()
{
int Dividendo, Divisor, Quoc, Resto;
printf("Digite o dividendo e o divisor (diferente de zero!): ");
scanf("%d %d", &Dividendo, &Divisor);

73
Capítulo 4 Estruturas de Repetição

Quoc = 1;
while (Quoc * Divisor <= Dividendo)
Quoc = Quoc + 1;
Quoc = Quoc - 1;
Resto = Dividendo - Quoc * Divisor;
printf("Quociente e resto da divisão de %d por %d: %d e %d\n", Dividendo,
Divisor, Quoc, Resto);
}
4. Em muitos casos há necessidade de que um dos comandos da seqüência que terá
sua execução repetida através de uma estrutura de repetição seja uma outra estrutura de
repetição (num caso deste dizemos que as estruturas estão aninhadas). Para um exemplo,
sejam A = {1, 2, 3, ..., n} e um programa que pretenda exibir o produto cartesiano AxA.
Observe que para cada valor da primeira componente o programa deve gerar todas as
segundas componentes. Devemos ter, portanto, uma estrutura de repetição para gerar as
primeiras componentes e uma outra, vinculada a cada valor da primeira componente, para
gerar as segundas.
/*Programa para gerar um produto cartesiano*/
#include <stdio.h>
main()
{
int n, i, j;
printf("Digite o numero de elementos do conjunto: ");
scanf("%d", &n);
printf("{");
for (i = 1; i <= n; i = i + 1)
for (j = 1; j <= n; j = j + 1)
printf("(%d, %d), ", i, j);
printf("}");
}
5. É interessante observar que a variável de controle da estrutura interna pode
depender da variável de controle da estrutura externa. Por exemplo, se ao invés dos pares
ordenados, quiséssemos os subconjuntos do conjunto A com dois elementos, o programa
não deveria exibir o subconjunto {1, 1}, que possui um só elemento, e deveria exibir
apenas um dos subconjuntos {1, 2} e {2, 1} já que eles são iguais. Isto pode ser obtido
inicializando j com uma unidade maior do que o valor de i.
/*Programa para gerar um conjunto de subconjuntos de um conjunto*/
#include <stdio.h>
main()
{
int n, i, j;
printf("Digite o numero de elementos do conjunto: ");
scanf("%d", &n);

74
Capítulo 4 Estruturas de Repetição

printf("{");
for (i = 1; i <= n; i = i + 1)
for (j = i + 1; j <= n; j = j + 1)
printf("{%d, %d}, ", i, j);
printf("}");
}
6. Seja um programa para o cálculo da média de uma dada quantidade de números.
Na seção 1.5 discutimos um algoritmo para determinar a média de 10.000 números dados.
Na ocasião discutimos que utilizaríamos uma única variável para receber os números
sendo que um valor subseqüente só seria solicitado depois que o anterior fosse
"processado". A diferença agora é que a quantidade de números será um dado de entrada,
o que torna o programa de aplicação mais variada. Como a quantidade de números será
dada, pode-se utilizar uma estrutura for para receber e somar os números.
/*Programa para calcular a media de n numeros, n dado*/
#include <stdio.h>
main()
{
int n, i;
float Num, Soma, Media;
Soma = 0;
printf("Digite o numero de elementos: ");
scanf("%d", &n);
printf("\n Digite os elementos:");
for (i = 1; i <= n; i = i + 1)
{
scanf("%f", &Num);
Soma = Soma + Num;
}
Media = Soma/n;
printf("Media = %f", Media);
}
7. O exemplo acima tem o inconveniente de que sua execução exige que se saiba
anteriormente a quantidade de números e isto não ocorre na maioria dos casos. Vejamos
então um programa para determinar a média de uma relação de números dados, sem que
se conheça previamente a quantidade deles. Neste caso, não devemos utilizar o comando
for, pois não sabemos o número de repetições! Assim, o comando while deve ser
utilizado; porém, uma pergunta deve ser formulada: qual a expressão lógica que
controlará a estrutura? A solução é "acrescentar" à relação um valor sabidamente
diferente dos valores da relação e utilizar este valor para controlar a repetição. Este valor
aqui referido é conhecido como flag. Como dito logo acima, deve-se ter certeza que o flag
não consta da relação. Isto não é complicado, pois ao se escrever um programa se tem
conhecimento de que valores o programa vai manipular e a escolha do flag fica facilitada.
Por exemplo, se o programa vai manipular números positivos pode-se usar -1 para o flag.

75
Capítulo 4 Estruturas de Repetição

Além do flag, o programa necessita de uma variável (no caso Cont de contador) que
determine a quantidade de números da relação, pois este valor será utilizado no cálculo da
média.
/*Programa para calcular a media de uma relacao de numeros*/
#include <stdio.h>
main()
{
int Cont;
float Num, Soma, Media;
Soma = 0;
printf("\n Digite os elementos(-1 para encerrar):");
scanf("%d", &Num);
Cont = 0;
while (Num != -1)
{
Soma = Soma + Num;
Cont = Cont + 1;
scanf("%f", &Num);
}
Media = Soma/Cont;
printf("Media = %f", Media);
}
8. Na seção 1.6 apresentamos o algoritmo de Euclides para a determinação do
máximo divisor comum de dois números dados. Para relembrar, vejamos como calcular o
máximo divisor comum de 204 e 84.

2 2 3
204 84 36 12
36 12 0
O algoritmo é o seguinte: divide-se 204 por 84 obtendo-se resto 36; a partir daí
repete-se divisões até que o resto seja zero, sendo o dividendo da divisão atual o divisor
da divisão anterior e o divisor da divisão atual o resto da divisão anterior. O último
divisor é o máximo divisor procurado.
Escrever este algoritmo numa linguagem de programação é muito simples, pois
uma estrutura de repetição e comandos de atribuição permitem que se obtenha facilmente
a seqüência de divisões desejadas.
/*Programa para determinar o máximo divisor comum de dois números positivos*/
#include <stdio.h>
main()
{
int x, y, a, b, Mdc, Resto;
printf("Digite os dois numeros \n");

76
Capítulo 4 Estruturas de Repetição

scanf("%d %d", &x, &y);


a = x;
b = y;
Resto = a % b;
while (Resto != 0)
{
a = b;
b = Resto;
Resto = a % b;
}
Mdc = b;
printf("mdc(%d, %d) = %d \n", x, y, Mdc);
}
Note a necessidade da utilização das variáveis a e b. Elas são utilizadas no
processamento e terão seus conteúdos alterados durante a execução do programa. Se
usássemos as variáveis x e y, os valores dos dados de entrada seriam perdidos, o que,
evidentemente, não deve ocorrer. No capítulo 5, quando estudarmos funções, estas
variáveis terão outra conotação.
À primeira vista, o programa deveria inicialmente determinar o maior dos números
x e y, armazenando-o na variável a. O quadro seguinte mostra que isto não é necessário,
apresentando a simulação da execução do programa para x = 68 e y = 148.

x y a b Resto Mdc
68 148 68 148 68
148 68 12
68 12 8
12 8 4
8 4 0
4
9. Um outro algoritmo matemático cuja implementação numa linguagem de
programação apresenta um bom exemplo do uso de estruturas de repetição é o algoritmo
para a determinação do mínimo múltiplo comum (mmc) de dois números dados. Como
indica a própria denominação, o mínimo múltiplo comum de dois números é o menor
número que é divisível pelos dois números. A matemática prova que o mmc de dois
números é o produto dos divisores primos dos dois números, comuns ou não, ambos com
as suas multiplicidades. Para se obter os divisores primos, realiza-se divisões sucessivas
pelos primos que são divisores de pelo menos um dos números.
A tabela seguinte mostra o cálculo do mínimo múltiplo comum dos números 360 e
420, como nos é ensinado no ensino fundamental.

77
Capítulo 4 Estruturas de Repetição

360, 420 2
180, 210 2
90, 105 2
45, 105 3
15, 35 3
5, 35 5
1, 7 7
1, 1 MMC = 2*2*2*3*3*5*7 = 2 520
Observe que, quando um divisor primo é encontrado, repete-se a divisão, com o
quociente no lugar do dividendo, até se obter um número que não é múltiplo daquele
divisor. Aí, incrementa-se o tal divisor. Isto é feito até que ambos os quocientes sejam
iguais a 1. Temos o seguinte programa.
/*Programa para determinar o minimo multiplo comum de dois numeros
positivos*/
#include <stdio.h>
main()
{
int x, y, d, a, b, i, Mmc;
printf("Digite os dois numeros \n");
scanf("%d %d", &x, &y);
a = x;
b = y;
Mmc = 1;
i = 2;
while ((a != 1) || (b != 1))
{
while ((a % i == 0) || (b % i == 0))
{
if (a % i == 0)
a = a/i;
if (b % i == 0)
b = b/i;
Mmc = Mmc * i;
}
i = i + 1;
}
printf("mmc(%d, %d) = %d \n", x, y, Mmc);
}
10. A questão do mínimo múltiplo comum é muito interessante como exemplo para
a aprendizagem de programação pelo fato de que podemos apresentar um outro algoritmo
de compreensão bem mais simples que o anterior. A idéia é a seguinte: x, 2x, 3x, etc. são
múltiplos de x. Para se obter o mínimo múltiplo comum basta que se tome o primeiro
destes números que seja múltiplo também de y.

78
Capítulo 4 Estruturas de Repetição

/*Programa para determinar o mínimo múltiplo comum de dois números


positivos*/
#include <stdio.h>
main()
{
int x, y, i, Mmc;
printf("Digite os dois numeros \n");
scanf("%d %d", &x, &y);
Mmc = x;
while (Mmc % y != 0)
Mmc = Mmc + x;
printf("mmc(%d, %d) = %d \n", x, y, Mmc);
}

4.7 Exercícios propostos


1. Mostre a configuração da tela após a execução do programa
#include <stdio.h>
main()
{
int i, a, q, Termo;
for (i = 5; i > 0; i = i - 1)
{
a = i;
q = 3;
Termo = a;
while (Termo <= 9 * a)
{
printf("%d \n", Termo);
Termo = Termo * q;
}
}
}
2. Escreva um programa que determine a soma dos quadrados dos n primeiros
números naturais, n dado.
3. Escreva um programa para calcular a soma dos n primeiros termos das
seqüências abaixo, n dado.
1 3 5 
a)  , , , ...
2 5 8 
 1 1 1 
b)  1, − , , − , ...
 2 3 4 

79
Capítulo 4 Estruturas de Repetição

4. O exemplo 10 da seção anterior apresentava uma solução para a questão do


mínimo múltiplo comum de simples compreensão. Um problema que esta solução possui é
que se o primeiro valor digitado fosse muito menor do que o segundo, o número de
repetições necessárias para se chegar ao mmc seria muito grande. Refaça o exemplo,
tomando o maior dos números dados como base do raciocínio ali utilizado.
5. Um número inteiro é dito perfeito se o dobro dele é igual à soma de todos os
seus divisores. Por exemplo, como os divisores de 6 são 1, 2, 3 e 6 e 1 + 2 + 3 + 6 = 12, 6
é perfeito. A matemática ainda não sabe se a quantidade de números perfeitos é ou não
finita. Escreva um programa que liste todos os números perfeitos menores que um inteiro
n dado.
6. O número 3.025 possui a seguinte característica: 30 + 25 = 55 e 552 = 3 025.
Escreva um programa que escreva todos os números com quatro algarismos que possuem
a citada característica.
7. Escreva um programa que escreva todos os pares de números de dois algarismos
que apresentam a seguinte propriedade: o produto dos números não se altera se os dígitos
são invertidos. Por exemplo, 93x13 = 39x31 = 1.209.
8. Escreva um programa para determinar o número de algarismos de um número
inteiro positivo dado.
9. Escreva um programa que verifique se um dado número inteiro positivo é o
produto de dois números primos. Por exemplo, 15 é o produto de dois primos, pois 15 = 3
x 5; 20 não é o produto de dois primos, pois 20 = 2 x 10 e 10 não é primo.
10. Quando um número não é produto de dois números primos, a matemática prova
que ele pode ser escrito de maneira única como um produto de potências de números
primos distintos. Este produto é chamado de decomposição em fatores primos do número
e os expoentes são chamados de multiplicidade do primo respectivo. Por exemplo, 360 =
23x32x5. Escreva um programa que obtenha a decomposição em fatores primos de um
inteiro dado.
11. Escreva um programa que transforme o computador numa urna eletrônica para
eleição, em segundo turno, para presidente de um certo país, às quais concorrem os
candidatos 83-Alibabá e 93-Alcapone. Cada voto deve ser dado pelo número do
candidato, permitindo-se ainda o voto 00 para voto em branco. Qualquer voto diferente
dos já citados é considerado nulo; em qualquer situação, o eleitor deve ser consultado
quanto à confirmação do seu voto. No final da eleição o programa deve emitir um
relatório contendo a votação de cada candidato, a quantidade votos em branco, a
quantidade de votos nulos e o candidato eleito.
12. A sequência de Fibbonaci é a seqüência (1, 1, 2, 3, 5, 8, 13, ...) definida por
1, se n = 1 ou n = 2
an = 
an −1 + an − 2 , se n > 2
Escreva um programa que determine o n-ésimo termo desta seqüência, n dado.

80
Capítulo 4 Estruturas de Repetição

1 1 1
13. A série harmônica S = 1 + + +...+ +... é divergente. Isto significa que
2 3 n
1 1 1
dado qualquer real k existe n0 tal que 1 + + +...+ > k . Escreva um programa que
2 3 n0
dado um real k determine o menor inteiro n0 tal que S > k. Por exemplo se k = 2, o
1 1 1 1 1
programa deve fornecer n0 = 4, pois 1 + + + = 2 ,083.... e 1 + + = 1,8333....
2 3 4 2 3
14. Dois números inteiros são ditos amigos se a soma dos divisores de cada um
deles (menores que eles) é igual ao outro. Por exemplo, os divisores de 220 são 1, 2, 4, 5,
10, 11, 20, 22, 44, 55 e 110 e 1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110 = 284 e
os divisores de 284 são 1, 2, 4, 71 e 142 e 1 + 2 + 4 + 71 + 142 = 220. Escreva um
programa que determine todos os pares de inteiros amigos menores que um inteiro dado.
15. Escreva um programa que escreva todos os subconjuntos com três elementos
do conjunto {1, 2, 3, ..., n}, n dado.

81
5. Funções e Ponteiros
5.1 O que são funções
Como dissemos no capítulo 2, um programa em C pode (e deve) ser escrito como
um conjunto de funções, que são executadas a partir da execução de uma função
denominada main(). Cada função pode conter declarações de variáveis, instruções,
ativações de funções do sistema e de outras funções definidas pelo programador.
Naturalmente, o objetivo de uma função deve ser a realização de alguma "sub-tarefa"
específica da tarefa que o programa pretende realizar. Assim, pode-se escrever funções
para a leitura dos dados de entrada, para a saída do programa, para a determinação da
média de vários elementos, para a troca dos conteúdos de uma variável, para o cálculo do
máximo divisor comum de dois números dados, etc. Normalmente, a realização da "sub-
tarefa" para a qual a função foi escrita é chamada de retorno da função. Este retorno pode
ser a realização de uma ação genérica, como a leitura dos dados de entrada, ou um valor
específico, como o cálculo do máximo divisor comum de dois números dados.
Como foi dito na seção citada, uma função deve ser definida com a seguinte
sintaxe:
Tipo de Dado Identificador da função(Lista de parâmetros)
{
Declaração de variáveis
Seqüência de instruções
}
onde, como também já foi dito, o conjunto Tipo de Dado Identificador da função(Lista de
parâmetros) é chamado protótipo da função. Aí, Tipo de dado é int, char, float, ou um
dos seus "múltiplos", quando a função deve retornar um valor específico e void se a
função deve realizar uma ação genérica sem retornar um valor definido. Por seu turno,
Lista de parâmetros é um conjunto de variáveis utilizadas para a função receber os
valores para os quais a função deve ser executada; estes valores são chamados
argumentos, que, comparando uma função a um programa, constituem os dados de
entrada da função.
Na declaração de variáveis são declaradas as variáveis que as instruções da função
vão manipular internamente. Estas variáveis (e os parâmetros da função) só são
acessáveis pelas instruções da função e, por esta razão, são chamadas variáveis locais (na
seção 5.5 são apresentados maiores detalhes).
Se a função deve retornar um valor, uma de suas instruções deve ter a seguinte
sintaxe:
return (expressão);
sendo os parênteses facultativos. A semântica desta instrução é evidente: a expressão é
avaliada e o seu valor é retornado à função que a ativou. Além disso (e isto agora não é
Capítulo 5 Funções e Ponteiros

evidente), a execução desta instrução interrompe a execução da função e o processamento


retorna à função que ativou a função em discussão.
A ativação (ou chamada) da função por outra função se faz com a referência ao
identificador da função seguido dos argumentos em relação aos quais se pretende
executar a função. Por exemplo, o cálculo do máximo divisor comum de dois números
dados, discutido no capítulo anterior, poderia ser reescrito da seguinte forma:
/*Função que retorna o máximo divisor comum de dois números positivos dados*/
int MaxDivCom(int x, int y)
{
int Resto;
Resto = x % y;
while (Resto != 0)
{
x = y;
y = Resto;
Resto = x % y;
}
return (y);
}
/*Função principal: recebe os números e ativa a função MaxDivCom()*/
main()
{
int Mdc, a, b;
printf("Digite os dois inteiros");
scanf("%d %d", &a, &b);
Mdc = MaxDivCom(a, b);
printf("Mdc(%d, %d) = %d \n", a, b, Mdc);
}
Se este programa for executado para a entrada a = 204 e b = 184, a execução do
comando
Mdc = MaxDivCom(a, b)
chamaria a execução da função, recebendo o parâmetro x o conteúdo de a e o parâmetro y
o conteúdo de b. Ou seja, as instruções da função seriam executadas para a entrada
x = 204 e y = 184 e teríamos a seguinte seqüência de valores para as variáveis locais x, y
e Resto até a interrupção da estrutura while:
x y Resto
204 184 20
184 20 4
20 4 0
Neste instante, a instrução return (y) é executada e o processamento retorna ao
comando

83
Capítulo 5 Funções e Ponteiros

Mdc = MaxDivCom(a, b)
e o valor 4 é armazenado na variável Mdc.
Em alguns sistemas, uma função só pode ativar uma outra função que foi definida
previamente (como no exemplo anterior) ou cujo protótipo esteja explicitado como uma
das suas instruções. Por exemplo, o programa aqui referido poderia ter o seguinte
formato:
main()
{
int Mdc, a, b;
int MaxDivCom(int x, int y); /*Protótipo da função que vai ser definida
posteriormente */
printf("Digite os dois inteiros");
scanf("%d %d", &a, &b);
Mdc = MaxDivCom(a, b);
printf("Mdc(%d, %d) = %d \n", a, b, Mdc);
}

int MaxDivCom(int x, int y)


{
int Resto;
Resto = x % y;
while (Resto != 0)
{
x = y;
y = Resto;
Resto = x % y;
}
return (y);
}

5.2 Para que servem funções


Evidentemente, não há grandes vantagens do programa que calcula o máximo
divisor comum escrito com uma função em relação àquele do exemplo já citado, que
realizava todas as ações necessárias dentro da função main(). Na verdade, a utilização de
funções só é bastante vantajosa quando se trata de programas grandes, capazes de realizar
diversas tarefas independentes, mas relacionadas. Por exemplo, um programa que
gerencie as contas correntes de um banco deve ser capaz, entre outras coisas, de fornecer
o saldo de uma dada conta; atualizar o saldo em função da ocorrência de uma retirada ou
de um depósito; cadastrar uma nova conta; excluir do cadastro uma dada conta, etc.
Naturalmente, embora estas tarefas estejam relacionadas, deve-se pretender que elas
sejam realizadas de forma independente, pois um dado cliente num dado momento pode
querer a realização de apenas uma delas.

84
Capítulo 5 Funções e Ponteiros

Num caso como este, o programa deve possuir uma função para cada uma das
tarefas pretendidas, ficando a cargo da função main() a chamada de uma ou de outra
função de acordo com a tarefa pretendida. Isto permite que vários programadores
desenvolvam o programa (cada um desenvolvendo algumas funções), facilita a realização
de testes de correção do programa (as funções podem ser testadas de forma isolada) e a
manutenção posterior do programa (modificações necessárias no programa podem ficar
restritas a modificações em algumas das funções). Na verdade, a modularização do
programa só traz benefícios e deve ser uma prática de todo programador.
É comum que um programa "multi-tarefa" como o exemplificado acima seja
iniciado com a exposição para o usuário das diversas tarefas que ele é capaz de executar.
Normalmente, este conjunto de tarefas é chamado de menu de opções e pode ser obtido
através de uma função que não retorna nenhum valor. Considerando apenas as tarefas
listadas acima, o menu de opções do programa referido poderia ser construído a partir da
seguinte função.
void Menu()
{
printf("1-Saldo \n 2-Depósito \n 3-Retirada \n 4-Nova conta \n 5-Encerra conta \n
6-Sai do programa);"
}
Neste caso, uma das primeiras instruções da função main() é a ativação da função
Menu() com a simples referência ao seu identificador seguido de parênteses vazios e de
ponto-e-vírgula:
main()
{
Menu();
...
}
Observe que esse exemplo mostra uma função que não retorna um valor (daí ser do
tipo void) e cuja Lista de parâmetros é vazia.
Atualmente, as linguagens visuais (VisualBasic, Delphi e outras) disponibilizam
menus de opções através de interfaces programa/usuário (contendo botões, banners e
outras denominações) e as ativações das funções que executam a tarefa pretendida é feita
através de mouses ou mesmo através de toque manual na tela do computador. Existem
algumas bibliotecas gráficas que permitem que se criem interfaces de programas em C;
porém, o estudo destas bibliotecas não está no escopo deste livro.
Outra aplicação importante de funções se dá quando há necessidade de que o
programa determine a mesma grandeza para valores diferentes. Um exemplo típico desta
necessidade aparece num programa que determine medidas estatísticas, como média
aritmética, mediana, desvio médio, desvio padrão, de uma relação de números. Como o
desvio médio é a média aritmética dos valores absolutos dos desvios em relação à média,
o seu cálculo exigirá a determinação da média aritmética da relação e a média aritmética
dos desvios. Escreveremos então uma função para o cálculo da média de uma relação

85
Capítulo 5 Funções e Ponteiros

qualquer e a utilizaremos para os cálculos das duas médias necessárias. Este exemplo será
visto no capítulo seguinte.

5.3 Passagem de parâmetros


Uma outra possível utilização de funções é para substituir uma seqüência de
instruções que se repete em várias partes do programa. Por exemplo, o exemplo 3 da
seção 3.4 apresentava um programa para ordenar três números:
/* Programa para ordenar tres numeros dados*/
#include <stdio.h>
main()
{
float x, y, z, Aux;
printf("Digite os tres numeros");
scanf("%f %f %f", &x, &y, &z);
printf("Numeros dados: %f , %f , %f \n", x, y, z);
if ((x > y) || (x > z)) /* verifica se x não é o menor */
if (y < z) /* neste caso y é o menor */
{
Aux = x; /* troca os conteúdos de x e de y */
x = y;
y = Aux;
}
else /* neste caso z é o menor */
{
Aux = x; /* troca os conteúdos de x e de z */
x = z;
z = Aux;
}
if (y > z) /* verifica se z e y ainda não estão ordenados */
{
Aux = y; /* troca o conteúdo de y e de z */
y = z;
z = Aux;
}
printf("Numeros ordenados: %f , %f , %f \n", x, y, z);
}
Observe que uma seqüência de comandos com o mesmo objetivo (trocar os
conteúdos de duas variáveis) se repete. Num caso como este, poderíamos escrever uma
função que realizasse aquela ação pretendida e, então, esta função seria utilizada para
substituir a seqüência referida.
Por enquanto temos o seguinte problema. Se definirmos a função

86
Capítulo 5 Funções e Ponteiros

void Troca(float x, float y)


{
float Aux;
Aux = x;
x = y;
y = Aux;
}
e a executarmos passando as variáveis a e b, apenas os conteúdos de a e de b serão
passados para x e para y e a troca realizada pela função só afeta os conteúdos de x e de y,
não modificando os conteúdos de a e de b que é o que se pretendia. Ou seja, a função
Troca recebe apenas os "valores" de a e de b e as ações realizadas pela função interfere
apenas nos parâmetros x e y, não alterando nada em relação aos argumentos a e b. Neste
caso, dizemos que os parâmetros foram passados por valor.
O sistema Turbo C++ 3.0 oferece a possibilidade de que a execução de uma função
altere conteúdos de variáveis “não locais”. Para isto, no protótipo da função os
parâmetros devem ser precedidos de &. Neste caso, os argumentos para ativação da
função têm que ser variáveis e qualquer alteração no conteúdo do parâmetro se reflete no
conteúdo da variável argumento. Diz-se então que a passagem dos parâmetros é feita por
referência.
Com este tipo de passagem de parâmetro, o programa acima poderia ser escrito
da seguinte forma:
/* Programa para ordenar tres numeros dados*/
#include <stdio.h>
void Troca(float &a, float &b)
{
float Aux;
Aux = a;
a = b;
b = Aux;
}
main()
{
float x, y, z, Aux;
printf("Digite os tres numeros");
scanf("%f %f %f", &x, &y, &z);
printf("Numeros dados: %f , %f , %f \n", x, y, z);
if ((x > y) || (x > z))
if (y < z)
Troca(x, y);
else
Troca(x, z);
if (y > z)
Troca(y, z);

87
Capítulo 5 Funções e Ponteiros

printf("Numeros ordenados: %f , %f , %f \n", x, y, z);


}
A passagem de parâmetro por referência permite que entrada de dados seja feita
através de uma função. Isto pode ser útil, por exemplo, em programas multitarefas em que
o número de entradas pode variar de acordo com a tarefa pretendida. Para exemplificar
apresentaremos um programa para tratar números complexos. Naturalmente, um
programa com este objetivo deve estar apto a somar e multiplicar complexos, casos em
que a entrada será dois números complexos, e a determinar o módulo e a forma polar de
um complexo, quando entrada será apenas de um número. Além de exemplificar a entrada
de dados através de uma função, o programa abaixo exemplifica um programa multitarefa
“completo”.
/*Programa para álgebra dos números complexos*/
#include <stdio.h>
#include <math.h>
void LeComplexo(float &a, float &b)
{
puts("Digite a parte real <enter> parte imaginaria");
scanf("%f %f", &a, &b);
}
float Modulo(float a, float b)
{
return sqrt(a*a + b*b);
}
void Polar(float a, float b, float &c, float &d)
{
c = Modulo(a, b);
d = asin(b/c);
}
void Soma(float a, float b, float c, float d, float &e, float &f)
{
e = a + c;
f = b + d;
}
void Produto(float a, float b, float c, float d, float &e, float &f)
{
e = a*c - b*d;
f = a*d + b*c;
}

88
Capítulo 5 Funções e Ponteiros

void Menu()
{
puts("1-Modulo \n 2- Forma polar \n 3-Soma \n 4-Produto \n 5-Encerra \n
Digite sua opcao: ");
}

main()
{
float x, y, z, w, t, u;
int Opc;
Menu();
scanf("%d", &Opc);
switch (Opc)
{
case 1:
LeComplexo(x, y);
z = Modulo(x, y);
printf("|%.2f + %.2fi| = %.2f", x, y, z);
break;
case 2:
LeComplexo(x, y);
Polar(x, y, z, w);
printf("%.2f + %.2fi = %.2f(cos%.2f + isen%.2f)", x, y, z, w, w);
break;
case 3:
LeComplexo(x, y);
LeComplexo(z, w);
Soma(x, y, z, w, t, u);
printf("(%.2f + %.2fi) + (%.2f + %.2fi) = %.2f + %.2fi", x, y, z, w, t,
u);
break;
case 4:
LeComplexo(x, y);
LeComplexo(z, w);
Produto(x, y, z, w, t, u);
printf("(%.2f + %.2fi) + (%.2f + %.2fi) = %.2f + %.2fi", x, y, z, w, t,
u);
break;
}
}
Embora o help do Turbo C++ 3.0 afirme que em C a única passagem de parâmetro
é por valor, conseguimos, no Turbo C 2.01, uma forma de passagem de parâmetros por
referência. Para isto utilizaremos ponteiros que, além de permitir esta forma de passagem
de parâmetros, tem outras aplicações importantes em programação em C e em C++.

89
Capítulo 5 Funções e Ponteiros

5.4 Ponteiros
No capítulo 2, foi dito que a cada posição de memória é associado um número
inteiro chamado endereço da posição de memória. Como uma variável é uma posição de
memória, a cada variável é associado um endereço.
Um ponteiro é uma variável capaz de armazenar um endereço de outra variável,
sendo declarado com a seguinte sintaxe:
Tipo de dado *Identificador;
A semântica desta declaração pode ser assim entendida: Identificador é capaz de
armazenar o endereço de uma variável de tipo Tipo de dado. Por exemplo, uma
declaração do tipo
int *p;
indica que p é uma variável capaz de armazenar o endereço de uma variável do tipo int.
Na prática dizemos que p aponta para um inteiro. Assim, ponteiros também são
chamados apontadores. Como ponteiros são variáveis, pode-se atribuir um ponteiro a
outro do mesmo tipo. Por exemplo, são válidas as seguintes instruções:
int *p, *t, i;
p = &i;
t = p;
pois, como dissemos no capítulo 2, o operador de endereço & fornece o endereço da
variável em que ele está operando. Deste modo, p receberá o endereço de i o que também
acontecerá com t quando da execução do comando t = p.
Se os ponteiros apontam para variáveis de tipos diferentes, a atribuição de um
ponteiro a outro requer a utilização de um molde, operador unário capaz de alterar o tipo
de uma variável e que definido através da seguinte sintaxe
(Tipo de Dado)Expressão
A execução desta instrução faz com que o valor de Expressão seja convertido para
o tipo Tipo de dado. Por exemplo, o programa
#include <stdio.h>
main()
{
float f;
char c;
c = 'A';
f = (float)(c + ' ');
printf("%f \n", f);
}

90
Capítulo 5 Funções e Ponteiros

tem como saída 97.000000, pois o código ASCII de A é 65 e o do espaço em branco, 32


(lembre-se que o tipo char é um tipo inteiro e, portanto, dois valores deste tipo podem ser
somados).
Se p é um ponteiro, a indicação *p num programa acessa o conteúdo da variável
para a qual p aponta. Assim podemos ter o seguinte programa:
#include <stdio.h>
main()
{
float *a, *b;
float Aux, x, y;
x = 1; /* o conteúdo de x agora é igual a 1 */
y = 2; /* o conteúdo de y agora é igual a 2 */
a = &x; /* a aponta para x */
b = &y; /* b aponta para y */
Aux = *a; * o conteúdo de Aux agora é 1 (conteúdo de x) */
*a = *b; /* o conteúdo de x agora é 2 (conteúdo de y) */
*b = Aux; * o conteúdo de y agora é 1 */
printf("x = %f e y = %f \n", x, y);
}
Temos então um programa - talvez um pouco sofisticado - para permutar os
conteúdos de duas variáveis.
Qualquer que seja o tipo de variável apontada por um ponteiro, pode-se "atribuir-
lhe" a constante 0 (zero) e pode-se comparar um ponteiro com esta constante. O sistema
oferece uma constante simbólica NULL que pode (e normalmente o é) ser utilizado no
lugar do zero para, mnemonicamente, indicar mais claramente que este é um valor
especial para um ponteiro. Como veremos daqui por diante, este valor especial de
ponteiro será utilizado para inicializações de ponteiros, para valores de escape de
estruturas de repetição e para retorno de funções quando alguma ação pretendida não é
conseguida.

5.5 Passagem de parâmetros por referência no Turbo


C 2.01
A utilização de ponteiros como parâmetros de funções permite que a passagem de
parâmetros seja feita por referência. Considere um parâmetro p do tipo ponteiro. Como p
armazenará um endereço, se for passado para p o endereço de uma variável que possa ser
apontada por ele, qualquer ação realizada no ponteiro afetará o conteúdo da variável.
O caso da função Troca(), comentada na seção anterior, poderia ser definida da
seguinte forma

91
Capítulo 5 Funções e Ponteiros

void troca(float *a, float *b)


{
float Aux;
Aux = *a;
*a = *b;
*b = Aux;
}
e suas ativações deveriam ser feitas através de Troca(&x, &y).

5.6 Uma urna eletrônica


A passagem de parâmetros por referência também é muito útil quando se pretende
que uma função retorne mais de um valor. Um destes valores pode ser retornado pelo
comando return() e os demais podem ser retornados para variáveis que foram passadas
por referência para parâmetros da função.
O exemplo abaixo, uma melhor resposta a um exercício proposto no capítulo
anterior, transforma um computador numa urna eletrônica para a eleição, em segundo
turno, para a presidência de um certo país, às quais concorrem dois candidatos Alibabá,
de número 89, e Alcapone, de número 93, sendo permitido ainda o voto em branco
(número 99) e considerando como voto nulo qualquer voto diferente dos anteriores.
A função Confirma() deve retornar dois valores: o primeiro para, no caso de
confirmação do voto, permitir sua contabilização e o segundo para, ainda no caso de
confirmação do voto, interromper a estrutura do while, o que permitirá a recepção do voto
seguinte. Observe também a passagem por referência do parâmetro da função
ComputaVoto(). Há necessidade de que seja desta forma, pelo fato de que esta função
alterará conteúdos de variáveis diferentes.
#include <stdio.h>
#include <ctype.h>
#include <dos.h>
#include <conio.h>
/*Função para confirmação do voto*/
int Confirma(char *s, char *p)
{
int r;
char Conf;
printf("Voce votou em %s! Confirma seu voto (SN)? ", s);
fflush(stdin);
scanf("%c", &Conf);
if (toupper(Conf) == 'S')
{
*p = 's';
r = 1;

92
Capítulo 5 Funções e Ponteiros

}
else
{
*p = 'n';
printf("\a Vote de novo: ");
sound(1000);
delay(80000);
nosound();
r = 0;
}
return r;
}
/*Função para computar cada voto confirmado para o candidato*/
void ComputaVoto(int *p)
{
*p = *p + 1;
}
/*Função principal*/
main()
{
int Alibaba, Alcapone, Nulos, Brancos, Eleitores, Voto;
char Sim, Cont;
clrscr();
Alibaba = Alcapone = Nulos = Brancos = 0;
do
{
do
{
printf(" 89 - Alibaba \n 93 - Alcapone \n 99 - Branco \n");
printf("Digite seu voto: ");
scanf("%d", &Voto);
switch (Voto)
{
case 89:
if (Confirma("Alibaba", &Sim) == 1)
ComputaVoto(&Alibaba);
break;
case 93:
if (Confirma("Alcapone", &Sim) == 1)
ComputaVoto(&Alcapone);
break;
case 99:
if (Confirma("Brancos", &Sim) == 1)
ComputaVoto(&Brancos);

93
Capítulo 5 Funções e Ponteiros

break;
default:
if (Confirma("Nulo", &Sim) == 1)
ComputaVoto(&Nulos);
break;
}
clrscr();
}
while (Sim != 's');
printf("Outro eleitor (S/N)? ");
fflush(stdin);
scanf("%c", &Cont);
}
while (toupper(Cont) == 'S');
Eleitores = Alibaba + Alcapone + Brancos + Nulos;
printf("Total de eleitores %d \n Alibaba %d \n Alcapone %d \n Brancos %d \n
Nulos %d", Eleitores, Alibaba, Alcapone, Brancos, Nulos);
}
O arquivo de cabeçalhos dos.h contém as funções sound(n), nosound() e delay(n).
A primeira emite um som de freqüência n hertz; a segunda interrompe a emissão de som e
a terceira suspende a execução do programa por n milisegundos.
A razão da chamada da função fflush() é a seguinte. Em alguns sistemas, quando
algum dado de entrada é digitado para execução da função scanf(), os compiladores C não
o armazenam diretamente na posição de memória respectiva, armazenando-o inicialmente
numa região chamada buffer para, ao final da execução da função de leitura, transferir o
conteúdo do buffer para a memória. Se quando da execução de uma função de leitura o
conteúdo do buffer não estiver vazio, é este conteúdo (naturalmente, indesejado) que será
armazenado na variável. A ativação de fflush(stdin) "descarrega" todo o buffer dos dados
digitados no teclado e assim a função de leitura aguardará que o dado realmente
pretendido seja digitado. É prudente, portanto, preceder leituras de caracteres e de cadeias
de caracteres pela chamada de fflush(stdin).
Observe que um ponteiro do tipo char é capaz de “armazenar” uma cadeia de
caracteres. Observe também que utilizamos inicializações sucessivas no comando
Alibaba = Alcapone = Nulos = Brancos = 0. Esta forma de inicializar variáveis também é
válida, mas não será muito utilizada neste livro.
Para concluir (por enquanto) o estudo de ponteiros, vale ressalvar que, sendo
variáveis capazes de receber endereços (portanto, valores do tipo int) pode-se somar e
subtrair ponteiros. No capítulo 7, veremos um exemplo onde isto será útil.

5.7 Recursividade
Algumas funções matemáticas podem ser estabelecidas de tal forma que as suas
definições utilizem, de modo recorrente, a própria função que se está definindo. Um

94
Capítulo 5 Funções e Ponteiros

exemplo trivial (no bom sentido) de um caso como este é a função fatorial. No ensino
médio aprendemos que o fatorial de um número natural n é o produto de todos os
números naturais de 1 até o referido n, ou seja, n! = 1 . 2 . 3 . ... . n. Como mostra o
exemplo abaixo, é muito simples se escrever uma função que calcule o fatorial de n: basta
se inicializar um variável com 1 e, numa estrutura de repetição, calcular os produtos
1 x 2 = 2, 2 x 3 = 6; 6 x 4 = 24; 24 x 5 = 120; ...; etc., até multiplicar todos os naturais até
n.
long int Fatorial(int n)
{
long int Fat;
int i;
Fat = 1;
for (i = 2; i <= n; i = i + 1)
Fat = Fat * i;
return (Fat);
}
Embora o conceito anterior seja de simples compreensão, é matematicamente mais
elegante definir o fatorial de um natural n por
1, se n = 0 ou n = 1
n!= 
n . ( n − 1)!, se n > 1
Desta forma, o fatorial de n é definido a partir dos fatoriais dos naturais menores
que ele. Isto significa que para o cálculo do fatorial de um determinado número natural há
necessidade de que se recorra aos fatoriais dos naturais anteriores. Por exemplo,
4! = 4 . 3! = 4 . (3 . 2!) = (4 . 3) . (2 . 1!) = 4 . 3 . 2 . 1 = 24. Uma definição com estas
características é dita uma definição por recorrência ou uma definição recursiva.
Um outro exemplo de uma definição recursiva foi dada no exercício 12 da seção
4.5: a seqüência de Fibbonaci é a seqüência (an) definida por
1, se n = 1 ou n = 2
Fibb(n) = 
 Fibb( n − 1) + Fibb( n − 2), se n > 2
Observe que o termo de ordem n é definido a partir de termos anteriores. Isto
significa que para o cálculo de um determinado termo há necessidade de que se recorra a
valores de todos os termos anteriores. Por exemplo, para a determinação de a5
necessitamos conhecer a4 e a3; para a determinação destes dois, necessitamos conhecer a2
e a1.
Naturalmente, uma definição recursiva deve conter uma condição que interrompa a
recorrência. Esta condição é chamada condição de escape. No caso do fatorial a condição
de escape é n = 0 ou n = 1; na seqüência de Fibbonaci, a condição de escape é n = 1 ou
n = 2. A expressão que realiza propriamente a recorrência pode ser chamada expressão de
recorrência

95
Capítulo 5 Funções e Ponteiros

O surpreendente é que os ambientes para desenvolvimento de programas, de um


modo geral, oferecem recursos para implementação de funções recursivas da mesma
maneira que elas são escritas em matemática. Por exemplo, a implementação recursiva do
fatorial em C pode ser feita simplesmente da seguinte forma:
long int FatRec(int n)
{
if ((n == 0) || (n == 1))
return (1);
else
return (n * FatRec(n - 1));
}
É interessante ter uma idéia do que acontece na recursividade. Quando se ativa uma
função recursiva, cada nova chamada da mesma é empilhada na chamada pilha de
memória, até que a condição de escape é atingida. A partir daí, cada ativação pendente é
desempilhada (evidentemente, na ordem inversa do empilhamento) e as operações vão
sendo realizadas.
Se ativarmos a função acima com n = 5 (com um comando printf("%d"%,
FatRec(5)), por exemplo) teríamos a seguinte seqüência de operações:
1 - Após a ativação de Fat(5)

Fat(5) n
5*Fat(4) 5

2 - Após a ativação de Fat(4)

Fat(5) n Fat(4) n
5*Fat(4) 5 4*Fat(3) 3

3 - Após a ativação de Fat(3)

Fat(5) n Fat(4) n Fat(3) n


5*Fat(4) 5 4*Fat(3) 3 3*Fat(2) 2

4 - Após a ativação de Fat(2)

Fat(5) n Fat(4) n Fat(3) n Fat(2) n


5*Fat(4) 5 4*Fat(3) 3 3*Fat(2) 2 2*Fat(1) 1

5 - Após a ativação de Fat(1)

Fat(5) n Fat(4) n Fat(3) n Fat(2) n


5*Fat(4) 5 4*Fat(3) 3 3*Fat(2) 2 2*1 = 2 1

96
Capítulo 5 Funções e Ponteiros

Fat(5) n Fat(4) n Fat(3) n


5*Fat(4) 5 4*Fat(3) 3 3*2 = 6 2

Fat(5) n Fat(4) n
5*Fat(4) 5 4*6 = 24 3

Fat(5) n
5*24 = 120 5
Embora a utilização da recursividade apresente a vantagem de programas mais
simples, ela tem o inconveniente de sacrificar a eficiência do programa. Isto ocorre
devido à necessidade de chamadas sucessivas da função e das operações de
empilhamento e desempilhamento, o que demanda um tempo maior de computação e uma
maior necessidade de uso de memória. Esta observação faz com que a solução não
recursiva (chamada solução iterativa) seja preferível. No capítulo 7 apresentaremos um
exemplo de uma função recursiva que é tão eficiente quanto a função iterativa.
Um outro exemplo interessante de recursividade é a implementação do jogo
conhecido como Torre de Hanói, jogo que consiste em três torres chamadas origem,
destino e auxiliar e um conjunto de n discos de diâmetros diferentes, colocados na torre
origem, na ordem decrescente dos seus diâmetros. O objetivo do jogo é, movendo um
único disco de cada vez e não podendo colocar um disco sobre outro de diâmetro menor,
transportar todos os discos para pilha destino, podendo usar a torre auxiliar como
passagem intermediária dos discos.
Indicando torre 1 → torre 2 o movimento do disco que no momento está parte
superior da torre 1 para a torre 2, teríamos a seguinte solução para o caso n = 2:
1. origem → auxiliar
2. origem → destino
3. auxiliar → destino
Para n = 3, a solução seria:
1. origem → destino
2. origem → auxiliar
3. destino → auxiliar
4. origem → destino
5. auxiliar → origem
6. auxiliar → destino
7. origem → destino
Observe que os três movimentos iniciais transferem dois discos da torre origem
para a torre auxiliar, utilizando a torre destino como auxiliar; o quarto movimento
transfere o maior dos discos da origem para destino e os últimos movimentos transferem
os dois discos que estão na auxiliar para destino utilizando origem como torre auxiliar.
Assim, a operação Move(3, origem, auxiliar, destino) - move três discos da origem
para destino usando auxiliar como torre auxiliar - pode ser decomposta em três etapas:
97
Capítulo 5 Funções e Ponteiros

1) Move(2, origem, destino, auxiliar) - move dois discos de origem para auxiliar
usando destino como auxiliar;
2) Move um disco de origem para destino
3) Move(2, auxiliar, origem, destino) - move dois discos de auxiliar para destino
usando origem como auxiliar.
O interessante é que é fácil mostrar que este raciocínio se generaliza para n discos,
de modo que a operação Move(n, a, b, c) pode ser obtida com as seguintes operações:
1) Move(n-1, a, c, b)
2) Move um disco de a para c
3) Move(n-1, b, a, c)
O mais interessante ainda é que isto pode ser implementado em C, através do
seguinte programa:
/* Programa que implementa o jogo Torre de Hanoi*/
#include <stdio.h>
void MoveDisco(char t1[10], char t2[10])
{
printf("%s --> %s \n", t1, t2);
}

void Hanoi(int x, char o[10], char a[10], char d[10])


{
if (x > 0)
{
Hanoi(x - 1, o, d, a);
MoveDisco(o, d);
Hanoi(x - 1, a, o, d);
}
}

main()
{
int n;
printf("Digite o numero de discos \n");
scanf("%d", &n);
Hanoi(n, "origem", "auxiliar", "destino");
}

5.8 Usando funções de outros arquivos


98
Capítulo 5 Funções e Ponteiros

Os compiladores C permitem que um programa utilize funções definidas em outros


programas. Basta que o referido programa seja incluído na instrução #include
"NomeArquivo". Por exemplo, imagine que a declaração de variáveis e a função abaixo
#include <stdio.h>
int x, y;
int MaxDivCom(int a, int b)
{
int Resto;
Resto = a % b;
while (Resto != 0)
{
a = b;
b = Resto;
Resto = a % b;
}
return b;
}
estejam num arquivo mdc.c. Como a Matemática prova que o produto de dois números
inteiros é igual ao produto do máximo divisor comum dos números pelo mínimo múltiplo
comum, a função MaxDivCom() poderia ser utilizada para se escrever um programa para
o cálculo do mínimo múltiplo comum de dois números dados. Este programa poderia
utilizar as variáveis x, y e Mdc declaradas no arquivo mdc.c. Teríamos então o seguinte
programa:
/*Programa que calcula o minimo multiplo comum de dois numeros utilizando uma
função definida em outro arquivo*/
#include <stdio.h>
#include "mdc.c"
main()
{
int m, Mmc;
printf("Digite os numeros ");
scanf("%d %d", &x, &y);
m = MaxDivCom(x, y);
Mmc = (x * y)/m;
printf("Mmc(%d, %d) = %d \n", x, y, Mmc);
}
A referência ao arquivo que vai ser “incluído” no programa pode ser feita com o
caminho do arquivo escrito entre aspas ou com o nome do arquivo entre < >, se ele está
gravado na pasta padrão dos arquivos de cabeçalho .h.

99
Capítulo 5 Funções e Ponteiros

5.9 "Tipos" de variáveis


Variáveis locais
Como foi dito na seção 5.1, as variáveis declaradas no interior de uma função
(variáveis locais, para lembrar) só são acessáveis por instruções desta função. Na
realidade, elas só existem durante a execução da função: são "criadas" quando a função é
ativada e são "destruídas" quando termina a execução da função. Por esta última razão,
variáveis locais também são chamadas variáveis automáticas e variáveis dinâmicas.
Também são variáveis locais os parâmetros da função. Isto explica a necessidade de
declará-los, definindo-se seus identificadores e seus tipos de dados.

Variáveis globais e o modificador extern


Se uma variável deve ser acessada por mais de uma função ela deve ser declarada
fora de qualquer função, sendo chamada, neste caso, de variável global. Uma variável
global pode ser referenciada em qualquer função do programa e, embora isto não seja
aconselhável, pode-se identificar uma variável local com o mesmo identificador de uma
variável global. Neste caso, referências ao identificador comum dentro da função na qual
a variável local foi definida referem-se a esta variável local. Isto, naturalmente, impede a
função de acessar a variável global.
O modificador de variável extern permite que um programa utilize variáveis
definidas e inicializadas em funções de um outro arquivo que tenha sido "incluído"
através da instrução #include "NomeArquivo", como visto na seção 5.8. No capítulo
seguinte apresentaremos um exemplo bastante esclarecedor do uso do modificador extern.

Variáveis estáticas
Como uma variável local deixa de existir quando se encerra a execução da função,
o último valor nela armazenado é perdido. Pode ocorrer que o último valor armazenado
numa variável local seja necessário para uma chamada subseqüente da função. Após a
execução de uma função, o último valor armazenado numa variável local pode ser
preservado para ser utilizado numa chamada posterior da função através do modificador
de variável static. No Turbo C 2.01 e no Turbo C++ 3.0, uma variável local static deve
ser inicializada por um valor constante ou o endereço de uma variável global, quando da
sua declaração. Neste caso, considerando que receberá endereços, uma variável static
deve ser definida como um ponteiro.
Por exemplo, o programa abaixo utiliza uma variável estática para guardar o valor
de Termo em cada ativação da função GeraPA() para, com este valor, obter o valor do
termo seguinte.
#include <stdio.h>
#include <conio.h>

100
Capítulo 5 Funções e Ponteiros

int a1;
int GeraPA(int r)
{
static int *Termo = &a1;
*Termo = *Termo + r;
return (*Termo);
}

main()
{
int i, Razao;
clrscr();
printf("Digite o primeiro termo e a razÆo: ");
scanf("%d %d", &a1, &Razao);
printf("Progressao Aritmetica de primeiro termo %d e razao %d: \n%d ", a1,
Razao, a1);
for (i = 1; i <= 9; i = i + 1)
printf("%d ", GeraPA(Razao));
}
Naturalmente, concordo com o leitor que está pensando que existem programas
para geração de progressões aritméticas bem mais simples.
Uma outra aplicação de variáveis estáticas pode ser obtida numa função que
determine a decomposição em fatores primos de um inteiro dado, exercício proposto no
capítulo anterior. Um algoritmo para determinar a decomposição em fatores primos de
um inteiro é efetuar divisões sucessivas pelos primos divisores, sendo o número de
divisões pelo primo realizadas a sua multiplicidade. Por exemplo, para se obter a
decomposição de 1.400 teríamos
1.400 2
700 2
350 2
175 5
35 5
7 7
1 1400 = 23x52x7
As funções abaixo, a primeira iterativa e a segunda recursiva, retornam a
decomposição de um inteiro passado para o parâmetro n.
void DecompFatores(int n)
{
int d, m;
d = 2;
while (n > 1)
{

101
Capítulo 5 Funções e Ponteiros

m = 0;
while (n % d == 0)
{
m++;
n = n / d;
}
if (m > 0)
printf("\t%d\t%d\n", d, m);
d++;
}
}
void DecompFatoresRec(int n)
{
static int d = 2;
int m = 0;
if (n > 1)
{
while (n % d == 0)
{
m++;
n = n / d;
}
if (m > 0)
printf("\t%d\t%d\n", d, m);
d++;
DecompFatoresRec(n);
}
}
Observe que, como d foi definida como estática, o seu valor da ultima execução da
função, obtido através do comando d++, é preservado permitindo que um novo divisor
primo seja procurado.
O valor de uma variável estática é preservado para uma próxima execução da
função pelo fato de que, na verdade, ela não é destruída quando se encerra a execução da
função, como se ela fosse uma variável global. A diferença entre variável local estática e
variável global é que a primeira continua só podendo ser acessada pela função onde ela
foi definida.

5.10 Uma aplicação à História da Matemática


Em 1742, Christian Goldbach, um professor de Matemática e de História nascido
na Alemanha, numa carta que escreveu ao matemático Leonard Euler, fez a seguinte
afirmação: todo número par maior que dois é a soma de dois números primos. Embora a

102
Capítulo 5 Funções e Ponteiros

veracidade desta afirmação, conhecida hoje como Conjectura de Goldbach, já tenha sido
verificada para todos os números pares menores do que 4x1014, ainda não se conseguiu
prová-la integralmente, apesar dos esforços já despendidos por muitos matemáticos.
O programa abaixo, permite a verificação da Conjectura de GoldBach para
qualquer número par menor que 32.767.
#include <math.h>
int Primo(int n)
{
int d = 2;
float r = sqrt(n);
while (n%d != 0 && d <= r)
d++;
if (d <= r)
return 0;
else
return 1;
}

void Goldbach(int n, int *a, int *b)


{
int i = 2;
while (!Primo(i) || !Primo(n - i))
i++;
*a = i;
*b = n - i;
}

main()
{
int x, y, z;
printf("Digite um inteiro par ");
scanf("%d", &x);
Goldbach(x, &y, &z);
printf("Primos cuja soma é igual a %d: %d e %d \n", x, y, z);
}

5.11 Exercícios propostos


1. Escreva uma função que retorne o k-ésimo dígito (da direita para esquerda) de
um inteiro n, k e n dados. Por exemplo, K_esimoDigito(2845, 3) = 8.
2. O fatorial ímpar de um número n ímpar positivo é o produto de todos os
números ímpares positivos menores do que ou iguais a n. Indicando o fatorial ímpar de n

103
Capítulo 5 Funções e Ponteiros

por n| temos, n| = 1 . 3. 5 . ... . n. Por exemplo, 7| = 1 . 3 . 5 . 7 = 105. Escreva funções


iterativas e recursivas para a determinação do fatorial ímpar de um inteiro ímpar dado.
3. Como na questão anterior, o fatorial primo (ou primorial) de um número primo
positivo é o produto de todos os primos positivos menores do que ou iguais a ele,
p# = 2 . 3 . 5 . 7 . ... .p. Por exemplo, 7# = 2 . 3 . 5 . 7 = 210. Escreva um programa que
determine o fatorial primo de um primo dado. O fatorial primo é utilizado numa
demonstração de que a quantidade de números primos é infinita. Esta demonstração foi
concebida pelo matemático grego Euclides de Alexandria (360 a.C. — 295 a.C.).
4. Escreva uma função que retorne a soma dos algarismos de um inteiro positivo
dado.
5. Escreva uma função recursiva que retorne o n-ésimo termo da seqüência de
Fibbonaci, n dado.
6. Escreva uma função recursiva que gere uma tabuada de multiplicação, exibindo-
a no formato (para posicionar a saída pode-se utilizar a função gotoxy()).
1x2 = 2 1x3 = 3 1x4 = 4 ...
2x2 = 4 2x3 = 6 2x4 = 8 ...
3x2 = 6 3x3 = 9 3x4 = 12 ...
... ... ... ...
9x2 = 18 9x3 = 27 9x4 = 36 ...

104
6 Vetores
6.1 O que são vetores
No exemplo 6 da seção 4.6 discutimos uma função para a determinação da média
de uma relação de números dados. Para tal, utilizamos uma variável simples para receber
os números, sendo que cada vez que um número, a partir do segundo, era recebido o
anterior era "perdido". Ou seja, a relação de números não era armazenada. Imagine que a
relação fosse uma relação de notas escolares e além da média se quisesse também saber a
quantidade de alunos que obtiveram nota acima da média ou uma outra medida estatística
(desvio médio, por exemplo) que dependesse da média. Neste caso, haveria a necessidade
de que a relação fosse redigitada o que, além da duplicidade do trabalho, facilitaria os
erros de digitação. É importante então que exista uma "variável" capaz de armazenar
vários valores simultaneamente de tal forma que se possa acessar cada um deles
independentemente de se acessar os demais.
Um outro exemplo é o caso do exemplo 2 da seção 4.6. Lá queríamos a relação dos
divisores de um inteiro dado e estes divisores eram apenas exibidos, não sendo
armazenados, como recomendado na seção 2.9. Até aquele momento, a dificuldade de se
armazenar os divisores residia no fato de que não se sabe a priori o número de divisores
de um inteiro dado e, portanto, não saberíamos quantas variáveis deveríamos declarar.
Um vetor é um conjunto de variáveis de um mesmo tipo de dado as quais são
acessadas e referenciadas através da aposição de índices ao identificador do vetor.

6.2 Declaração de um vetor unidimensional


Um vetor unidimensional (ou simplesmente vetor) é declarado através da seguinte
sintaxe:
Tipo de dado Identificador[n];
onde Tipo de dado fixará o tipo de dado das variáveis componentes do vetor e n indicará
o número das tais componentes.
Por exemplo, a declaração
int Vetor[10]
definirá um conjunto de dez variáveis do tipo int, enquanto que a declaração
char Cadeia[100]
definirá um conjunto de cem variáveis do tipo char. Como cada variável do tipo int
utiliza dois bytes de memória e cada variável do tipo char utiliza apenas um byte, a
variável Vetor ocupará vinte bytes de memória enquanto que a variável Cadeia ocupará
cem bytes.
Capítulo 6 Vetores

Os compiladores C possuem uma função de biblioteca, sizeof(), que retorna o


número de bytes ocupado por uma variável ou por um vetor. Por exemplo, o programa
#include <stdio.h>
main()
{
float x;
int v[30];
printf("Numero de bytes de x = %d \nNumero de bytes de v = %d \n", sizeof(x),
sizeof(v));
}
exibirá na tela a seguinte saída:
Numero de bytes de x = 4
Numero de bytes de v = 60
Cada componente de um vetor pode ser acessada e referenciada através de índices
associados ao identificador do vetor, sendo o índice da primeira componente igual a zero.
Assim, as componentes do vetor Cadeia do exemplo acima serão identificadas por
Cadeia[0], Cadeia[1], ..., Cadeia[99].
O índice de uma componente pode ser referido através de uma expressão que
resulte num valor inteiro. Por exemplo, a seqüência de comandos
int i, Quadrados[100];
for (i = 1; i <= 100; i = i + 1)
Quadrados[i - 1] = i * i;
armazena no vetor Quadrados os quadrados dos cem primeiros inteiros positivos.
Uma coisa com que o programador em C deve se preocupar é o fato de que os
compiladores C não verificam se os valores atribuídos a um índice estão dentro dos
limites definidos na declaração do vetor. Se os limites não forem obedecidos, podem
ocorrer erros de lógica na execução programa ou conflitos com o sistema operacional
(provocando, até mesmo, travamento no sistema).

6.3 Vetores e ponteiros


É muito importante observar que o identificador de um vetor em C é, na verdade,
um ponteiro que aponta para o primeiro elemento do vetor. Quando se declara
int v[10];
está se reservando um conjunto de dez posições de memória contíguas, cada uma delas
com dois bytes, como mostra a figura abaixo:
v

v[0] v[1] v[9]

106
Capítulo 6 Vetores

A partir daí, qualquer referência ao identificador v é uma referência ao endereço da


componente v[0], de tal forma que, se tivermos a declaração
int *p;
os comandos
p = &v[0]; p = v;
executam a mesma ação: armazenam no ponteiro p o endereço de v[0].
Sendo um ponteiro que aponta para sua primeira componente, um vetor pode ser
um parâmetro de uma função, podendo receber, portanto, endereços. Qualquer ação
realizada no vetor, portanto, afetará o conteúdo do vetor passado como argumento; ou
seja, a passagem de parâmetros do tipo vetor é sempre feita por referência. Esta
observação também justifica a possibilidade de que parâmetros do tipo vetor sejam
declarados como um ponteiro: void funcao(int *v).

6.4 Lendo e escrevendo um vetor


Como foi dito na seção 6.1, vetores servem para armazenar uma relação de dados
do mesmo tipo. Uma função para fazer este armazenamento depende do conhecimento ou
não da quantidade de elementos da relação. Na hipótese do número de elementos da
relação ser conhecido, basta usar uma função, do tipo void, com dois parâmetros: um para
receber o vetor que vai armazenar a relação e outro para receber a quantidade de
elementos da relação. Dentro da função, pode-se utilizar um comando for.
#include <stdio.h>
void ArmazenaRelacaoN(int *v, int t)
{
int i;
printf("Digite os elementos da relacao \n");
for (i = 0; i < t; i++)
scanf("%d", &v[i]);
}
Se o número de elementos da relação não é conhecido a priori, deve-se utilizar um
flag para encerrar a entrada dos dados, de acordo com o que foi comentado na seção 4.6.
É importante que a função, para utilizações posteriores, determine o número de elementos
da relação. Este valor pode ser retornado através do comando return(). Dentro da função
pode-se utilizar um comando while que deverá ser executado enquanto o dado de entrada
for diferente do flag escolhido.
int ArmazenaRelacao(int *v)
{
int i;
i = 0;
printf("Digite os elementos da relacao (-1 para encerrar)");

107
Capítulo 6 Vetores

scanf("%d", &v[i]);
while (v[i] != -1)
{
i = i + 1;
scanf("%d", &v[i]);
}
return (i);
}
Observe a dupla finalidade da variável i: ela serve para indexar as componentes e
para determinar a quantidade de elementos da relação.
Para se exibir uma relação de dados armazenados num vetor, basta uma função
com dois parâmetros: um para receber o vetor onde a relação está armazenada e o outro
para receber a quantidade de elementos da relação. Esta quantidade está armazenada em
alguma variável: ela foi um dado de entrada ou foi determinada na ocasião do
armazenamento da relação.
void ExibeRelacao(int *v, int t)
{
int i;
for (i = 0; i < t; i++)
printf("%d ", v[i]);
printf("\n");
}

6.5 Exemplos Parte IV


Os exemplos a seguir, além de reforçar vários aspectos da utilização de vetores, são
muito úteis no desenvolvimento da lógica de programação.
1. Para introduzir o estudo dos vetores nos referimos, na seção 6.1, ao problema de
se determinar o número de alunos de uma turma que obtiveram notas maiores que a
média. Com a utilização de vetores, podemos calcular a tal média e depois "percorrer"
novamente o vetor, comparando cada nota com a referida média. Teríamos então o
seguinte programa:
#include <stdio.h>
int ArmazenaNotas(float *v)
{
int i;
i = 0;
printf("Digite as notas (-1 para encerrar)");
scanf("%f", &v[i]);
while (v[i] != -1)
{
i = i + 1;

108
Capítulo 6 Vetores

scanf("%f", &v[i]);
}
return (i);
}
/*Função para determinar a média de uma relação de números*/
float Media(float *v, int t)
{
int i;
float Soma;
Soma = 0;
for (i = 0; i < t; i++)
Soma = Soma + v[i];
return (Soma/t);
}
main()
{
int i, Quant, NotasBoas;
float *p, Med;
Quant = ArmazenaNotas(p);
Med = Media(p, Quant);
NotasBoas = 0;
for (i = 0; i < Quant; i = i + 1)
if (p[i] > Med)
NotasBoas = NotasBoas + 1;
printf("Media das notas: %f \n", Med);
printf("Notas maiores que a media: %d", NotasBoas);
}
2. Imagine que quiséssemos o desvio médio das notas do exemplo anterior. Esta
medida estatística é assim definida: o desvio de um elemento em relação à média
aritmética é a diferença entre o elemento e a média aritmética da relação; o desvio médio
é a média aritmética dos valores absolutos dos desvios.
Podemos então, como fizemos acima, escrever uma função para calcular a média
da relação de notas, armazenar os valores absolutos dos desvios em um vetor e utilizar a
função que calcula a média para calcular a média destes valores absolutos.
float DesvioMedio(float *v, int t)
{
int i;
float *d;
float Med;
Med = Media(v, t);
for (i = 0; i < t; i++)
d[i] = abs(v[i] - Med);
return(Media(d, t));

109
Capítulo 6 Vetores

}
Observe que este exemplo ilustra outra vantagem do uso de funções: a mesma
função Media foi utilizada para determinação de médias de relações diferentes.
3. Imaginemos agora que queiramos uma função que retorne o maior valor de uma
relação armazenada em um vetor. Uma possível solução é supor que o maior valor
procurado é o primeiro elemento do vetor e, em seguida, percorrer o vetor comparando
cada componente com o valor que, até o momento, é o maior, substituindo o valor deste
maior elemento quando se encontra uma componente maior que ele.
float MaiorElemento(float *v, int t)
{
int i;
float Maior;
Maior = v[0];
for (i = 1; i < t; i = i + 1)
if (v[i] > Maior)
Maior = v[i];
return(Maior);
}
Observe que a função acima retorna o maior elemento armazenado no vetor, mas
não retorna a posição deste maior elemento, o que em muitas situações é indispensável.
Para isto é necessário um parâmetro com passagem por referência, como visto no capítulo
anterior. Este parâmetro recebe o valor zero e em seguida o valor da posição onde foi
encontrada uma componente maior do que Maior.
float MaiorElemento(float *v, int t, int *pos)
{
int i;
float Maior;
Maior = v[0];
*pos = 0;
for (i = 1; i < t; i = i + 1)
if (v[i] > Maior)
{
Maior = v[i];
*pos = i;
}
return(Maior);
}
Uma chamada desta função vai requerer, além do vetor p onde está armazenada a
relação, duas variáveis, digamos Maior e Pos; nestas condições a ativação da função será
feita através do comando
Maior = MaiorElemento(p, Quant, &Pos);

110
Capítulo 6 Vetores

onde p é o vetor onde está armazenada a relação, Quant é a quantidade de elementos da


relação e &Pos é o endereço da variável que irá armazenar a posição da ocorrência do
maior elemento procurado.
Vale ressaltar que, na função acima, adotamos a passagem por referência "formato
Turbo C 2.01", que também funciona no Turbo C++ 3.0.
4. O exemplo a seguir tem o objetivo de mostrar que o índice de acesso às
componentes de um vetor pode ser dado através de expressões, como já foi dito
anteriormente. O exemplo mostra uma função que, recebendo dois vetores com a mesma
quantidade de elementos, gera um vetor intercalando as componentes dos vetores dados.
Assim se v1 = {4, 8, 1, 9} e v2 = {2, 5, 7, 3} a função deve gerar o vetor v = {4, 2, 8, 5, 1,
7, 9, 3}. Observe que as componentes ímpares de v são os elementos de v1 e as
componentes pares são os elementos de v2.
void IntercalaVetor(float *v1, float *v2, float *v, int t)
{
int i;
for (i = 0; i < 2 * t; i = i + 1)
if (i % 2 == 1)
v[i] = v2[(i - 1)/2];
else
v[i] = v1[i/2];
}
5. Agora apresentaremos um exemplo que mostra um vetor cujas componentes são
cadeias de caracteres, além de mostrar como um vetor de caracteres pode ser inicializado.
Trata-se de uma função que retorne o nome do mês correspondente a um número dado.
char *NomeMes(int n)
{
char *Nome[13] = {"Mes ilegal", "Janeiro", "Fevereiro", "Marco", "Abril",
"Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro",
"Dezembro"};
if ((n > 0) && (n < 13))
return(Nome[n]);
else
return(Nome[0]);
}
Observe que a inicialização do vetor se faz quando da sua declaração, com a
enumeração dos valores das componentes entre chaves, como a matemática faz com
conjuntos. Observe também que a função NomeMes() retorna um ponteiro para uma
variável do tipo char, o que significa que retornará uma cadeia de caracteres (uma string),
pois, como veremos no próximo capítulo, uma string é um vetor de caracteres. Este fato
também é indicado no vetor Nome que é um vetor de ponteiros para variáveis do tipo
char.

111
Capítulo 6 Vetores

6. Este exemplo mostra uma situação em que há a necessidade de vários vetores.


Trata-se de um programa para administrar os pedidos de uma lanchonete, cujo cardápio é
o seguinte:
Codigo Produto Preço
101 Refrigerante 1.20
102 Suco 1.00
103 Sanduíche 2.50
104 Salgado 1.00
105 Torta 2.00
Uma possível solução é considerar três vetores globais, inicializados como no
exemplo anterior, de tal forma que seja possível para cada pedido gerar vetores com os
códigos dos produtos solicitados e, a partir daí, se possa armazenar as quantidades de
cada item do pedido.
#include <stdio.h>
#include <conio.h>
int Cod[5] = {101, 102, 103, 104, 105};
char *Prod[5] = {"Refrigerante", "Suco", "Sanduiche", "Salgado", "Torta"};
float Precos[5] = {1.20, 1.00, 2.50, 1.00, 2.00};
int Pedido(int *c, int *q, float *v)
{
int i = 0;
do
{
do
{
puts("Código (100 para encerrar o pedido):");
scanf("%d", &c[i]);
if (c[i] < 100 || c[i] > 105)
printf("\a C¢digo inv lido");
}
while (c[i] < 100 || c[i] > 105);
if (c[i] != 100)
{
puts("Quantidade");
scanf("%d", &q[i]);
v[i] = q[i]*Precos[c[i] - 101];
i++;
}
}
while (c[i] != 100);
return i;
}

112
Capítulo 6 Vetores

void ExibePedido(int *c, int *q, float *v, int t)


{
int i;
float Total = 0.0;
clrscr();
puts("Código Discriminação Quantidade Valor \n");
for (i = 0; i < t; i++)
{
printf("%d %s %10.d %10.2f\n", Cod[c[i] - 101], Prod[c[i] - 101], q[i],
v[i]);
Total = Total + v[i];
}
printf("\nValor total do pedido: %.2f\n\n", Total);
}

main()
{
int NumItens, *Itens, *Quant;
float *Valor;
char S = 'n';
do
{
clrscr();
NumItens = Pedido(Itens, Quant, Valor);
if (NumItens != 0)
ExibePedido(Itens, Quant, Valor, NumItens);
puts("Outro pedido (S/N)?");
fflush(stdin);
scanf("%c", &S);
}
while (toupper(S) != 'N');
}

6.6 Vetores multidimensionais


Na seção 6.1 foi dito que um vetor é um conjunto de variáveis de mesmo tipo,
chamadas componentes do vetor. A linguagem C permite que as componentes de um
vetor sejam também vetores, admitindo que se armazene uma matriz da matemática, uma
tabela de dupla entrada que, por exemplo, enumere as distâncias entre as capitais
brasileiras ou um livro considerando a página, a linha e a coluna em que cada caractere se
localiza.
A declaração de um vetor multidimensional é uma extensão natural da declaração
de um vetor unidimensional:

113
Capítulo 6 Vetores

Tipo de dado Identificador[n1][n2] ... [nk];


onde k indica a dimensão e n1, n2, ..., nk indicam o número de componentes em cada
dimensão.
Por exemplo, a declaração
int Mat[10][8];
define um vetor de dez componentes, cada uma delas sendo um vetor de oito
componentes. Ou seja, Mat é um conjunto de 10 x 8 = 80 variáveis do tipo int.
Para um outro exemplo, a declaração
char Livro[72][30][30];
define uma variável capaz de armazenar os caracteres de um livro com até setenta duas
páginas, cada página possuindo trinta linhas e cada linha possuindo trinta colunas.
A inicialização de um vetor multidimensional também segue o padrão da
inicialização de um vetor unidimensional, com a ressalva de que as componentes, que
agora são vetores, devem estar entre chaves. Por exemplo, se quiséssemos um vetor
bidimensional para armazenar os números de dias dos meses do ano, fazendo a distinção
entre anos bissextos e não bissextos poderíamos declarar e inicializar um vetor
DiasMeses da seguinte forma:
int DiasMeses = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31,
30, 31, 30, 31, 31, 30, 31, 30, 31}};
onde a primeira componente refere-se aos dias dos meses de um ano não bissexto e a
segundo, aos dias dos meses de um ano bissexto (este vetor será utilizado num exemplo a
seguir e, na ocasião, será explicada a razão da primeira componente de cada vetor
componente ser zero).
Um vetor bidimensional é usualmente chamado de matriz e os números de
componentes são chamados, respectivamente, número de linhas e número de colunas.
Estes dois números separados por x (que é lido por) é a ordem da matriz. Assim, a
variável Mat do exemplo acima está apta a armazenar até uma matriz de ordem 8 x 10.
Estas denominações, emprestadas da matemática, são justificadas pelo fato de que,
embora as componentes de um vetor bidimensional sejam armazenadas de forma
consecutiva (a primeira componente do segundo vetor logo após a última componente do
primeiro vetor), uma matriz, para facilitar sua compreensão, pode ser imaginada como
constituída de linhas e de colunas. Por exemplo, o vetor DiasMeses do exemplo acima
pode ser imaginado como sendo
0 31 28 31 30 31 30 31 31 30 31 30 31
0 31 29 31 30 31 30 31 31 30 31 30 31
facilitando a compreensão de referências do tipo DiasMeses[1][2] que indica o elemento
da segunda linha (a primeira é de índice zero) e da terceira coluna (a primeira é de índice
zero).

114
Capítulo 6 Vetores

Se o número de linhas e o número de colunas de uma tabela são conhecidos, o seu


armazenamento em uma matriz é muito simples. Basta utilizar dois comandos for
aninhados, controlados pelo número de linhas e pelo número de colunas, respectivamente.
void ArmazenaTabelaMN(float Mat[10][10], int m, int n)
{
int i, j;
printf("Digite, por linha, os elementos da matriz");
for (i = 0; i < m; i = i + 1)
for (j = 0; j < n; j = j + 1)
scanf("%f", &Mat[i][j]);
}
Se o número de linhas e o número de colunas de uma tabela não são conhecidos,
pode-se usar um duplo while aninhado, definindo-se um flag para encerramento da
digitação dos elementos de cada linha e um outro flag para encerramento da digitação da
matriz. Naturalmente, a função deverá retornar o número de linhas e o número de colunas
da tabela, o que justifica os ponteiros m e n da proposta abaixo.
void ArmazenaTabela(float Mat[10][10], int *m, int *n)
{
int i, j;
printf("Digite, por linha, os elementos da matriz (-1 para encerrar cada linha e -2
para encerrar a matriz");
i = 0;
j = 0;
scanf("%f", &Mat[i][j]);
while (Mat[i][j] != -2)
{
while (Mat[i][j] != -1)
{
j = j + 1;
scanf("%f", &Mat[i][j]);
}
*n = j;
i = i + 1;
j = 0;
scanf("%f", &Mat[i][j]);
}
*m = i;
}
Uma função para exibir uma tabela armazenada numa matriz também é muito
simples. Basta, para que a matriz seja exibida na forma de tabela, mudar a linha cada vez
que a exibição de uma linha é concluída.
void ExibeTabela(float Mat[10][10], int m, int n)

115
Capítulo 6 Vetores

{
int i, j;
for (i = 0; i < m; i = i + 1)
{
for (j = 0; j < n; j = j + 1)
printf("%.1f ", Mat[i][j]);
printf("\n");
}
}

6.7 Exemplos Parte V


1. Um dos exemplos do capítulo 3 apresentava uma função que recebia uma data e
fornecia o dia da semana correspondente. Neste programa precisamos calcular o número
de dias do ano decorridos até aquela data. Com a utilização da matriz DiasMeses
comentada acima, podemos escrever facilmente uma função com este objetivo.
int DiaAno(int d, int m, int a)
{
int DiasMes[2][13] = {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
int i;
if (((a % 4 == 0) && (a % 100 != 0)) || (a % 400 == 0))
for (i = 1; i < m; i = i + 1)
d = d + DiasMes[1][i];
else
for (i = 1; i < m; i = i + 1)
d = d + DiasMes[0][i];
return(d);
}
Lembrando que o valor de uma expressão lógica é 1 (um) se ela for verdadeira e 0
(zero) se ela for falsa, poderíamos armazenar o valor da expressão lógica que garante que
um ano é bissexto e utilizar o conteúdo desta variável para escolher a componente do
vetor DiasMeses que será utilizada: 1 (um) se for bissexto e (0) zero, caso contrário.
int DiaAno(int d, int m, int a)
{
int i,Biss;
Biss = ((a % 4 == 0) && (a % 100 != 0)) || (a % 400 == 0);
for (i = 1; i < m; i = i + 1)
d = d + DiasMes[Biss][i];
return(d);
}

116
Capítulo 6 Vetores

A razão de termos considerado a primeira componente igual a 0 (zero) foi para


compatibilizar o número correspondente a cada mês com a componente do vetor, já que
(repetindo pela última vez) a primeira componente de um vetor é de índice zero.
2. Como no exemplo em que o próprio sistema gerou os quadrados dos cem
primeiros números inteiros, o sistema pode gerar uma matriz. Para exemplificar isto,
apresentaremos uma função que gera a matriz identidade de ordem n. Para um inteiro
positivo dado, a matriz identidade de ordem n é a matriz In = (irs), de ordem nxn, dada por
irs = 1, se r = s, e irs = 0, se r ≠ s. Esta matriz é muito importante no estudo das matrizes,
sendo utilizada, por exemplo, para a determinação da matriz inversa de uma matriz
inversível. Por exemplo, se n = 3, temos
 1 0 0
 
I 3 =  0 1 0
 
 0 0 1
void GeraMatrizUnidade(int Mat[10][10], int m)
{
int i, j;
for (i = 0; i < m; i = i + 1)
for (j = 0; j < m; j = j + 1)
if (i == j)
Mat[i][j] = 1;
else
Mat[i][j] = 0;
}
3. Quando o número de linhas de uma matriz é igual ao número de colunas a matriz
é dita matriz quadrada. Neste caso, os elementos de índices iguais constituem a diagonal
principal. A soma dos elementos da diagonal principal de uma matriz quadrada é o traço
da matriz. Como mais um exemplo de programas que manipulam matrizes, a função
abaixo determina o traço de uma matriz quadrada dada. Observe que para percorrer a
diagonal principal não há necessidade de um duplo for.
float Traco(float Mat[10][10], int m, int n)
{
int i;
float Tr;
Tr = 0;
if (m == n)
{
for (i = 0; i < m; i = i + 1)
Tr = Tr + Mat[i][i];
return(Tr);
}
else

117
Capítulo 6 Vetores

printf("A matriz nao e quadrada");


}
4. Uma tabela que enumere as distâncias entre várias cidades é uma matriz
simétrica: os termos simétricos em relação à diagonal principal são iguais, ou seja
Mat[i][j] = Mat[j][i]. Obviamente, a digitação de uma matriz com esta propriedade pode
ser simplificada, devendo-se digitar apenas os termos que estão acima da diagonal
principal.
void ArmazenaMatrizSimetrica(float Mat[10][10], int m)
{
int i, j;
printf("Digite, por linha, os elementos da matriz, a partir da diagonal");
for (i = 0; i < m; i = i + 1)
for (j = i; j < m; j = j + 1)
{
scanf("%f", &Mat[i][j]);
Mat[j][i] = Mat[i][j];
}
}
Observe que a inicialização de j no segundo comando for foi com o valor de cada i
do primeiro. A razão disto é que só serão digitados os termos acima da diagonal
principal, termos em que j ≥ i.
5. Nos exemplos anteriores, sempre "percorremos a matriz pelos elementos de suas
linhas". O próximo exemplo mostra um caso em que é necessário percorrer as colunas.
Trata-se de uma questão muito comum da totalização das colunas de uma tabela.
void TotalizaColunas(float Mat[10][10], int m, int n)
{
int i, j;
for (j = 0; j < n; j = j + 1)
{
Mat[m][j] = 0;
for (i = 0; i < m; i = i + 1)
Mat[m][j] = Mat[m][j] + Mat[i][j];
}
}

6.8 Uma aplicação esportiva


Nesta seção, apresentaremos um programa para administrar o placar de um set de
um jogo de vôlei de praia. De acordo com as regras em vigoravam nas Olimpíadas de
Atenas (2004), para uma equipe vencer um set de uma partida ela deveria obter um
mínimo de 21 pontos para os sets “normais” ou de 15 pontos para um set de desempate,
desde que a diferença entre sua pontuação e a do adversário fosse superior ou igual a dois.

118
Capítulo 6 Vetores

#include <stdio.h>
#include <conio.h>
#include <math.h>
void MostraPlacar(char *time1, char *time2, int pontos1, int pontos2)
{
printf("%20s %2d x %2d %-20s\n",time1,pontos1,pontos2,time2);
}

void VerificaMudanca(int pontos1, int pontos2, int mud)


{
if ( (pontos1+pontos2)%mud == 0)
{
puts("Atencao! mudanca de quadra! Digite uma tecla para continuar" );
getch();
}
}

void FimdeSet(char *time1,char*time2,int pontos1, int pontos2)


{
puts("FIM DE SET!");
if (pontos1>pontos2)
printf("%s",time1);
else
printf("%s" ,time2);
puts(" ganhou o set!");
puts("Placar final: ");
MostraPlacar(time1,time2,pontos1,pontos2);
}

void main()
{
char *Nome[2];
int Equipe1[200], Equipe2[200];
int Set, Mudanca, Saque, Ponto, Dif;
clrscr();
puts("Digite os nomes dos paises:");
gets(Nome[0]);
flushall();
gets(Nome[1]);
puts("Digite a quantidade de pontos do set (15/21):");
scanf("%d",&Set);
Mudanca = Set/3;
Equipe1[0] = 0;
Equipe2[0] = 0;

119
Capítulo 6 Vetores

Saque = 0;
clrscr();
do
{
/* Exibe o placar atual */
puts("Placar atual:");
MostraPlacar(Nome[0], Nome[1], Equipe1[Saque], Equipe2[Saque]);
if (Saque != 0)
VerificaMudanca(Equipe1[Saque], Equipe2[Saque], Mudanca);
Saque++;
puts("Digite a equipe que marcou ponto (1/2):" );
scanf("%d",&Ponto);
if (Ponto==1)
{
Equipe1[Saque] = Equipe1[Saque-1]+1;
Equipe2[Saque] = Equipe2[Saque-1];
}
else
{
Equipe1[Saque] = Equipe1[Saque-1];
Equipe2[Saque] = Equipe2[Saque-1]+1;
}
Dif = abs(Equipe1[Saque] - Equipe2[Saque]);
clrscr();
}
while(((Equipe1[Saque]<Set) && (Equipe2[Saque] < Set)) || (Dif < 2) );
FimdeSet(Nome[0],Nome[1],Equipe1[Saque],Equipe2[Saque]);
getch();
}

6.9 Exercícios propostos


1. Escreva um procedimento que exiba as componentes de um vetor na ordem
inversa daquela em que foram armazenadas.
2. Um vetor é palíndromo se ele não se altera quando as posições das componentes
são invertidas. Por exemplo, o vetor v = {1, 3, 5, 2, 2, 5, 3, 1} é palíndromo. Escreva uma
função que verifique se um vetor é palídromo.
3. Escreva uma função que receba um vetor e o decomponha em dois outros
vetores, um contendo as componentes de ordem ímpar e o outro contendo as
componentes de ordem par. Por exemplo, se o vetor dado for v = {3, 5, 6, 8, 1, 4, 2, 3, 7},
o vetor deve gerar os vetores u = {3, 6, 1, 2, 7} e w = {5, 8, 4, 3}.
4. Escreva uma função que decomponha um vetor de inteiros em dois outros
vetores, um contendo as componentes de valor ímpar e o outro contendo as componentes

120
Capítulo 6 Vetores

de valor par. Por exemplo, se o vetor dado for v = {3, 5, 6, 8, 1, 4, 2, 3, 7} a função deve
gerar os vetores u = {3, 5, 1, 3, 7} e w = {6, 8, 4, 2}.
5. Um vetor do Rn é uma n-upla de números reais v = {x1, x2, ..., xn}, sendo cada xi
chamado de componente. A norma de um vetor v = {x1, x2, ..., xn} é definida por
x12 + x22 + ... + xn2 . Escreva uma função que receba um vetor do Rn, n dado, e forneça
sua norma.
6. O produto escalar de dois vetores do Rn é a soma dos produtos das componentes
correspondentes. Isto e, se u = {x1, x2, ..., xn} e v = {y1, y2, ..., yn}, o produto escalar é
x1.y1 + x2.y2 ... + xn.yn. Escreva uma função que receba dois vetores do Rn, n dado, e
forneça o produto escalar deles.
7. A amplitude de uma relação de números reais é a diferença entre o maior e o
menor valores da relação. Por exemplo, a amplitude da relação 5, 7, 15, 2, 23 21, 3, 6 é
23 - 2 = 21. Escreva uma função que receba uma relação de números e forneça sua
amplitude.
8. O desvio padrão de uma relação de números reais é a raiz quadrada da média
aritmética dos quadrados dos desvios (ver exemplo 2, seção 6.4). Escreva uma função que
receba uma relação de números reais e forneça o seu desvio padrão.
9. Escreva uma função que forneça as componentes distintas de um vetor dado. Por
exemplo, se o vetor dado for v = {3, 2, 1, 3, 4, 1, 5, 5, 2} a função deve fornecer
v = {3, 2, 1, 4, 5}.
10. No capítulo 2 foi pedida uma função para extrair o algarismo da casa das
unidades de um inteiro dado. Aparentemente esta questão não tem interesse prático.
Vejamos um problema cuja solução depende deste problema. Algumas empresas que
realizam sorteios de prêmios entre seus clientes o fazem através dos sorteios da loteria
federal, sendo ganhador o número formado pelos algarismos das casas das unidades dos
números sorteados no cinco prêmios da referida loteria. Por exemplo, se o sorteio da
loteria federal deu como resultado os números 23451, 00234, 11236, 01235 e 23452, o
prêmio da tal empresa seria dado ao cliente que possuísse o bilhete de número 14652.
Escreva uma função que receba os números sorteados pela loteria federal e forneça o
número que ganhará o prêmio de acordo com as regras acima.
11. Escreva uma função que insira um valor dado num vetor numa posição dada.
Por exemplo, se o vetor for v = {3, 8, 5, 9, 12, 3}, o valor dado for 10 e a posição dada for
4, a função deve fornecer v = {3, 8, 5, 10, 9, 12, 3}.
12. Escreva uma função que insira um valor dado num vetor ordenado de modo
que o vetor continue ordenado. Por exemplo, se o vetor dado for v = {2, 5, 7, 10, 12, 13}
e o valor dado for 6, a função deve fornecer o vetor v = {2, 5, 6, 7, 10, 12, 13}.
13. Escreva uma função que delete uma componente de ordem dada de um vetor
dado. Por exemplo, se o vetor dado for v = {2, 5, 7, 10, 12, 13} e a componente a ser
deletada for a de ordem 4, programa deve fornecer o vetor v = {2, 5, 7, 12, 13}.
14. Escreva uma função que, dadas duas relações de números, cada uma delas com
números distintos, forneça os números que aparecem nas duas listas. Por exemplo, se as
relações forem u = {9, 32, 45, 21, 56, 67, 42, 55} e w = {24, 42, 32, 12, 45, 11, 67, 66,
78}, a função deve fornecer o vetor v = {32, 45, 67, 42}.

121
Capítulo 6 Vetores

15. Escreva uma função que, dado um vetor ordenado, forneça a maior diferença
entre duas componentes consecutivas, fornecendo também as ordens das componentes
que geraram esta maior diferença. Por exemplo, se o vetor dado for v = {3, 5, 9, 16, 17,
20, 26, 31}, a função deve fornecer como maior diferença o valor 7 (16 - 9) e as ordens 4
e 3.
16. Uma avaliação escolar consiste de 50 questões objetivas, cada uma delas com 5
opções, v = {1, 2, 3, 4 e 5}, sendo apenas uma delas verdadeira. Escreva uma função que
receba a seqüência de respostas corretas, o gabarito, e corrija um cartão-resposta dado.
17. Escreva uma função que forneça o valor numérico de um polinômio P(x) dado,
para um valor de x dado. Por exemplo, se o polinômio dado for P(x) = x3 + 2x - 1 e o
valor de x dado for 2, a função deve fornecer P(2) = 23 + 2x2 - 1 = 11.
18. O(s) valor(es) de maior freqüência de uma relação de valores numéricos é(são)
chamado(s) moda da relação. Escreva uma função que receba uma relação de notas
escolares maiores do que zero e menores do que ou iguais a 10, com uma casa decimal, e
forneça a(s) moda(s) desta relação. Por exemplo, se a relação de notas for v = {8,0; 3,5,
4,5; 8,0; 6,0; 4,5; 6,0; 3,5; 2,5; 6,0; 9,0} a função deve fornecer o valor 6,0 (freqüência 3).
19. Escreva uma função que receba um número inteiro n e forneça o número
formado pelos algarismos de n escritos na ordem inversa. Por exemplo, se o número dado
for 3876, a função deve fornecer 6783.
20. A matemática prova que a conversão de um número do sistema decimal para o
sistema binário pode ser feita através de divisões sucessivas do número e dos quocientes
sucessivamente obtidos por 2, sendo então o número binário dado pela seqüência iniciada
por 1 e seguida pelos restos obtidos nas divisões sucessivas, na ordem inversa em que são
obtidos. Por exemplo, para se converter 22 do sistema decimal para o sistema binário
temos: 22 % 2 = 0; 11 % 2 = 1; 5 % 2 = 1; 2 % 2 = 0 e, portanto, 22 = (10110)2. Escreva
uma função que converta um número positivo dado no sistema decimal de numeração
para o sistema binário, usando o algoritmo acima.
21. O exercício 10 da seção 4.5 solicitava uma função que determinasse a
decomposição em fatores primos, fornecendo os fatores primitivos e suas respectivas
multiplicidades. Na ocasião os fatores primos e suas multiplicidades eram apenas
exibidos não sendo armazenados. Modifique a função referida para que os fatores primos
e as suas multiplicidades sejam armazenados, antes de serem exibidos.
22. A Universidade Federal de Alagoas adota o sistema de verificação de
aprendizagem listado no exemplo 5 da seção 3.4, com o adendo de que terá direito a uma
reavaliação um aluno que obtiver uma nota inferior a 7,0 em algum bimestre. Neste caso,
a nota obtida na reavaliação substitui a menor das notas bimestrais obtidas. Escreva uma
função que, recebendo as notas das avaliações bimestrais e, se for o caso, a nota da
reavaliação e, se for o caso, a nota da prova final, forneça a média final de um aluno da
UFAL e a sua condição em relação à aprovação.
23. Escreva uma função que forneça a transposta de uma matriz dada.
24. Um dos métodos para a se estudar as soluções de um sistema linear de n
equações a n incógnitas aplica operações elementares sobre as linhas da matriz dos
coeficientes, sendo a permuta de duas linhas uma destas operações elementares. Escreva
uma função que permute as posições de duas linhas de uma matriz dadas.

122
Capítulo 6 Vetores

25. Uma matriz quadrada é dita triangular se os elementos situados acima de sua
diagonal principal são todos nulos. Escreva uma função que receba uma matriz quadrada
e verifique se ela é triangular.
26. O exemplo 4 deste capítulo apresentou uma função para armazenar uma matriz
simétrica. Este exercício quer algo contrário: escreva uma função que verifique se uma
matriz dada é simétrica.
27. Escreva uma função que determine o produto de duas matrizes.
28. Escreva uma função que determine as médias de cada uma das linhas de uma
 3 7 4 6
 
matriz. Por exemplo, se a matriz dada for  5 4 5 4  a função deve fornecer a
 
2 6 5 1
 3 7 4 6 5,0
 
matriz  5 4 5 4 4 ,5  .
 
 2 6 5 1 3,5
29. Escreva um programa que determine o menor valor de cada uma das linhas de
uma matriz dada, fornecendo o índice da coluna que contém este menor valor. Por
 3 7 4 6
 
exemplo, se a matriz dada for  5 2 5 4  , a função deve fornecer uma tabela do tipo
 
2 6 5 1
Linha Menor valor Coluna
1 3 1
2 2 2
3 1 4
Uma função como esta poderia receber os preços de diversos produtos praticados
por vários supermercados e forneceria, para cada produto, o menor preço e o
supermercado que pratica este melhor preço.
30. No exemplo 4 da seção anterior vimos como armazenar uma matriz simétrica.
Na prática, uma matriz deste tipo ocorre, por exemplo, numa tabela de distâncias entre
cidades, como a seguinte tabela, que dá as distâncias aéreas, em km, entre as capitais dos
estados nordestinos (Aracaju, Fortaleza, João Pessoa, Maceió, Natal, Recife, Salvador,
São Luís, Teresina).

123
Capítulo 6 Vetores

A F JP M N R S SL T
A 0 812 438 210 550 398 267 1218 1272
F 812 0 562 730 444 640 1018 640 432
JP 418 562 0 284 144 110 758 1208 987
M 210 730 294 0 423 191 464 1220 1126
N 550 414 144 423 0 252 852 1064 843
R 398 640 118 191 252 0 654 1197 935
S 267 1018 758 464 852 654 0 1319 1000
SL 1218 640 1208 1220 1064 1197 1319 0 320
T 1272 432 987 1126 843 935 1000 320 0
Imagine que uma companhia de transporte aéreo estabeleça que uma viagem entre
duas cidades que distem mais de 400 Km deve ter uma escala. Escreva um programa que
armazene uma tabela das distâncias aéreas entre n cidades e, dadas duas cidades,
determine, se for o caso, a cidade em que deve se realizar uma escala para que o percurso
seja o menor possível. Por exemplo, nas condições estabelecidas, a viagem entre Maceió
e São Luís deve ter uma escala em Fortaleza (o percurso Maceió/Fortaleza/São Luís é de
1370 Km; o percurso, por exemplo, Maceió/Recife/São Luís é de 1388 Km).
31. (Problema não trivial) Utilizando uma função recursiva, escreva um programa
que gere as combinações dos números 1, 2, ..., n com taxa k, n e k dados. Por exemplo, se
n = 5 e k = 3, o programa deve gerar as combinações
1, 2, 3
1, 2, 4
1, 2, 5
1, 3, 4
1, 3, 5
1, 4, 5
2, 3, 4
2, 3, 5
3, 4, 5

124
7 Pesquisa e Ordenação
7.1 Introdução
Neste capítulo, discutiremos dois problemas clássicos de computação. O primeiro
deles, chamado pesquisa, busca ou consulta, consiste em se verificar se um dado valor
está armazenado num vetor (ou num campo de um registro de um arquivo, como veremos
no capítulo 9).
São vários os exemplos de pesquisas em computação. Uma busca por páginas da
internet que contenham um determinado assunto; uma busca no Registro Nacional de
Veículos Automotores (RENAVAM) na tentativa de se encontrar o nome do proprietário
do veículo de uma placa dada; uma busca nos registros da Receita Federal a respeito de
um CPF dado.
O segundo problema clássico a ser discutido é conhecido como ordenação ou
classificação e foi introduzido no capítulo 3. Este problema consiste em se colocar numa
ordem preestabelecida uma relação de valores. No capítulo referido, mostramos como
ordenar uma relação contendo três valores. Neste capítulo, apresentaremos algoritmos
para ordenar uma lista com qualquer número de valores. A ordenação de uma relação é
realizada para que a leitura dos resultados seja facilitada ou para que, como veremos
abaixo, pesquisas sejam realizadas com mais eficiência. Um exemplo prático da
necessidade da ordenação ocorre na confecção da lista dos aprovados num concurso
vestibular. Algumas universidades divulgam esta lista com os nomes dos aprovados em
ordem alfabética e outras em ordem de classificação. Tanto num caso como no outro há
necessidade de ordenação.

7.2 Pesquisa seqüencial


O método de busca de mais fácil compreensão é o que temos utilizado até agora,
chamado pesquisa seqüencial, e que consiste em se percorrer, a partir da componente
zero, todo o vetor, comparando-se o valor de cada componente com o valor pesquisado.
Naturalmente, a pesquisa se encerra quando o valor pesquisado é encontrado ou quando
se atinge o final do vetor, significando, neste caso, que a pesquisa não foi bem sucedida.
A função abaixo pesquisa, numa relação de inteiros armazenada em v, um inteiro
passado para o parâmetro x. Observe que o parâmetro t receberá a quantidade de
elementos da relação e que a função retornará a posição do valor procurado na relação, se
a pesquisa for bem sucedida, e -1 se o valor procurado não for encontrado.
int PesqSeq(int *v, int t, int x)
{
int i;
for (i = 0; v[i] != x && i < t; i++);
if (i == t)
return -1;
Capítulo 7 Pesquisa e Ordenação

else
return i + 1;
}

7.3 Pesquisa binária


É muito fácil perceber que o método da pesquisa binária é bastante ineficiente:
imagine que este método fosse utilizado para se pesquisar a palavra zumbaia num
dicionário da língua portuguesa (a propósito, zumbaia significa cortesia exagerada;
cumprimento ruidoso e servil).
Quando a relação está ordenada, existe um método de busca, chamado pesquisa
binária, bem mais eficiente do que a pesquisa seqüencial: compara-se o elemento
pesquisado com a componente "central" da relação; se forem iguais, a pesquisa é
encerrada com sucesso; se o elemento pesquisado for menor que a componente central
repete-se a pesquisa em relação à "primeira metade" da relação; se o elemento pesquisado
for maior repete-se a pesquisa em relação à "segunda metade" da relação. Por exemplo,
uma pesquisa do número 7 na relação {1, 3, 4, 5, 6, 8, 10, 11, 12, 15, 18, 19, 20, 21, 22,
25, 26} começaria comparando-se 7 com 12; como 7 < 12, pesquisa-se 7 na relação {1, 3,
4, 5, 6, 8, 10, 11}; para isto compara-se 7 com 5 e, como 7 > 5, pesquisa-se este valor na
relação {6, 8, 10, 11}; pesquisa-se na relação {6, 8}; pesquisa-se em {6} e conclui-se que
7 não está relação.
int PesqBinaria(int *v, int t, int f)
{
int i, Central;
i = 0;
Central = t/2;
while ((f != v[Central]) && (i <= t))
{
if (f < v[Central])
t = Central - 1;
else
i = Central + 1;
Central = (i + t)/2;
}
if (i > t)
return (-1);
else
return(Central);
}
A pesquisa binária também é importante no desenvolvimento da lógica de
programação pelo fato de que é uma função que pode ser implementada recursivamente,
sem que a implementação recursiva seja menos eficiente do que a não recursiva. Para

126
Capítulo 7 Pesquisa e Ordenação

perceber a recursividade basta ver que a mesma pesquisa se repete, sendo que, em cada
repetição, o vetor pesquisado tem alterado a posição da sua última componente ou da sua
primeira componente.
int PesqBinRec(int *v, int i, int t, int x)
{
int Central;
Central = (i + t)/2;
if (v[Central] == x)
return (Central + 1);
else
if (t < i)
return (-1);
else
if (x < v[Central])
PesqBinRec(v, i, Central - 1, x);
else
PesqBinRec(v, Central + 1, t, x);
}

7.4 Ordenação

O SelecSort
O algoritmo SelectSort consiste em se selecionar, sucessivamente, o maior
elemento, o segundo maior elemento, o terceiro maior elemento, etc., e, após cada
seleção, armazenar o valor selecionado num vetor auxiliar na posição que mantém o tal
vetor auxiliar ordenado. Por exemplo, se se pretende a ordenação em ordem crescente, o
"primeiro maior valor" é armazenado na última posição do vetor auxiliar; o "segundo
maior valor" é armazenado na penúltima posição do vetor auxiliar e assim
sucessivamente. Para que se obtenha o "segundo maior valor" do vetor, excluímos o
"primeiro maior valor" atribuindo a esta componente um valor sabidamente menor do que
todos os valores armazenados no vetor. Por exemplo, se os valores do vetor são positivos
pode-se atribuir -1 a cada componente já selecionada e já armazenada no vetor auxiliar.
Para exemplificar o método, vamos ordenar o vetor v = {5, 2, 7, 1, 8}. Basta
percorrer o vetor 5 vezes selecionando sucessivamente 8, 7, 5, 2 e 1 e realizando as
seguintes atribuições:
1. Aux = { , , , , 8}
v = {5, 2, 7, 1, -1}
2. Aux = { , , , 7, 8}
v = {5, 2, -1, 1, -1}
3. Aux = { , , 5, 7, 8}
v = {-1, 2, -1, 1, -1}

127
Capítulo 7 Pesquisa e Ordenação

4. Aux = { , 2, 5, 7, 8}
v = {-1, -1, -1, 1, -1}
5. Aux = {1, 2, 5, 7, 8}
v = {-1, -1, -1, -1, -1},
Para finalizar, basta armazenar nas componentes de v as componentes de Aux.
void MaiorElemento(int *v, int t, int *m, int *p)
{
int i, Pos;
*m = v[0];
Pos = 0;
for (i = 1; i < t; i = i + 1)
if (v[i] > *m)
{
*m= v[i];
Pos = i;
}
*p = Pos;
}
void SelectSort(int *v, int t)
{
int i, Pos, Aux[500];
for(i = 0; i < t; i = i + 1)
{
MaiorElemento(v, t, &Aux[t - 1 - i], &Pos);
v[Pos] = -1;
}
for (i = 0; i < t; i = i + 1)
v[i] = Aux[i];
}
Observe que, como o parâmetro m é passado por referência, a função
MaiorElemento() já armazena no vetor Aux os maiores elementos de v nas suas posições
definitivas.
Há uma outra versão do SelectSort que prescinde de um vetor auxiliar. Se o vetor
contém k componentes, esta versão consiste em se comparar a maior dentre as k - 1
primeiras componentes com a componente de ordem k, permutando-se suas posições se
aquela maior componente for menor do que esta última. Esta operação coloca o maior
elemento na última posição do vetor, como desejado. Este raciocínio é repetido no vetor
das k - 1 primeiras componentes e assim sucessivamente.
void SelectSortVersao2(int *v, int t)
{
int Pos, k, m;
k = t - 1;

128
Capítulo 7 Pesquisa e Ordenação

while (k > 0)
{
MaiorElemento(v, k, &m, &Pos);
if (v[k] < v[Pos])
{
v[Pos] = v[k];
v[k] = m;
}
k--;
}
}

O BubbleSort
O algoritmo BubbleSort consiste em se percorrer o vetor a ser ordenado várias
vezes, comparando-se cada elemento com o seguinte, permutando suas posições se eles
não estiverem na ordem pretendida. Assim, cada vez que o vetor é percorrido, o maior
(ou o menor) elemento ainda não ordenado, é colocado na sua posição de ordenação
definitiva. Naturalmente, o vetor será percorrido até que não haja mais trocas a se fazer,
quando então ele estará ordenado. Por exemplo, se o vetor a ser ordenado em ordem
crescente for v = {5, 1, 9, 3, 7, 2}, teríamos as seguintes configurações para v, de acordo
com a ordem de percurso:
Percurso v
0 {5, 1, 9, 3, 7, 2}
1 {1, 5, 9, 3, 7, 2}
{1, 5, 3, 9, 7, 2}
{1, 5, 3, 7, 9, 2}
{1, 5, 3, 7, 2, 9}
2 {1, 3, 5, 7, 2, 9}
{1, 3, 5, 2, 7, 9}
3 {1, 3, 2, 5, 7, 9}
4 {1, 2, 3, 5, 7, 9}
A seguinte função implementa o algoritmo descrito acima.
void BubbleSort(int *v, int t)
{
int j, s, Aux;
do
{
s = 1;
t = t - 1;
for (j = 0; j < t; j = j + 1)

129
Capítulo 7 Pesquisa e Ordenação

if (v[j] > v[j + 1])


{
Aux = v[j];
v[j] = v[j + 1];
v[j + 1] = Aux;
s = 0;
}
}
while (s == 0);
}
Observe que a variável s verifica se houve alguma troca para que outro percurso
seja realizado. Observe também que o comando t = t – 1 se justifica pelo fato de que no
percurso de ordem i, i – 1 elementos já estão em suas posições definitivas.

7.5 Exercícios propostos


1. Algumas pessoas acham que são azaradas quando procuram uma ficha numa
pilha, sempre tendo receio que a ficha procurada seja uma das últimas da pilha. Uma
pessoa que acredite ser assim azarada pode pesquisar a tal ficha pesquisando,
sucessivamente, a parte superior e a parte inferior da pilha. Assim, verifica a primeira
ficha, em seguida, a última, em seguida, a segunda ficha, em seguida, a penúltima e assim
sucessivamente. Escreva uma função que implemente este método de pesquisa.
2. A algoritmo InsertSort para ordenação de um vetor Vet consiste em se tomar um
vetor auxiliar Aux, contendo uma única componente Vet[0]. Em seguida, inserem-se as
demais componentes de Vet, uma a uma, em Aux de modo que Aux se mantenha
ordenado. Escreva uma função que implemente o InsertSort.

130
8 Cadeias de Caracteres (Strings)
8.1 Introdução
Como já sabemos uma declaração do tipo
char Cad[10];
define um conjunto de dez posições de memória, cada uma delas de um byte, capazes de
armazenar variáveis do tipo char. Como dissemos de passagem no capítulo anterior, um
vetor cujas componentes são do tipo char constitui uma cadeia de caracteres ou,
emprestado do inglês, uma string.
Além da quantidade de bytes, o que diferencia a declaração acima da declaração
int v[10];
é que, enquanto o vetor v não pode ser referenciado globalmente com um comando do
tipo scanf("%v", v) ou printf("%v", v) os compiladores C contêm recursos para referência
a uma string como se ela fosse uma variável simples.
Desta forma, com a declaração acima, podemos ter um programa como o seguinte:
#include <stdio.h>
main()
{
char Cad[10];
printf("Digite uma palavra");
scanf("%s", Cad);
printf("A palavra digitada foi: %s ", Cad);
}
Vale lembrar que, de acordo com o capítulo 2, "%s" tanto é o código de conversão
da função scanf() para armazenamento de uma cadeia de caracteres como é o código de
formatação da função printf() para exibição de uma string. Vale também observar a não
colocação do operador de endereço & em scanf("%s", Cad). Para compreender este fato
basta lembrar que um vetor é um ponteiro. Ainda vale observar que a tentativa de se
armazenar em Cad uma cadeia com mais de 10 caracteres não é recusada pelo sistema,
mas podem ocorrer armazenamentos indesejados. Para que o tamanho da cadeia não fique
limitado, deve-se declarar a cadeia como um ponteiro: char *Cad;.
Também é possível se fazer atribuições explícitas a uma string, quando ela foi
declarada como um ponteiro. Por exemplo, poderíamos "sofisticar" o programa acima,
escrevendo-o:
#include <stdio.h>
main()
{
char *Cad, *Str = "Digite uma palavra";
printf("%s", Str);
Capítulo 8 Cadeias de Caracteres (Strings)

scanf("%s", Cad);
printf("A palavra digitada foi: %s\n", Cad);
}
A referência explícita a uma string é possível pelo fato de que o sistema coloca no
final da cadeia o caractere nulo, indicado por '\0', tanto numa atribuição (como
Str = "Digite uma palavra";) como numa entrada através da função scanf(). É este
caractere nulo que indica ao sistema o final da string para que ele possa processá-la de
acordo com o pretendido, como exibi-la através da função printf(), por exemplo.
É necessário lembrar que a aposição automática do caractere nulo no final da string
força se definir n numa declaração do tipo
char Cad[n];
uma unidade maior do que o número de caracteres da cadeia que se pretende armazenar
em Cad.
Neste formato de declaração, pode-se inicializar uma string quando da sua
declaração, colocando o valor inicial pretendido entre chaves, que não são necessárias no
Turbo C++ 3.0.
#include <stdio.h>
main()
{
char *Cad, Str[20] = {"Digite uma palavra"};
printf("%s", Str);
scanf("%s", Cad);
printf("A palavra digitada foi: %s\n", Cad);
}
Agora o sistema verifica o número de caracteres da cadeia que se pretende
armazenar, recusando, em nível de compilação, se este número for maior do que n.
O armazenamento de uma cadeia de caracteres através da função scanf() tem uma
limitação em função de que esta função considera também (além da digitação de <enter>)
o espaço em branco como finalizador da string. Isto implica que uma cadeia de caracteres
que possua um espaço em branco (o nome de uma pessoa, por exemplo) não possa ser
completamente armazenada.
Para estes casos, deve-se utilizar a função gets() cujo protótipo está no arquivo
stdio.h e armazena no argumento com o qual foi ativado uma cadeia de caracteres
digitada no teclado. Assim para se armazenar uma frase ou o nome completo de uma
pessoa, o programa acima deveria ser modificado para o seguinte programa:
#include <stdio.h>
main()
{
char *Cad;
printf("Digite a frase");
gets(Cad);

132
Capítulo 8 Cadeias de Caracteres (Strings)

printf("A frase digitada foi: %s", Cad);


}
Repetindo o que já foi dito, é necessário um certo cuidado com execuções
sucessivas das funções scanf() (para strings) e gets(). O que acontece é o seguinte:
quando os dados estão sendo digitados, eles são armazenados numa área chamada buffer
e as execuções das funções aqui referidas fazem o sistema armazenar os dados do buffer
na variável pretendida. Assim, se não estiver vazio, o conteúdo do buffer será armazenado
por uma próxima ativação de uma destas funções. É prudente, então, "esvaziar" o buffer
antes de uma chamada de uma segunda chamada de scanf() ou de gets(), isto podendo ser
feitos através da instrução fflhush(stdin) (no próximo capítulo, faremos comentários sobre
stdin).

8.2 Funções de biblioteca para manipulação de


cadeias de caracteres
Ao contrário de outras linguagens, C não possui operador que atue em operandos
do tipo cadeia de caracteres. Qualquer manipulação de strings é feita através de funções
da biblioteca padrão de C. Nesta seção, apresentaremos algumas destas funções, cujos
protótipos estão no arquivo string.h.
a) Determinando o comprimento de uma string.
A função de protótipo
int strlen(char *s)
retorna o número de caracteres da string armazenada em s, sem considerar o caractere
nulo.
Por exemplo, a seqüência de instruções
char *Cad;
int Comp;
Cad = "Universidade Federal de Alagoas";
Comp = strlen(Cad);
armazenará na variável Comp o valor 31.
Vale observar que o parâmetro de strlen() pode ser uma constante: podemos ter um
comando printf("%d", strlen("Brasil"));
b) Comparando duas strings.
A comparação entre duas strings em relação à ordem alfabética é feita através da
função de protótipo
int strcmp(char *s1, char *s2);
que retorna a diferença entre os códigos ASCII dos primeiros caracteres diferentes do
dois parâmetros, que podem ser constantes. Por exemplo, a chamada str("Casa", "Caso");

133
Capítulo 8 Cadeias de Caracteres (Strings)

retorna 14, que é a diferença entre os códigos ASCII de 'o' (111) e o de 'a' (97).
Naturalmente, se as cadeias são iguais, a função retorna 0 (zero).
c) Convertendo maiúsculas para minúsculas e vice-versa.
A conversão das letras de uma string de minúsculas para maiúsculas é feita através
da função de protótipo
char *strupr(char *s);
enquanto que a conversão inversa é feita através da função
char *strlwr(char *s);
podendo o parâmetro ser uma constante.
d) Concatenando uma string a outra.
A concatenação de uma cadeia de caracteres a uma outra cadeia é feita através da
função
char *strcat(char *s1, char *ss);
que retorna a cadeia s1 acrescida dos caracteres de s2, que pode ser uma constante. Por
exemplo, a seqüência de instruções
char *Str;
Str = "Computa";
strcat(Str, "dor");
armazena em Str a cadeia "Computador".
e) Fazendo cópia de uma string.
Se s1 e s2 foram definidas como strings não se pode fazer uma atribuição do tipo
s1 = s2. Se pretendemos armazenar o conteúdo de s2 em s1 devemos utilizar a função
char *strcpy(char *s1, char *s2);
que faz uma cópia do conteúdo de s2 em s1, podendo s2 ser uma constante.
f) Copiando parte de uma string.
Pode-se copiar os n primeiros caracteres de uma string através da função
char *strncpy(char *s1, char *s2, int n);
que armazena em s1 os n primeiros caracteres de s2, podendo este segundo parâmetro ser
uma constante. É necessário observar que o caractere nulo não é armazenado, devendo
isto ser feito pelo programa.
g) Verificando se uma string é subcadeia de outra string.
Para se verificar se uma dada cadeia de caracteres está contida em outra cadeia,
utiliza-se a função
char *strstr(char *s1, char *s2);

134
Capítulo 8 Cadeias de Caracteres (Strings)

que retorna um ponteiro para a primeira posição a partir da qual s2 ocorre em s1,
retornando NULL se s2 não está contida em s1. Por exemplo, a execução do programa
#include <stdio.h>
#include <string.h>
main(void)
{
char *Str1 = "Logica de Programacao", *Str2 = "grama", *p;
p = strstr(Str1, Str2);
if (p != NULL)
printf("A \"ultima\" substring de %s que contem %s e: %s.\n", Str1, Str2, p);
else
printf("%s nao esta contida em %s", Str2, Str1);
}
exibe na tela a afirmação:
A "ultima" substring de Logica de Programacao que contem grama e gramacao.
h) Convertendo uma string em números
Como veremos em exemplos a seguir, muitas vezes é preferível que um dado de
entrada seja um vetor de caracteres, mesmo que a função necessite realizar operações
matemáticas com ele. A conversão de uma subcadeia de dígitos para os tipos int, long ou
float, respectivamente, é feita através das funções
int atoi(char *s);
long atol(char *s);
double atof(char *s);
cujos protótipos estão no arquivo stdlib.h. Estas funções retornam o número (no formato
respectivo) correspondente à primeira (da esquerda para direita) subcadeia de s que pode
ser convertida , retornando 0 (zero) se o primeiro caractere de s não for um dígito ou um
dos caracteres + e – (se o primeiro caractere for + ou -, para que haja alguma conversão o
segundo deve ser um dígito).

8.3 Exemplos Parte VI


1. A função a seguir exclui uma dada quantidade n de caracteres a partir de uma
posição dada p. Para conseguir excluir os n caracteres, "trazemos" a substring formada
pelos últimos caracteres que não serão excluídos mais o caractere nulo para a posição p.
Naturalmente, se o número de caracteres a serem excluídos for maior do que o disponível,
todos os caracteres a partir de p serão excluídos. Isto é obtido através da função strncpy().
#include <stdio.h>
#include <string.h>
void DeletaCaracteres(char *s, int n, int p)
{

135
Capítulo 8 Cadeias de Caracteres (Strings)

int i, Comp;
char *Aux;
Comp = strlen(s);
if (p + n <= Comp)
{
i = p;
while (i <= Comp - n)
{
s[i] = s[i + n];
i = i + 1;
}
}
else
s[p] = '\0';
}
2. A próxima função insere uma cadeia s1 numa cadeia s2 a partir de uma posição p
dada.
void Insere(char *s1, char *s2, int p)
{
int i, c1, c2;
char a[20];
c1 = strlen(s1);
c2 = strlen(s2);
for (i = 0; i < c1 + c2 ; i++)
if (i < p)
a[i] = s1[i];
else
if (i < p + c2)
a[i] = s2[i - p];
else
a[i] = s1[i - c2];
a[i] = '\0';
strcpy(s1, a);
}
3. A função strstr() comentada acima verifica se uma string s1 está contida numa
string s2 mas não retorna a posição a partir da qual s2 ocorre em s1. A função abaixo
resolve esta questão através da diferença dos comprimentos de s1 e da cadeia que a
chamada strstr(s1, s2) retorna.
int Pos(char *s1, char *s2)
{
char *Aux;
Aux = strstr(s1, s2);

136
Capítulo 8 Cadeias de Caracteres (Strings)

if (Aux != NULL)
return(strlen(s1) - strlen(Aux));
else
return (-1);
}
4. Com a função strncpy() pode-se copiar os n primeiros caracteres de uma string.
A função abaixo armazena na string passada para s2 a substring composta por n
caracteres da cadeia s1 a partir de da posição p.
void Copia(char *s1, char *s2, int n, int p)
{
int i;
for (i = 0; i < n ; i = i + 1)
s2[i] = s1[i + p];
s2[n] = ‘\0’;
}
5. A próxima função converte uma data dada no formato americano mm/dd/aaaa
para o formato brasileiro dd/mm/aaaa. O algoritmo utilizado foi o seguinte:
1. Com a função strncpy() extrai-se a subcadeia mm/.
2. Com a função DeletaCaracteres() definida no exemplo 1 acima deleta-se a
subcadeia mm/ da data mm/dd/aaaa.
3. Com a função InsereCadeia() definida no exemplo 2 acima insere-se mm/ em
dd/aaaa na terceira posição.
void ConverteData(char *s)
{
char Aux[12];
strncpy(Aux, s, 3);
Aux[3] = '\0';
DeletaCaracteres(s, 3, 0);
Insere(s, Aux, 3);
}
6. Um programa que manipule datas deve possuir uma função que verifique se a
data dada era válida. Isto não ocorreria se o valor do mês fosse maior que 12 ou que, por
exemplo, se o mês fosse junho e o dia fosse 31.
int VerificaData(char s[11])
{
int i, d, m, a, Verifica;
char Dia[3], Mes[3], Ano[5];
Copia(s, Dia, 2, 0);
Copia(s, Mes, 2, 3);
Copia(s, Ano, 4, 6);
d = atoi(Dia);
m = atoi(Mes);

137
Capítulo 8 Cadeias de Caracteres (Strings)

a = atoi(Ano);
Verifica = 1;
if ((m <= 12) && (m >= 1) && (d >= 1) && (d <= 31))
switch(m)
{
case 2:
if (((a % 4 == 0) && (a % 100 != 0)) || (a % 400 == 0))
if (d > 29)
Verifica = 0;
else;
else
if (d > 28)
Verifica = 0;
break;
case 4: case 6: case 9: case 11:
if (d > 30)
Verifica = 0;
break;
}
else
Verifica = 0;
return(Verifica);
}
Vale observar que os comandos Verifica = 0 poderiam ser substituídos por
return(0). Neste caso, o último comando seria return(1).
7. Os compiladores C ignoram espaços em branco digitados num programa. Uma
maneira de se tornar isto possível é, antes da compilação, eliminar todos os espaços em
branco "supérfluos", ou seja, deixar duas palavras sempre separadas por um único espaço
em branco. A função abaixo, utilizando a função DeletaCaracteres(), realiza tal ação.
void ExcluiBrancosSuperfluos(char *s)
{
int i, NumBrancos;
i = 0;
while (s[i] != '\0')
{
NumBrancos = 0;
while (s[i] == ' ')
{
NumBrancos = NumBrancos + 1;
i = i + 1;
}
if (NumBrancos > 1)
{

138
Capítulo 8 Cadeias de Caracteres (Strings)

i = i - NumBrancos;
DeletaCaracteres(s, NumBrancos - 1, i);
}
i = i + 1;
}
}
8. A questão a seguir é bem interessante. Trata-se de um programa que determine o
dígito verificador de um número de uma conta corrente, de um número de matrícula de
um estudante de uma escola, etc. O dígito verificador serve para a prevenção de possíveis
erros de digitação. Por exemplo, se a matrícula 30245-7 fosse digitada erroneamente
como 39245-7, o erro seria detectado, pois o dígito verificador da conta 39245 seria 6 e
não 7. Existem vários métodos para a determinação do dígito verificador. Um deles é
dado pelo seguinte algoritmo:
1. Multiplica-se os números correspondentes aos dígitos da conta, da direita para
esquerda, por 2, por 3, etc..
2. Soma-se os produtos obtidos no item 1.
3. Determina-se o resto da divisão da soma obtida no item 2 por 11.
4. Subtrai-se de 11 o resto obtido no item 3
5. Se o valor obtido no item 4 for 10 ou 11 o dígito verificado é igual a zero; senão,
o dígito é o valor obtido no item referido.
Por exemplo, se o número da conta for 30245, temos
1. 5x2 = 10, 4x3 = 12, 2x4 = 8, 0x5 = 0, 3x6 = 18
2. 10 + 12 + 8 + 0 + 18 = 48
3. Resto(48, 11) = 4
4. 11 - 4 = 7
5. Dígito verificador = 7.
A função abaixo implementa esse algoritmo. Observe que foi utilizado molde para
converter caracteres em inteiros e vice-versa.
int ArmazenaDigitos(char *s, int *v)
{
int i, Comp;
Comp = strlen(s);
for (i = 0; i < Comp; i = i + 1)
v[i] = (int) (s[i] - '0');
return(i);
}

char CalculaDigito(char *s)


{
char c;
int t, i, j, Digito, *v;

139
Capítulo 8 Cadeias de Caracteres (Strings)

t = ArmazenaDigitos(s, v);
Digito = 0;
j = 2;
for (i = t - 1; i >= 0; i = i - 1, j = j + 1)
Digito = Digito + v[i]*j;
Digito = Digito % 11;
Digito = 11 - Digito;
if ((Digito == 10) || (Digito == 11))
Digito = 0;
c = (char) Digito + '0';
return (c);
}

8.4 Exercícios propostos


1. Uma palavra é palíndroma se ela não se altera quando lida da direita para
esquerda. Por exemplo, raiar é palíndroma. Escreva um programa que verifique se uma
palavra dada é palíndroma.
2. Um dos recursos disponibilizados pelos editores de texto mais modernos é a
determinação do número de palavras de um texto. Escreva um programa que determine o
número de palavras de um texto dado.
3. O exercício 21 do capítulo 6 solicitava um programa que convertesse um número
dado no sistema decimal para o sistema binário. Pela limitação do sistema em tratar
números inteiros, uma solução que tratasse a conversão como sendo do tipo long seria
limitada. Escreva um programa para a conversão citada, tratando o valor em binário como
uma cadeia de caracteres.
4. Escreva um programa que converta um número do sistema binário, dado como
uma cadeia de zeros e uns, para o sistema decimal de numeração.
5. Reescreva a função apresentada no exemplo 8 deste capítulo de tal modo que ele
possa, além de gerar dígitos verificadores, verificar se uma conta dada (incluindo o dígito
verificador) foi digitada incorretamente, incorreção esta detectada pelo dígito verificador.
6. Os editores de texto possuem um recurso que permite o usuário substituir uma
subcadeia de um texto por outra cadeia de caracteres. Escreva um programa que realize
esta ação numa frase dada.
7. As companhias de transportes aéreos costumam representar os nomes dos
passageiros no formato último sobrenome/nome. Por exemplo, o passageiro Carlos
Drumond de Andrade seria indicado por Andrade/Carlos. Escreva um programa que
receba um nome e o escreva no formato acima.
8. As normas para a exibição da bibliografia de um artigo científico, de uma
monografia, de um livro texto, etc., exigem que o nome do autor seja escrito no formato
último sobrenome, seqüência das primeiras letras do nome e dos demais sobrenomes,
seguidas de ponto final. Por exemplo, Antônio Carlos Jobim seria referido por Jobim, A.
C.. Escreva um programa que receba um nome e o escreva no formato de bibliografia.

140
Capítulo 8 Cadeias de Caracteres (Strings)

9. É muito comum que os títulos de documentos como avisos, declarações,


atestados, etc., apareçam em letras maiúsculas separadas por um espaço em branco.
Escreva uma função que receba uma palavra e a retorne no formato acima.

141
9 Estruturas e Arquivos
9.1 O que são estruturas
Um vetor é capaz armazenar diversos valores, com a ressalva de que todos sejam
de um mesmo tipo de dado. Um programa que gerencie os recursos humanos de uma
empresa manipula dados de tipos diferentes relativos a cada um dos funcionários. Por
exemplo, para cada funcionário deve-se ter sua matrícula, seu nome, seu endereço, o
cargo que ele ocupa, o número de seus dependentes, o seu salário, a data de admissão, etc.
Observe que, nome, matrícula, endereço, data de admissão e cargo que ele ocupa podem
ser tratados com variáveis do tipo string, porém, como eventualmente haverá necessidade
de se efetuar operações aritméticas com eles, o número de dependentes deve ser tratado
como do tipo int e valor do salário, do tipo float.
A utilização de uma variável simples para cada um destes elementos, implicaria,
como são vários funcionários, a necessidade de vários vetores, o que poderia atrapalhar a
legibilidade e a manutenibilidade do programa, alem de dificultar a possibilidade de
armazenamento dos dados em disco, conforme veremos numa seção posterior.
Uma estrutura é um conjunto de variáveis, denominadas campos ou membros, que
podem ser de tipos diferentes. É comum se associar um identificador a uma estrutura,
chamado etiqueta da estrutura, para que se possa definir uma variável deste tipo.
A definição de uma estrutura é feita com a seguinte sintaxe:
struct TEstrutura
{
Tipo de dado Identificador do campo 1;
Tipo de dado Identificador do campo 2;
. . .
Tipo de dado Identificador do campo n;
};
onde, para cada campo, Tipo de dado pode ser qualquer tipo, até mesmo uma estrutura.
Se esta declaração for feita fora de qualquer função, a estrutura TEstrutura será global e
qualquer função do programa pode declarar uma variável capaz de armazenar valores de
acordo com os tipos de dados dos campos, isto sendo feito a partir de uma declaração do
tipo
struct TEstrutura Identificador;
Por exemplo, para o programa do exemplo acima poderíamos definir as estruturas
struct TEndereco
{
char Rua[40];
char Numero[5];
char Bairro[10];
Capítulo 9 Estruturas e Arquivos

char Cep[9];
};
struct TEstrutura
{
char Matricula[11];
char Nome[40];
struct TEndereco Endereco;
int NumDependentes;
float Salario;
char Cargo[8];
};
e então uma função poderia ter uma variável declarada da seguinte forma:
struct TEstrutura Registro;
Na verdade, ao se definir struct TEstrutura está se definindo um novo tipo de dado
e isto justifica a definição de uma variável, no caso Registro, do tipo de dado struct
TEstrutura. A linguagem C oferece uma outra forma de se definir um novo tipo de dado.
Trata-se da declaração typedef que poderia ser utilizado para se definir a estrutura acima
da seguinte forma:
typedef struct
{
char Matricula[11];
char Nome[40];
struct TEndereco Endereco;
int NumDependentes;
float Salario;
char Cargo[8];
}
TEstrutura;
Neste caso, a declaração de uma variável do tipo TEstrutura seria feita sem a
referência ao tipo struct:
TEstrutura Registro;
A referência a um campo particular da estrutura se faz com a aposição do
identificador da variável estrutura e o identificador do campo separados por um ponto. No
exemplo anterior poderíamos ter comandos do tipo
Registro.Salario = 4500.00;
scanf("%s", Registro.Matricula);
gets((Registro.Endereco).Rua);
sendo os parênteses de (Registro.Endereco).Rua não obrigatórios.

143
Capítulo 9 Estruturas e Arquivos

Embora, na prática, as estruturas a serem manipuladas pelos programas necessitem


de mais campos, vamos, nas seções seguintes, considerar uma estrutura definida da
seguinte forma:
struct TRegistro
{
char Mat[4];
char Nome[40];
float SalarioBruto;
};

9.2 O que são arquivos


Até o momento, os dados manipulados pelos nossos programas (dados de entrada,
dados gerados pelo programa e resultados do processamento) foram armazenados na
memória do computador que, como já foi dito, é uma memória volátil, no sentido de que
todas as informações nela armazenadas são perdidas quando a execução do programa é,
por qualquer motivo, encerrada.
É evidente que um programa que gerencia os recursos humanos de uma empresa
não pode manipular os dados relativos aos funcionários apenas na memória do
computador. Isto implicaria, por exemplo, a necessidade de que fossem digitados todos os
dados em todas as execuções do programa. É evidente que os dados relativos a cada um
dos funcionários da empresa devem estar armazenados, de forma permanente, em um
disco, de modo que o programa que gerencia os recursos humanos possa acessá-los em
execuções distintas.
Dados e informações reunidos e armazenados num disco constituem um arquivo e
a linguagem C permite que se manipulem arquivos em discos, fornecendo recursos para a
realização das operações básicas que podem ser neles executadas: criação de um arquivo,
alteração dos dados de um arquivo, exclusão de dados de um arquivo, inclusão de novos
dados no arquivo, exibição (na tela ou em formato impresso) do conteúdo de um arquivo,
etc..

9.3 Arquivos de registros


Os arquivos de uso mais comum na prática de programação em C são os arquivos
que armazenam dados oriundos de estruturas. Por exemplo, um sistema que gerencie uma
locadora de fitas deve manipular um arquivo que armazene para cada fita, um código, o
título do filme, o tema, a data de aquisição, o preço de custo, o valor da locação, etc. No
momento da entrada, estes dados podem ser armazenados numa estrutura para serem, em
seguida, armazenados num arquivo. Um conjunto de dados relativo a uma fita (neste
exemplo) é chamado registro e um arquivo em que um conjunto de registros está
armazenado é chamado arquivo de registros ou arquivo binário.

144
Capítulo 9 Estruturas e Arquivos

Uma grande parte das operações que são feitas em arquivos requer a verificação de
que o registro que se pretende manipular está armazenado no arquivo. Isto exige que os
registros possuam um campo cujos valores sejam distintos entre si, sendo o valor deste
campo um identificador do referido registro. Esta é a função de campos do tipo CPF,
matrículas, placas de veículos, etc.. Um campo identificador dos registros de um arquivo
é chamado chave.

Criando um arquivo de registros


Um arquivo é criado através da função fopen(), que possui dois parâmetros do tipo
string e retorna um ponteiro para uma estrutura pré-definda FILE (um ponteiro que
aponta para uma estrutura FILE é chamado ponteiro de arquivo). O primeiro dos
parâmetros de fopen() fixa o nome com o qual o arquivo será gravado no disco e o
segundo parâmetro é a string "wb" que indica o formato binário para o arquivo que se
está criando. Se, por alguma razão, o arquivo não for criado, a função fopen() retorna o
ponteiro NULL.
Por exemplo, o programa abaixo, cria, no disco da unidade A, um arquivo
denominado Teste.arq.
#include <stdio.h>
FILE *CriaArquivo(char s[12])
{
FILE *p;
p = fopen(s, "wb");
return(p);
}
main()
{
FILE *PontArquivo;
PontArquivo = CriaArquivo("A:\Teste.arq");
if (PontArquivo != NULL)
printf("Arquivo Teste.arq criado como sucesso");
else
printf("O arquivo Teste.arq não foi criado");
}
A função fopen(), além de criar um arquivo gravado no disco associa, através do
que ela retorna, um ponteiro para o arquivo referido. Este ponteiro é utilizado para se
referenciar o tal arquivo no restante do programa e é chamado fluxo. É o caso do ponteiro
de arquivo PontArquivo do exemplo anterior. Qualquer referência a ele será uma
referência ao arquivo Teste.arq.
A criação de um arquivo com a ativação de fopen(NomeArquivo, "wb") deve ser
solicitada com cautela. Se o arquivo de nome NomeArquivo existir, a chamada referida
"apagará" todo o seu conteúdo. O tipo de cautela necessária veremos a seguir.

145
Capítulo 9 Estruturas e Arquivos

Gravando registros em um arquivo


O programa anterior, apenas cria o arquivo no disco não gravando nenhum registro.
Se, após a sua execução, procurarmos o arquivo Teste.arq, encontrá-lo-emos com a
indicação de que seu conteúdo tem zero bytes. Ou seja é um arquivo vazio. É natural que
se crie um arquivo para gravar registros e isto pode ser feito quando da sua criação.
A gravação de registros em um arquivo é feito através da função fwrite() que
possui quatro parâmetros:
1. Um ponteiro r para uma variável do tipo void que receberá o endereço da
variável do tipo estrutura que contém os dados que se quer armazenar;
2. Um inteiro n que receberá o tamanho, em bytes, do registro a ser armazenado;
3. Um inteiro q que receberá o número de registros que serão armazenados;
4. Um ponteiro de arquivo p que receberá o fluxo associado através da função
fopen() ao arquivo de disco onde os dados serão armazenados.
Para se criar um arquivo de registros com a estrutura TRegistro definida na seção
anterior e, logo em seguida, gravar registros no arquivo, teríamos o seguinte programa.
#include <stdio.h>
struct TRegistro
{
char Mat[4];
char Nome[40];
float SalarioBruto;
};
/*Função que cria um arquivo em disco, deixando-o apto a armazenar dados */
FILE *CriaArquivo(char s[12])
{
FILE *p;
p = fopen(s, "wb");
return(p);
}
/* Função que grava dados armazenados numa estrutura em um arquivo*/
void GravaRegistros(char s[12])
{
FILE *p;
struct TRegistro r;
printf("Matricula (para encerrar, digite matricula 0): ");
fflush(stdin);
gets(r.Mat);
while ((r.Mat)[0] != '0')
{
printf("Nome: ");
fflush(stdin);
gets(r.Nome);

146
Capítulo 9 Estruturas e Arquivos

printf("Salario bruto: ");


scanf("%f", &r.SalBruto);
fwrite(&r, sizeof(r), 1, p);
printf("Matricula (para encerrar, digite matricula 0): ");
fflush(stdin);
gets(r. Mat);
}
fclose(p);
}
main()
{
FILE *PontArquivo;
char NomeArq[12];
printf("Digite o nome do arquivo");
gets(NomeArq);
PontArquivo = CriaArquivo(NomeArq);
if (PontArquivo != NULL)
GravaRegistros(NomeArq);
else
printf("O arquivo %s nao pode ser criado \n", NomeArq);
}
A função fclose() "fecha" um arquivo para que o sistema operacional possa
atualizar a tabela do diretório de arquivos. Observe que esta função tem um parâmetro
que receberá o fluxo associado ao arquivo que se pretende fechar. É necessário que todo
arquivo "aberto" seja fechado antes do encerramento da execução do programa.

Exibindo o conteúdo de um arquivo


Para se ter acesso ao conteúdo de um arquivo é necessário que este conteúdo seja
transferido para memória do computador para, em seguida, ser exibido na tela pela função
printf() ou impresso por uma impressora através da função fprintf() (veremos isto numa
seção seguinte). A transferência do conteúdo de um arquivo para memória pode ser feita
registro a registro, armazenando cada um deles em uma estrutura, ou através de um
conjunto de registros, armazenando-o num vetor de estruturas. Aqui optaremos pela
primeira alternativa.
A transferência de registros de um arquivo para a memória é feita através da função
fread() que, como a função fwrite(), possui quatro parâmetros:
1. Um ponteiro para uma variável do tipo void que receberá o endereço da variável
que armazenará os dados contidos no registro;
2. Um inteiro que receberá o tamanho, em bytes, da estrutura que armazenará o
registro na memória;
3. Um inteiro que receberá o número de registros que serão transferidos para a
memória (com a opção aqui escolhida este parâmetro sempre será igual a 1);

147
Capítulo 9 Estruturas e Arquivos

4. Um ponteiro de arquivo que receberá o fluxo associado, através da função


fopen(), ao arquivo de disco que contém os registros.
Para que seja possível a aplicação da função fread() é necessário que o arquivo
esteja "aberto para leitura", o que é feito também através da função fopen() agora com
segundo parâmetro "rb". Quando é feita uma chamada da função fopen() com os
argumentos Nome do Arquivo e "rb", o primeiro registro do arquivo fica disponível para
leitura (registramos este fato dizendo que o ponteiro de leitura e gravação aponta para o
primeiro registro).
Considerando-se que após a execução da função fread() o ponteiro de leitura e
gravação avança automaticamente para o próximo registro, pode-se percorrer todo o
arquivo até atingir o seu final, que é fornecido pela função feof(). Esta função tem como
parâmetro um ponteiro de arquivo e retorna um número diferente de zero quando o
ponteiro de leitura e gravação aponta para o final do arquivo.
Por exemplo, pode-se exibir na tela o conteúdo do arquivo gerado acima através da
seguinte função.
/*Função que exibe na tela o conteúdo de um arquivo */
void ExibeArquivo(char s[12])
{
FILE *p;
struct TRegistro r;
p = fopen(s, "rb");
fread(&r, sizeof(r), 1, p);
while (feof(p) == 0) /*Ou, o que é o mais utilizado, while (!feof(p))*/
{
printf("%s \b %s \b %f \n", r.Mat, r.Nome, r.SalarioBruto);
fread(&r, sizeof(r), 1, p);
}
fclose(p);
}

Verificando a existência de um arquivo


A ativação de fopen() no modo "rb" (o segundo parâmetro de fopen() é chamado
modo de abertura do arquivo) permite que se escreva uma função para verificar a
existência de um arquivo, que será útil para evitar uma ativação "desastrada" de fopen(),
já que, como dissemos acima, a ativação desta função no modo "wb" apaga todo o
conteúdo do arquivo que possuir o nome passado para o primeiro parâmetro desta função.
Isto implica a necessidade de que se tenha cuidado na abertura de um arquivo no modo
"wb", pois se for passado um nome de um arquivo que já existe todo o seu conteúdo será
perdido. É prudente, portanto, que a abertura de um arquivo no modo aqui discutido seja
precedida de uma função que verifique se um arquivo com o nome escolhido já existe.

148
Capítulo 9 Estruturas e Arquivos

Uma função com este objetivo é bastante simples, pois a função fopen() retorna NULL se
for ativada no modo "rb" com o arquivo que não existe.
int ExisteArquivo(char s[12])
{
FILE *p;
p = fopen(s, "rb");
if (p == NULL)
return(0);
else
{
fclose(p);
return(1);
}
}
Assim a função CriaArquivo() definida anteriormente deveria ser escrita da
seguinte forma:
FILE *CriaArquivo(char s[12])
{
FILE *p;
p = fopen(s, "rb");
if (p == NULL)
{
p = fopen(s, "wb");
return(p);
}
else
printf("\a Arquivo %s já existe!");
}

Localizando um registro num arquivo


Uma operação muito comum em arquivos é a verificação de que um determinado
registro está nele armazenado. Esta operação é normalmente (como já foi dito no capítulo
7) chamada consulta, pesquisa ou busca e deve ser feita de acordo com o valor da chave
do registro ou de um outro campo que, relativamente, identifique o registro. No exemplo
que estamos discutindo, a consulta pode ser feita pelo campo Mat (de matrícula) ou pelo
campo Nome. Em geral, a consulta se processa com a localização do registro, a
conseqüente exibição do seu conteúdo e o retorno da posição que ele ocupa no arquivo.
A localização do registro pode ser feita, abrindo-o com fopen() e o percorrendo até
que o valor da chave seja encontrado; a exibição do seu conteúdo pode ser feita através
das funções fread() e fprintf(), e a posição que ele ocupa no arquivo é fornecida por uma
das funções fgetpos() e ftell() que possuem os protótipos

149
Capítulo 9 Estruturas e Arquivos

int fgetpos(FILE *p, fpos_t *pos);


long ftell(FILE *p);
onde, na primeira, fpos_t é um tipo de dado pré-definido.
Nas duas funções, p receberá o ponteiro associado ao arquivo onde está se
realizando a pesquisa; a posição do registro pesquisado (dada pela ordem do último byte
ocupado pelo último campo deste registro) é armazenada na variável cujo endereço for
passado para o parâmetro pos de fgetpos() ou será retornado pela função ftell(). Como em
vetores, o primeiro byte ocupado pelo primeiro campo do primeiro registro é o de ordem
zero.
/*Função que verifica se um registro com matricula dada pertence ao arquivo,
retornando sua posição no arquivo*/
int ConsultaRegistro(char s[12], char s1[12])
{
FILE *p;
int Achou = 0;
struct TRegistro r;
fpos_t Byte;
p = fopen(s1, "rb");
fread(&r, sizeof(r), 1, p);
while (!feof(p) && Achou == 0)
if (strcmp(s, r.Mat) == 0)
{
fgetpos(p, &Byte);
Achou = 1;
}
else
fread(&r, sizeof(r), 1, p);
if (Achou == 0)
return (-1);
else
return(Byte);
}
Como no nosso exemplo o tamanho da estrutura é de 48 bytes (4 bytes para o
campo Mat, 40 para o campo Nome e 4 para o campo SalarioBruto), se o registro
pesquisado for o primeiro a função retornará 48, se o registro pesquisado for o segundo,
a função retornará retorna 96, se for o terceiro, a função retornará 144 e assim por diante.
Se o registro não estiver no arquivo, a função retornará –1.
Quando o registro é encontrado, seu conteúdo está armazenado na estrutura r.
Assim, para exibir o conteúdo do registro, basta no comando if (strcmp(s, r.Mat) == 0)
incluir o comando
printf("Matricula: %s \n Nome: %s \n Salario: %f \n", r.Mat, r.Nome, r.SalBruto);

150
Capítulo 9 Estruturas e Arquivos

Para escrever a função acima com a função ftell() bastaria se substituir os comando
fgetpos(p, &Byte) pelo comando Byte = ftell(p).
Considerando que a instrução return() interrompe a execução de uma função, a
função acima poderia prescindir da variável Achou:
int ConsultaRegistro1(char s[12], char s1[12])
{
FILE *p;
struct TRegistro r;
fpos_t Byte;
p = fopen(s1, "rb");
fread(&r, sizeof(r), 1, p);
while (!feof(p))
if (strcmp(s, r.Mat) == 0)
{
fgetpos(p, &Byte);
return(Byte);
}
else
fread(&r, sizeof(r), 1, p);
return (-1);
}
Optamos pela primeira versão pelo fato de que existem linguagens que não
possuem instruções do tipo return() e, nestas linguagens, teríamos de escrever a função
como na versão inicial.
Vale observar que as funções ConsultaRegistro() acima utilizam a pesquisa
seqüencial. Se os registros dos arquivos estiverem ordenados pelo campo Mat
poderíamos ter utilizado a pesquisa binária, que, como estudado no capítulo 7, é bem
mais eficiente.

Alterando o conteúdo de um registro


Às vezes, há necessidade de que os dados de um registro sejam alterados. No
arquivo que estamos utilizando como exemplo isto poderia ocorrer no caso de uma
promoção de um funcionário que implicasse um aumento no seu salário bruto ou no caso
de uma funcionária que alterou o seu nome em função de um casamento.
Uma função para alterar os dados de um registro deve, inicialmente, abrir o arquivo
para leitura e gravação, o que é feito através da função fopen() no modo "rb+". Feito isto,
a função deve receber o valor da chave do registro e com este valor chamar a função
ConsultaRegistro() definida acima para obter a posição do registro pretendido. Tendo esta
posição, deve posicionar o ponteiro de leitura e gravação naquele registro e realizar as
alterações que são desejadas. Para posicionar o ponteiro de leitura e gravação num
determinado registro utiliza-se a função fsetpos() cujo protótipo é

151
Capítulo 9 Estruturas e Arquivos

int fsetpos(FILE *p, fpos_t *pos);.


Numa ativação desta função, o parâmetro p recebe o ponteiro associado ao arquivo
e pos recebe a posição do registro, obtido pela função fgetpos() ou pela função ftell().
No exemplo que estamos discutindo, podemos alterar o campo Nome de um
registro de campo Mat dado utilizando a seguinte função.
/*Função que altera o nome de um registro, dada a matrícula */
void AlteraRegistro(char s[4], char s1[12])
{
char c;
struct TRegistro r;
fpos_t Byte;
int Tam;
FILE *p;
Tam = sizeof(r);
Byte = ConsultaRegistro(s, s1);
if (Byte != -1)
{
Byte = Byte – Tam;
p = fopen(s1, "rb+");
fsetpos(p, &Byte);
fread(&r, Tam, 1, p);
printf("Nome atual: %s \n Altera (S/N)? ", r.Nome);
fflush(stdin);
scanf("%c", &c);
if (toupper(c) == 'S')
{
printf("\Digite o novo nome: \n");
gets(r.Nome);
fsetpos(p, &Byte);
fwrite(&r, Tam, 1, p);
}
}
else
printf("\n Registro nao encontrado \n");
fclose(p);
}
Observe que, ao contrário das funções anteriores, optamos por armazenar o valor
de sizeof(r) na variável Tam, para evitar várias chamadas dessa função. Observe também
que o comando Byte = Byte – Tam posiciona o ponteiro no início do registro que se
pretende alterar.
Outra função que posiciona o ponteiro de leitura e gravação num registro de
posição conhecida é a função fseek() que tem o seguinte protótipo:

152
Capítulo 9 Estruturas e Arquivos

int fseek(FILE *p, long pos, int orig)


Aí, p receberá o fluxo associado ao arquivo e pos indicará a nova posição do
ponteiro, a partir da posição dada pelo valor passado para orig. O sistema possui três
constantes pré-definidas, SEEK_SET, SEEK_CUR e SEEK_END que podem ser
passados para o parâmetro orig. A primeira toma como origem o registro zero do arquivo;
a segunda, o registro apontado pelo ponteiro de leitura e gravação (registro corrente); a
terceira, o final do arquivo. No caso da posição do registro ser obtida por fgetpos() ou por
ftell(), o valor que deve ser passado para orig é SEEK_SET.
Desta forma, para se escrever a função AlteraRegistro() escrita acima utilizando-se
a função fseek() basta substituir os comandos fsetpos(p, &Byte) pelo comando fseek(p,
Byte, SEEK_SET).
A constante SEEK_END, a função fseek() e a função ftell() permitem determinar o
tamanho, em bytes, de um arquivo. Basta posicionar o ponteiro de leitura e gravação no
final do arquivo através de fseek(p, 0, SEEK_END) e obter a posição do ponteiro através
de ftell(p).
int TamanhoArquivo(char *s)
{
FILE *p;
int Tamanho;
p = fopen(s, "rt");
fseek(p, 0, SEEK_END);
Tamanho = ftell(p);
fclose(p);
return(Tamanho);
}
Com esta função é possível se determinar o número de registros de um arquivo.
Basta dividir o tamanho do arquivo pelo tamanho de cada registro:
int NumRegistros(char *s)
{
FILE *p;
struct TRegistro r;
p = fopen(s, "rt");
return(TamanhoArquivo(s)/sizeof(r));
fclose(p);
}
Vale observar que, da mesma forma que a chamada de fseek(p, 0, SEEK_END)
posiciona o ponteiro de leitura e gravação no final do arquivo, fseek(p, 0, SEEK_SET)
posiciona o tal ponteiro no início do arquivo (existe outra forma de apontar o ponteiro de
leitura e gravação para o início do arquivo: rewind(p)).

153
Capítulo 9 Estruturas e Arquivos

Incluindo novos registros num arquivo


A inclusão de novos registros em um arquivo é feita de forma bastante simples,
pois a função fopen() ativada no modo "ab+" abre um arquivo e permite que novos
registros sejam nele gravados. Naturalmente, a inclusão de um novo registro deve ser
precedida da verificação de que o tal registro já está armazenado no arquivo, o que
impediria uma nova inclusão. Temos a seguinte sugestão para atingir o objetivo aqui
proposto:
/* Função que inclui um novo registro num arquivo */
void IncluiRegistro(struct TRegistro r, char s[12])
{
char c;
long Byte;
int Tam;
FILE *p;
Tam = sizeof(r);
Byte = ConsultaRegistro(r.Mat, s);
if (Byte == -1)
{
p = fopen(s, "ab+");
fwrite(&r, Tam, 1, p);
}
else
printf("\n Registro ja cadastrado \n");
fclose(p);
}

Excluindo um registro de um arquivo


Outra operação muito utilizada em arquivos é a exclusão de um registro. No nosso
exemplo, esta operação seria necessária, por exemplo, na ocasião de um pedido de
demissão de um funcionário. Uma possível solução é, após localizar o registro, gravar
todos os outros registros num arquivo auxiliar, Temp, excluir do disco o arquivo original
e renomear o arquivo Temp com o nome do arquivo original.
A maioria dos compiladores C excluem um arquivo através da função remove()
que possui um parâmetro do tipo vetor de caracteres para receber o nome do arquivo a
ser removido. Para renomear um arquivo, os compiladores C possuem a função rename()
que possui dois parâmetros do tipo vetor de caracteres, devendo o primeiro receber o
nome atual do arquivo e o segundo receber o novo nome que se pretende.
Dentro do exemplo que estamos estudando, a função abaixo recebendo o valor do
campo r.Mat e o nome do arquivo, exclui, se a matrícula dada for uma matrícula
cadastrada, o registro correspondente.

154
Capítulo 9 Estruturas e Arquivos

/*Função que exclui um registro de matrícula dada */


void ExcluiRegistro(char s[4], char s1[12])
{
struct TRegistro r;
char c;
long Byte;
int Tam, Reg;
FILE *p, *t;
Tam = sizeof(r);
Byte = ConsultaRegistro(s, s1);
if (Byte != -1)
{
p = fopen(s1, "rb");
Byte = Byte – Tam;
fsetpos(p, &Byte);
fread(&r, Tam, 1, p);
printf("Matricula: %s \b Nome: %s \n", r.Mat, r.Nome);
printf("Exclui este registro (S/N)? ");
fflush(stdin);
scanf("%c", &c);
if (toupper(c) == 'S')
{
t = fopen("Temp", "wb");
rewind(p); /*Primeiro registro do arquivo*/
Reg = 0;
fread(&r, Tam, 1, p);
while (!feof(p))
{
if (Reg != Byte)
fwrite(&r, Tam, 1, t);
Reg = Reg + Tam;
fread(&r, Tam, 1, p);
}
fclose(p);
fclose(t);
remove(s1);
rename("Temp", s1);
}
}
else
printf("\n Registro nao encontrado \n");
}

155
Capítulo 9 Estruturas e Arquivos

9.4 Arquivo texto


Outra forma de arquivo que os compiladores C manipulam são os chamados
arquivos textos. Nestes arquivos, também criados através da função fopen(), agora no
modo "wt", cadeias de caracteres podem ser armazenadas byte a byte, através do código
ASCII de cada caractere.
A gravação de texto em um arquivo texto pode ser feita através da função fprintf()
que, além dos parâmetros da função printf(), exige um primeiro parâmetro do tipo fluxo
que indicará o arquivo onde o texto será gravado. Por exemplo, o programa abaixo cria
um arquivo Teste.txt e grava nele a frase Isto é um teste.
#include <stdio.h>
main()
{
FILE *PontArquivo;
PontArquivo = fopen("Teste.txt", "wt");
fprintf(PontArquivo, "Isto é um teste");
fclose(PontArquivo);
}
Após a execução deste programa, qualquer processador de texto que edite textos
em ASCII pode abrir o arquivo Teste.txt, sendo o seu conteúdo absolutamente legível.
Na verdade, é possível gravar conteúdos de variáveis e resultados de
processamentos em arquivos utilizando-se a função fprintf(). Nestes casos, são utilizados
os códigos de especificação de formato da função printf(). Por exemplo, para se
armazenar no arquivo Teste.txt uma tabela de raízes quadradas dos cem primeiros inteiros
positivos basta se executar o seguinte programa:
#include <stdio.h>
#include <math.h>
main()
{
int i;
float r;
FILE *PontArquivo;
PontArquivo = fopen("Teste.txt", "wt");
for (i = 1; i <= 100; i++)
{
r = sqrt(i);
fprintf(PontArquivo, "%d %f \n", i, r);
}
fclose(PontArquivo);
}
A função printf() é, de fato, um caso particular da função fprintf() cujo primeiro
parâmetro é o fluxo pré-definido stdout. O fluxo stdout, que é omitido em printf(), aponta
156
Capítulo 9 Estruturas e Arquivos

para um arquivo que faz referência ao dispositivo padrão de saída, em geral a tela do
monitor. Ou seja, printf() "grava" a saída na tela, enquanto fprintf() grava a saída no
arquivo associado ao fluxo passado para ela. Por exemplo, os comandos printf("Estou
aprendendo a programar em C") e fprintf(stdout, "Estou aprendendo a programar em
C") executam ações idênticas: exibem na tela a frase Estou aprendendo a programar em
C.
Um outro fluxo pré-definido é stdprn que aponta para um arquivo que gerencia a
relação entre o sistema e a impressora conectada ao computador. Por exemplo, o
programa
#include <stdio.h>
main()
{
int i;
for (i = 0; i < 10; i++)
fprintf(stdprn, "Estou aprendendo a programar em C \n");
}
imprime, através da impressora conectada ao computador, dez vezes a frase Estou
aprendendo a programar em C, uma vez em cada linha.
Um terceiro fluxo pré-definido é stdin que aponta para um arquivo que administra
a relação do sistema com o dispositivo de entrada padrão, em geral o teclado. Isto explica
a chamada de fflush(stdin) comentada no capítulo 5.

Exibindo um arquivo texto


A biblioteca da linguagem C dispõe de uma função capaz de "ler" uma linha de um
arquivo texto, armazenando-a num ponteiro de caracteres. Trata-se da função de protótipo
char *fgets(char *s, int n, FILE *p);
que lê uma quantidade x de caracteres do arquivo associado a p e os armazena em s, como
uma string. A quantidade de caracteres x é inferior ou igual a n, sendo inferior quando
uma marca de fim de linha é atingida, ou seja, quando o caractere indicado por \n é
encontrado. Quando o fim de arquivo é alcançado, a função retorna NULL.
Com fgets() é possível exibir o conteúdo de um arquivo texto com a seguinte
função:

#include <stdio.h>
void ExibeArquivoTexto(char *s)
{
FILE *p;
char *Linha, *Fim;
p = fopen(s, "rt");
Fim = fgets(Linha, 80, p);
while (Fim != NULL)
157
Capítulo 9 Estruturas e Arquivos

{
printf("%s", Linha);
Fim = fgets(Linha, 80, p);
}
}
Se este arquivo adicionado da função
main()
{
char *NomeArq;
puts("Digite o nome do arquivo");
scanf("%s", NomeArq);
ExibeArquivoTexto(NomeArq);
}
for gravado com o nome ExibText.c, sua execução para a entrada exibtext.c exibe o seu
próprio conteúdo.

Utilizando um arquivo texto como entrada de dados


É possível utilizar um arquivo texto para obter a entrada de um programa.
Imaginemos que o arquivo texto cardapio.txt contivesse o cardápio da lanchonete referida
num exemplo do capítulo 4. Ou seja, suponhamos que o conteúdo do arquivo cardápio.txt
fosse o seguinte:
101 Refrigerante 1.20
102 Suco 1.00
103 Sanduíche 2.50
104 Salgado 1.00
105 Torta 2.00
O programa abaixo lê este arquivo e armazena num vetor de estruturas permitindo
o gerenciamento dos pedidos dos clientes.
#include <math.h>
#include <conio.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
int Cod;
char Prod[30];
float Preco;
}TItem;
typedef struct
{

158
Capítulo 9 Estruturas e Arquivos

int Cod;
int Quant;
}TPedido;
TItem Cardapio[100];
TPedido Pedido[100];
/*Função para “separar” os componentes do cardápio*/
void ProcessaItem(char *s, int *Codigo, char *Produto, float *Pr)
{
char a[10];
int c = strlen(s);
*Codigo = atoi(s);
strncpy(Produto, s + 4, c-9);
strncpy(a, s + c - 5, 4);
*Pr = atof(a);
}
/*Funcao para ler o cardápio do arquivo texto*/
int LerCardapio(char *s)
{
FILE *p;
int i = 0;
char *Item, *FimDeLinha;
p = fopen(s, "rt");
FimDeLinha = fgets(Item, 80, p);
while (FimDeLinha != NULL)
{
ProcessaItem(Item, &Cardapio[i].Cod, Cardapio[i].Prod,
&Cardapio[i].Preco);
i++;
FimDeLinha = fgets(Item, 80, p);
}
return i;
}
/*Função para exibir o cardápio*/
void ExibeCardapio(TItem v[100], int t)
{
int i;
printf("COD ESPECIFICACAO PRECO\n");
for (i = 0; i < t; i++)
printf("%d %-20s %.2f\n", v[i].Cod, v[i].Prod, v[i].Preco);
}

/* Funcao para exibir pedido*/


void ExibePedidos(int n)
{

159
Capítulo 9 Estruturas e Arquivos

int i, Ind;
printf("COD ESPECIFICACAO QUANT PRECO\n");
for(i=0; i<n; i++)
{
Ind = Pedido[i].Cod - 101;
printf("%3d %-30s %5d R$ %.2f\n",Pedido[i].Cod, Cardapio[Ind].Prod,
Pedido[i].Quant, Cardapio[Ind].Preco*Pedido[i].Quant);
}
}

void main()
{
int i, NumItens;
float Total;
clrscr();
NumItens = LerCardapio("card.txt");
ExibeCardapio(Cardapio, NumItens);
Total = 0;
i = 0;
puts("Digite o codigo do produto desejado (0 para encerrar):");
scanf("%d",&Pedido[i].Cod);
while(Pedido[i].Cod!=0)
{
if( (Pedido[i].Cod>=100) &&(Pedido[i].Cod<=106) )
{
puts("Digite a quantidade:");
scanf("%d",&Pedido[i].Quant);
Total = Total + Pedido[i].Quant * Cardapio[Pedido[i].Cod -
101].Preco;
i++;
}
else
{
puts("Erro! O código informado não está cadastrado!");
getch();
}
puts("Digite o codigo do produto desejado (0 para encerrar):");
scanf("%d",&Pedido[i].Cod);
}
puts("\nTotal dos pedidos:");
ExibePedidos(i);
printf("\nValor total dos pedidos: R$ %.2f", Total);
getch();
}

160
Capítulo 9 Estruturas e Arquivos

9.5 Exercícios propostos


1. Escreva uma função main() que, através de um menu de opções, utilize as
funções estudadas neste capítulo e que, portanto, seja capaz de
a) criar e gravar dados num arquivo de registros;
b) exibir o conteúdo de um arquivo;
c) alterar dados de um registro de um arquivo;
d) incluir novos registros em um arquivo;
e) excluir um registro de um arquivo.
2. Escreva uma função que reúna dois arquivos de registros de mesma estrutura em
um terceiro arquivo.
3. Escreva um programa que, dado um arquivo cujos registros possuem os campos
Nome (do tipo vetor de strings) e Salario (do tipo float), gere um arquivo dos registros
cujo campo Salario é maior que 5.000,00.
4. Escreva uma função que inclua um registro dado num arquivo de registros
ordenado por um campo Mat de modo que o arquivo se mantenha ordenado, sem utilizar
um arquivo auxiliar.
5. Escreva um programa que, dados dois arquivos ordenados por um campo Mat,
gere um terceiro arquivo também ordenado.
6. Escreva um programa que dados dois arquivos cujos registros têm os campos
char Cpf[12] e char Nome[40] gere um arquivo contendo os registros que pertencem aos
dois arquivos.
7. Escreva uma função que troque as posições de dois registros de um arquivo.
8. Escreva uma função que ordene um arquivo, cujos registros têm os campos char
Cpf[12] e char Nome[40], em relação ao campo Cpf.
9. Escreva uma função que exclua os comentários de um programa da linguagem
C.

161
10 Alocação Dinâmica de Memória
10.1 O que é alocação dinâmica
Até agora, os programas utilizavam a memória do computador estaticamente: todas
as posições de memória eram reservadas para as variáveis no início da execução do
programa ou da função e, mesmo que não estivessem sendo mais utilizadas, continuavam
reservadas para as mesmas variáveis até a conclusão da execução do programa ou da
função. Um vetor global do tipo float com dez mil componentes, por exemplo, “ocupará”
quarenta mil bytes de memória durante toda a execução do programa. Naturalmente, isto
pode, em grandes programas, sobrecarregar ou, até mesmo, esgotar a memória disponível.
No primeiro caso, há uma degradação na eficiência do programa; no segundo caso a
execução do programa pode ser inviabilizada.
Os compiladores C permitem a alocação dinâmica da memória de tal modo que
posições de memória sejam reservadas para variáveis no instante em que sejam
necessárias e sejam liberadas (as posições de memória) para o sistema nos instantes em
que não estejam sendo utilizadas.
A alocação dinâmica de memória pode ser feita através das funções malloc(),
calloc() e realloc(), cujos protótipos se encontram no arquivo alloc.h e são os seguintes
void *malloc(size_t Tam);
void *calloc(size_t NumItens, size_t Tam);
void *realloc(void *Bloco, size_t Tam);
Aí, size_t é um tipo de dado pré-definido, definido também no arquivo alloc.h,
Tam é o número de bytes que se pretende alocar dinamicamente, NumItens é a quantidade
de itens de Tam bytes que se pretende alocar e Bloco é um ponteiro que contém o
endereço da variável cuja memória se pretende expandir em Tam bytes.
A função malloc() retorna um ponteiro para um bloco de Tam bytes até então
disponível, a função calloc() retorna um ponteiro para um espaço de memória até então
disponível capaz de armazenar NumItens objetos, cada um deles com Tam bytes e a
função realloc() retorna um ponteiro para um bloco de memória com quantidade de bytes
igual à soma algébrica da quantidade de bytes apontada por Bloco e Tam, podendo Tam
ser negativo. Caso a quantidade de bytes pretendida não esteja disponível, as funções
acima retornam NULL.
Como os ponteiros retornados são ambos do tipo void, eles devem ser moldados
para poderem receber endereços de qualquer tipo de variável. Por exemplo, o programa
#include <stdio.h>
#include <alloc.h>
main()
{
int *v, t;
v = (int *)malloc(80);
if (v == NULL)
Capítulo 10 Alocação Dinâmica da Memória

printf("Memoria nao disponivel");


else
{
for (t = 0; t <= 40; t = t + 1)
v[t] = t*t;
for (t = 0; t <= 40; t = t + 1)
printf("%d ", v[t]);
free(v);
}
}
armazena os quadrados dos quarenta primeiros números inteiros num vetor criado
dinamicamente. A função free() libera para o sistema a quantidade de memória alocada
para o seu argumento por uma das funções malloc(), calloc() ou realloc().
Naturalmente, o leitor pode estar pensando que o programa acima não teria muita
vantagem em relação ao programa abaixo, onde o vetor v é criado estaticamente,
#include <stdio.h>
main()
{
int v[40], t;
for (t = 0; v <= 40; t = t + 1)
v[t] = t * t;
for (t = 0; v <= 40; t = t + 1)
printf("%d ", v[t])
}
De fato, os dois programas durante suas execuções utilizam oitenta bytes de
memória. Haverá diferença se estes programas fizerem parte de um programa maior.
Neste caso, o primeiro utiliza oitenta bytes apenas até a execução do comando free(),
enquanto que o segundo utiliza os oitenta bytes durante toda execução do programa.
Naturalmente, também, o leitor pode estar se perguntando qual a vantagem do
primeiro dos programas acima em relação ao programa
#include <stdio.h>
main()
{
int *v, t;
for (t = 0; v <= 40; t = t + 1)
v[t] = t * t;
for (t = 0; v <= 40; t = t + 1)
printf("%d ", v[t])
}
no qual o "tamanho" de v não foi fixado e portanto não há "desperdício" de memória. O
que ocorre é que no primeiro se não houver memória disponível, o vetor v não é "criado",
cabendo ao programador ajustar o programa para esta hipótese.

163
Capítulo 10 Alocação Dinâmica da Memória

10.2 Armazenando dinamicamente um polinômio


A função realloc() aumenta a eficiência de utilização de memória pelo fato de que
a quantidade de memória alocada para um vetor pode crescer à medida da necessidade.
Para exemplificar, imagine a função abaixo que "lê um polinômio" p(x). Como se sabe,
um polinômio p(x) = a0xn + a1xn-1 + ... + an-1x + an é identificado pelo seu grau e pelos
valores de seus coeficientes. Se se pretende que o grau não seja dado de entrada, a
questão é a mesma que ocorre quando se trabalha com uma relação de números: não se
sabe a quantidade deles e, portanto, não se pode precisar o tamanho do vetor necessário.
Uma solução é utilizar a função malloc() para inicializar um ponteiro e, à medida que os
coeficientes são digitados, utilizar a função realloc() para expandir a memória necessária.
#include <stdio.h>
#include <alloc.h>
#include <string.h>
#include <stdlib.h>
int LePolinomio(int *p)
{
char *Coef;
int i;
printf("Digite os coeficientes ('fim' para encerrar)\n");
i = 1;
do
{
p = (int *)realloc(p, 2*i);
gets(Coef);
if (strcmp(Coef, "fim") != 0)
{
p[i - 1] = atof(Coef);
i = i + 1;
}
}
while (strcmp(Coef, "fim") != 0);
free(p);
return (i - 1);
}
A "inicialização de p" deve ocorrer antes da chamada da função. Por exemplo, na
função main(), através do ponteiro declarado nesta função e que será passado para a
função LePolinomio().
main()
{
int *Poli;

164
Capítulo 10 Alocação Dinâmica da Memória

int Grau;
Poli = (int *)malloc(2);
Grau = LePolinomio(Poli);
...
}
Observe que optamos por dar entrada nos coeficientes como strings. Esta opção foi
feita para que pudéssemos usar "fim" como flag, já que a utilização de qualquer número
com esta finalidade fica complicada já que qualquer número pode ser um coeficiente de
um polinômio.

10.3 Listas
Para mais um exemplo de alocação dinâmica, apresentaremos um tipo de dado
chamado lista simplesmente encadeada. Este tipo de dado pode ser definido como uma
seqüência de elementos ligados através de ponteiros, com um número máximo de
elementos não fixado a priori. Usualmente, cada elemento da lista é uma estrutura, com
um campo contendo um ponteiro e os demais campos contendo os dados que o programa
vai manipular. Associa-se um ponteiro ao primeiro elemento e o campo do tipo ponteiro
do último elemento da lista tem valor NULL. O ponteiro que aponta para o primeiro
elemento da lista indica o seu início e o valor do ponteiro da última estrutura da lista ser
NULL indicará o seu final.
Para as funções que vamos apresentar (criação de uma lista de inteiros, exibição e
remoção de um elemento desta lista) necessitamos definir a seguinte estrutura:
struct TElemento
{
int Valor;
struct TElemento *p;
};
Observe que na definição de uma estrutura é possível se definir um campo de tipo
idêntico ao da estrutura. Este campo definirá um ponteiro que aponta para o elemento
seguinte da lista e o campo Valor armazenará o inteiro.
Necessitamos também de duas variáveis globais:
struct TElemento *Inicio, *Prox;
a variável Inicio para apontar para o inicio da lista e Prox para apontar para o próximo
elemento. É necessário observar que o ponteiro Inicio aponta para o último elemento que
foi inserido na lista, enquanto que o campo *p do primeiro elemento da lista é igual a
NULL.
Para criar a lista fazemos Inicio receber NULL e, dentro de uma estrutura de
repetição, alocamos memória para o ponteiro Prox, damos entrada em Prox.Valor e
fazemos o campo p de Prox receber Inicio e Inicio receber Prox para que este ponteiro
aponte sempre para o último elemento a dar entrada. Temos a seguinte função:

165
Capítulo 10 Alocação Dinâmica da Memória

void CriaLista()
{
Inicio = NULL;
printf("Digite os numeros (-1 para encerrar)\n");
do
{
Prox = (struct TElemento *)malloc(5);
scanf("%d",&(*Prox).Valor);
if ((*Prox).Valor != -1)
{
(*Prox).p = Inicio;
Inicio = Prox;
}
}
while ((*Prox).Valor != -1);
}
Para exibir a lista (ou realizar nela qualquer processamento), basta percorrê-la
desde seu início (Prox = Inicio) até o seu final ((*Prox).p = NULL).
void ExibeLista()
{
Prox = Inicio;
while (Prox != NULL)
{
printf("%d ", (*Prox).Valor);
Prox = (*Prox).p;
}
printf("\n");
}
Para deletar um elemento da lista é necessário que, quando ele for localizado, se
armazene o ponteiro do elemento anterior a ele e o ponteiro que aponta para ele. Se o
elemento a ser deletado for o primeiro, basta fazer Inicio apontar para o segundo
elemento; se o elemento a ser excluído for outro elemento, basta fazer o ponteiro do
elemento anterior para o elemento seguinte e devolver ao sistema a posição de memória
ocupada pelo elemento a ser excluído.
void Deleta(int n)
{
struct TElemento *Ant;
Prox = Inicio;
Ant = Inicio;
while ((Prox != NULL) && ((*Prox).Valor != n))
{
Ant = Prox;

166
Capítulo 10 Alocação Dinâmica da Memória

Prox = (*Prox).p;
}
if (Prox != NULL)
{
if ((Ant == Inicio) && (Prox == Inicio))
Inicio = (*Prox).p;
else
(*Ant).p = (*Prox).p;
free(Prox);
}
else
printf("Elemento nao esta lista \n");
}

10.4 Exercícios propostos


1. Escreva uma função que calcule a média de uma relação de números armazenada
numa lista criada pela função CriaLista() acima.
2. Escreva uma função que insira um elemento numa lista ordenada de modo que a
lista permaneça ordenada.

167
Respostas dos exercícios propostos
Capítulo 1
1. Naturalmente, na primeira travessia um índio levaria um branco até a outra
margem e voltaria sozinho. A questão é a segunda: não poderia atravessar um índio e um
branco, pois ao chegar na outra margem haveria dois brancos e um índio; não poderiam
atravessar dois índio, pois o terceiro ficaria com dois brancos. A solução é atravessar dois
brancos e um deles retornar. A terceira travessia só pode ser feita por dois índios, pois já
existem dois brancos na outra margem. A questão é o retorno. A única possibilidade é
retornar um índio e um branco! Temos então o seguinte algoritmo:
1. Atravessem um índio e um branco.
2. Retorne o índio.
3. Atravessem dois brancos.
4. Retorne um branco.
5. Atravessem dois índios.
6. Retornem um índio e um branco.
7. Atravessem dois índios.
8. Retorne um branco.
9. Atravessem dois brancos.
10. Retorne um branco.
11. Atravessem dois brancos.
2. Indicando por 1, 2, 3, 4, ... os discos na ordem crescente dos seus diâmetros,
temos para o caso n = 2:
1. Disco 1 da origem para auxiliar.
2. Disco 2 da origem para o destino.
3. Disco 1 da auxiliar para o destino.
Para o caso n = 3, basta observar que é necessário apenas transportar os dois discos
1 e 2 da origem para auxiliar (que é o caso anterior), transportar o disco três da origem
para o destino e os discos 1 e 2 da torre auxiliar para o destino (que é, novamente, o caso
anterior).
1. Disco 1 da origem para destino.
2. Disco 2 da origem para auxiliar.
3. Disco 1 do destino para auxilia.
4. Disco 3 da origem para destino.
5. Disco 1 da auxiliar para origem.
6. Disco 2 da auxiliar para o destino.
7. Disco 1 da origem para o destino.
3. Indiquemos por P(m, n) = 0 se m e n têm o mesmo peso e P(m, n) > 0 se a esfera
m é mais pesada que a esfera n. Temos então a seguinte proposta:
Respostas dos exercícios propostos

1. Pese as esferas 1 e 2.
2. Se P(1, 2) = 0, pese as esferas 1 e 3.
2.1 Se P(1, 3) > 0 então forneça como resposta: a esfera 3 tem peso menor
que as esferas 1 e 2.
2.2 Se P(3, 1) > 0 então forneça como resposta: a esfera 3 tem peso maior
que as esferas 1 e 2.
3. Se P(1, 2) > 0, pese as esferas 1 e 3.
3.1 Se P(1, 3) = 0 então forneça como resposta: a esfera 2 tem peso menor
que as esferas 1 e 3.
3.2 Se P(1, 3) > 0 então forneça como resposta: a esfera 1 tem peso maior
que as esferas 2 e 3.
3.3 Se P(3, 1) > 0 então forneça como resposta: a esfera 1 tem peso menor
que as esferas 2 e 3.
4. Se P(2, 1) > 0, pese as esferas 2 e 3.
4.1 Se P(2, 3) = 0 então forneça como resposta: a esfera 1 tem peso menor
que as esferas 2 e 3.
4.2 Se P(2, 3) > 0 então forneça como resposta: a esfera 2 tem peso maior
que as esferas 1 e 3.
4.3 Se P(3, 2) > 0 então forneça como resposta: a esfera 2 tem peso menor
que as esferas 1 e 3.
4. Para calcular o produto, utilizamos uma variável P que assume inicialmente o
primeiro valor da relação e, para cada novo elemento, vai tendo o seu valor substituído
pelo produto do seu valor atual pelo novo elemento. A partir daí, a média geométrica é
raiz n-ésima desse produto.
1. Chame de A o primeiro número dado.
2. Chame de N o número de elementos da relação
3. Faça P = A.
4. Repita N - 1 vezes as instruções 4.1 e 4.2.
4.1. Chame de A o próximo número dado.
4.2. Substitua o valor de P por P x A.
5. Calcule M = Raiz(P, N)
6. Forneça M para o valor da média.
5. Basta observar que os dias da semana, sendo em número de 7, repetem-se em
ciclos de 7 dias. Assim, se 01/01/1900 foi uma segunda-feira, o foram também os dias
08/01/1900, 15/01/1900, 22/01/1900, 29/01/1900, 05/02/1900 e assim sucessivamente.
Basta então determinar o número de dias decorridos entre a data dada e o dia 01/01/1900
e calcular o resto da divisão por 7.
1. Determine o número n de dias entre a data dada e 01/01/1900.
2. Calcule o resto r da divisão de n por 7
3. Se r = 1 forneça como resposta segunda-feira.
4. Se r = 2 forneça como resposta terça-feira.
5. Se r = 3 forneça como resposta quarta-feira.

169
Respostas dos exercícios propostos

6. Se r = 4 forneça como resposta quinta-feira.


7. Se r = 5 forneça como resposta sexta-feira.
8. Se r = 6 forneça como resposta sábado.
9. Se r = 0 forneça como resposta domingo.
6. Indicando por A(x, y) a travessia dos integrantes x e y e por V(x) a volta do
integrante x, teríamos:
1. A(baterista, baixista).
2. V(baterista).
3. A(guitarrista, vocal).
4. V(baixista)
5. A(baixista, baterista)

Capítulo 2
1.a) ((-(-9) + sqrt((-9)*(-9) - 4*3*6))/(2*3)) = ((9 + sqrt(81 – 72))/6) = ((9 +
+ sqrt(9))/6) = ((9 + 3)/6) = (12/6) = 2.
1.b) ((pow(3, 2) == 9) && (acos(0) == 0)) || (4 % 8 == 3) = (1 && 0) || 0 = 0 || 0 =
= 0.
2.a) /*Programa que converte uma temperatura dada em graus Fahrenheit para
graus Celsius */
#include <stdio.h>
main()
{
float Fahrenheit, Celsius;
printf("Digite a temperatura em Fahrenheit");
scanf("%f", &Fahrenheit);
Celsius = 5 * (Fahrenheit - 32)/9;
printf("A temperatura de %.2f Fahrenheit corresponde a %.2f Celsius ",
Fahrenheit, Celsius);
}
2.b) /*Programa que inverte um numero com tres algarismos*/
#include <stdio.h>
main()
{
int Num, Invert, Unid, Dez, Cent;
printf("Digite o numero a inverter ");
scanf("%d", &Num);
Unid = Num % 10;
Dez = (Num % 100)/10;
Cent = Num/100;
Invert = Unid*100 + Dez*10 + Cent;

170
Respostas dos exercícios propostos

printf("Invertendo-se %d obtemos %d \n", Num, Invert);


}
2.c /*Programa para somar duas frações*/
#include <stdio.h>
main()
{
int Num1, Den1, Num2, Den2, Num, Den;
printf("Digite os termos das fracoes \n");
scanf("%d %d %d %d", &Num1, &Den1, &Num2, &Den2);
Num = Num1*Den2 + Num2*Den1;
Den = Den1*Den2;
printf("(%d/%d) + (%d/%d) = (%d/%d)", Num1, Den1, Num2, Den2, Num, Den);
}
2.d) /* Programa que determina o maior multiplo de um inteiro menor ou igual a
outro inteiro*/
#include <stdio.h>
main()
{
int Num, Divisor, MaiorMultiplo;
printf("Digite o inteiro do qual ser o número procurado deve ser multiplo \n");
scanf("%d", &Divisor);
printf("Digite o inteiro que deve ser maior do o que múltiplo procurado\n");
scanf("%d", &Num);
MaiorMultiplo = Num - Num % Divisor;
printf(" \n O maior multiplo de %d menor do que ou igual %d e' %d \n", Divisor,
Num, MaiorMultiplo);
}
2.e) /*Programa para calcular o perimetro de um poligono regular de n lados
inscrito numa circunferencia de raio r*/
#include <stdio.h>
#include <math.h>
main()
{
const float Pi = 3.1416;
float r, Perim, Seno;
int n;
printf("Digite o numero de lados do poligono e o raio \n");
scanf("%d %f", &n, &r);
Perim = 2*n*r*sin(Pi/n);
printf("Perimetro do poligono regular de %d lados inscrito numa circunferencia de
raio %.2f: %.2f \n", n, r, Perim);
}

171
Respostas dos exercícios propostos

3. /*Programa para permutar os conteudos de duas variaveis sem utilizar uma


variavel auxiliar*/
#include <stdio.h>
main()
{
float x, y;
printf("Digite os dois numeros \n");
scanf("%f %f", &x, &y);
printf("Conteudos antes da troca x = %0.1f, y = %0.1f \n", x, y);
x = x + y;
y = x - y;
x = x - y;
printf("Conteudos depois da troca x = %0.2f, y = %0.2f \n", x, y);
}
4. /*Programa para determinar entrada mais duas parcelas inteiras de uma
compra*/
#include <stdio.h>
main()
{
float Compra, Entrada;
int Parcelas;
printf("Digite o valor da compra: ");
scanf("%f", &Compra);
Parcelas = Compra/3;
Entrada = Compra - 2*Parcelas;
printf("Entrada = %0.2f, Parcelas %d.00 \n", Entrada, Parcelas);
}
5. /*Programa para converter um tempo dado em segundos para horas, minutos e
segundos*/
#include <stdio.h>
main()
{
int Tempo, Horas, Minutos, Segundos;
printf("Digite o valor do intervalo de tempo (em segundos): ");
scanf("%d", &Tempo);
Horas = Tempo/3600;
Minutos = (Tempo % 3600)/60;
Segundos = Tempo % 60;
printf("O intervalo de tempo de %d segundos equivale a %d h %d min %d s \n",
Tempo, Horas, Minutos, Segundos);
}
6. /*Programa para converter um tempo dado em minutos para horas, minutos e
segundos*/

172
Respostas dos exercícios propostos

#include <stdio.h>
main()
{
float Tempo, Segundos;
int Horas, Minutos;
printf("Digite o valor do intervalo de tempo (em minutos): ");
scanf("%f", &Tempo);
Horas = Tempo/60;
Minutos = Tempo - 60*Horas;
Segundos = (Tempo - 60*Horas - Minutos)*60;
printf("O intervalo de tempo de %.2f min equivale a %d h %d min %.2f s \n",
Tempo, Horas, Minutos, Segundos);
}
7. /*Programa para discriminar as notas de um saque num caixa eletrônico*/
#include <stdio.h>
main()
{
int s, Saque, Notas100, Notas50, Notas10, Notas5, Notas1;
printf("Digite o valor do saque: ");
scanf("%d", &Saque);
Notas100 = Saque/100;
s = Saque % 100;
Notas50 = s/50;
s = s % 50;
Notas10 = s/10;
s = s % 10;
Notas5 = s/5;
Notas1 = s % 5;
printf("Valor do saque: %d \n Distribuicao da notas: \n %d de 100 \n %d notas de
50 \n %d notas de 10 \n %d notas de 5 \n %d notas de 1", Saque, Notas100, Notas50,
Notas10, Notas5, Notas1);
}
8. /*Programa para determinar a prestação de um financiamento*/
#include <math.h>
main()
{
float Valor, i, Coef, Pot, Prestacao;
int n;
printf("Digite o valor do financiamento: ");
scanf("%f", &Valor);
printf("Digite o valor da taxa: ");
scanf("%f", &i);
i = i/100;

173
Respostas dos exercícios propostos

puts("Digite o número de prestações: ");


scanf("%d", &n);
Pot = pow(1 + i, n);
Coef = (Pot - 1)/(i*Pot);
Prestacao = Valor/Coef;
printf("Financiamento: %.2f\n Taxa: %.2f\n Número de prestações: %d\n Valor da
prestação: %.2f", Valor, 100*i, n, Prestacao);
}

Capítulo 3
1. /*Programa que determina o intervalo de tempo decorrido entre dois instantes*/
main()
{
int h1, min1, s1, h2, min2, s2, h, min, s;
puts("Digite o instante inicial (horas, minutos e segundos)");
scanf("%d %d %d", &h1, &min1, &s1);
puts("Digite o instante final (horas, minutos e segundos)");
scanf("%d %d %d", &h2, &min2, &s2);
h = h2 -h1;
min = min2 - min1;
s = s2 - s1;
if ((h < 0) || ((h == 0) && (min < 0)) || ((h == 0) && (min == 0) && (s < 0)))
puts("Dados invalidos! O segundo instante é anterior ao primeiro");
else
{
if (s < 0)
{
min = min - 1;
s = s + 60;
}
if (min < 0)
{
h = h - 1;
min = min + 60;
}
printf( "Entre os instantes %d h %d min %d s e %d h %d min %d s
passaram-se %d h %d min %d s", h1, min1, s1, h2, min2, s2, h, min, s);
}
}

2. /*Programa para arredondar um numero dado*/

174
Respostas dos exercícios propostos

#include <stdio.h>
main()
{
float Num, ParteFrac;
int Arredondamento;
printf("Digite um numero ");
scanf("%f", &Num);
Arredondamento = Num;
ParteFrac = Num - Arredondamento;
if (ParteFrac >= 0.5)
Arredondamento = Arredondamento + 1;
printf("Aredondando %f obtemos %d ", Num, Arredondamento);
}
3. /*Programa para verificar se um inteiro dado e um quadrado perfeito*/
#include <stdio.h>
#include <math.h>
main()
{
float RaizReal, ParteFrac;
int Num, RaizInteira;
printf("Digite um numero ");
scanf("%d", &Num);
RaizReal = sqrt(Num);
RaizInteira = RaizReal;
ParteFrac = RaizReal - RaizInteira;
if (ParteFrac == 0)
printf("%d eh quadrado perfeito de raiz igual a %d \n", Num, RaizInteira);
else
printf("%d nao eh quadrado perfeito \n ", Num);
}
4. /*Progrma para determinar o maior de tres numeros dados*/
#include <stdio.h>
main()
{
float a, b, c, Maior;
printf("Digite tres numeros");
scanf("%f %f %f", &a, &b, &c);
Maior = a;
if ((b > a) || (c > a))
if (b > c)
Maior = b;
else
Maior = c;

175
Respostas dos exercícios propostos

printf("O maior dos numeros %.2f , %.2f e %.2f e' %.2f \n", a, b, c, Maior);
}
5. /*Programa para classificar um triangulo quanto ao comprimento dos lados*/
#include <stdio.h>
main()
{
float a, b, c;
printf("Digite os comprimentos dos lados: \n");
scanf("%f %f %f", &a, &b, &c);
if ((a < b + c) && (b < a + c) && (c < a +b))
if ((a == b) && (b == c))
printf("O triangulo de lados %0.2f, %0.2f e %0.2f e equilatero", a, b, c);
else
if ((a == b) || (a == c) || (b == c))
printf("O triangulo de lados %0.2f, %0.2f e %0.2f e isosceles", a, b, c);
else
printf("O triangulo de lados %0.2f, %0.2f e %0.2f e escaleno", a, b, c);
else
printf("Os numeros dados nao sao comprimentos dos lados de um triangulo");
}
6. /*Programa para verificar se um triangulo de lados dados e retangulo*/
#include <stdio.h>
main()
{
float a, b, c, Hip, Cat1, Cat2;
printf("Digite os comprimentos dos lados: \n");
scanf("%f %f %f", &a, &b, &c);
Cat1 = b;
Cat2 = c;
if ((a < b + c) && (b < a + c) && (c < a +b))
{
if ((a > b) && (a > c))
Hip = a;
else
if (b > c)
{
Hip = b;
Cat1 = a;
}
else
{
Hip = c;
Cat2 = a;

176
Respostas dos exercícios propostos

}
if (Hip*Hip == Cat1*Cat1 + Cat2*Cat2)
printf("Triangulo retangulo de hipotenusa %0.2f e catetos %0.2f e
%0.2f", Hip, Cat1, Cat2);
else
printf("O triangulo de lados %0.2f, %0.2f e %0.2f nao e retangulo", a,
b, c);
}
else
printf("Os numeros dados nao sao comprimentos dos lados de um
triangulo");
}
7. /*Programa para determinar as raizes de uma equacao do segundo grau*/
#include <stdio.h>
#include <math.h>
main()
{
float a, b, c, Imag, Real, Delta, x1, x2;
printf("Digite os coeficientes \n");
scanf("%f %f %f", &a, &b, &c);
if (a != 0)
{
Real = -b/(2*a);
Delta = b*b - 4*a*c;
if (Delta >= 0)
{
Imag = sqrt(Delta)/(2*a);
x1 = Real + Imag;
x2 = Real - Imag;
printf("As raizes da equacao de coeficientes %.2f, %.2f e %.2f sao
%.2f e %.2f ", a, b, c, x1, x2);
}
else
{
Imag = sqrt(-Delta)/(2*a);
printf("As raizes da equacao de coeficientes %.2f , %.2f e %.2f sao
%.2f + %.2fi e %.2f - %.2fi ", a, b, c, Real, Imag, Real, Imag);
}
}
else
printf("A equacao nao e do segundo grau");
}
8. /* Programa para determinar a idade de uma pessoa em anos, meses e dias*/
#include <stdio.h>
177
Respostas dos exercícios propostos

main()
{
int DiaNasc, MesNasc, AnoNasc, DiaAtual, MesAtual, AnoAtual, Anos, Meses,
Dias;
printf("Digite a data do nascimento \n");
scanf("%d %d %d", &DiaNasc, &MesNasc, &AnoNasc);
printf("Digite a data de hoje \n");
scanf("%d %d %d", &DiaAtual, &MesAtual, &AnoAtual);
Anos = AnoAtual - AnoNasc;
Meses = MesAtual - MesNasc;
Dias = DiaAtual - DiaNasc;
if ((Anos < 0) || ((Anos == 0) && (Meses < 0)) || ((Anos == 0) && (Meses == 0)
&& (Dias < 0)))
printf("Data de nascimento invalida");
else
{
if (Meses < 0)
{
Anos = Anos - 1;
Meses = Meses + 12;
}
if (Dias < 0)
{
if (Meses > 0)
Meses = Meses - 1;
else
Anos = Anos - 1;
switch (MesNasc)
{
case 2 :
if (AnoAtual % 4 == 0)
Dias = Dias + 29;
else
Dias = Dias + 28;
break;
case 4: case 6: case 9: case 11:
Dias = Dias + 30;
break;
default: Dias = Dias + 31;
};
}
printf("Idade = %d ano(s), %d mes(es) e %d dia(s)", Anos, Meses, Dias);
}
}

178
Respostas dos exercícios propostos

9. /*Programa para calcular a nota minima de um aluno*/


#include <stdio.h>
main()
{
float Bim1, Bim2, Bim3, Bim4, MediaBimestral, NotaMinima;
printf("Digite as notas bimestrais: \n");
scanf("%f %f %f %f", &Bim1, &Bim2, &Bim3, &Bim4);
MediaBimestral = (Bim1 + Bim2 + Bim3 + Bim4)/4;
if ((MediaBimestral >= 5) && (MediaBimestral < 7))
{
NotaMinima = (55 - 6*MediaBimestral)/4;
printf("O aluno com notas bimestrais %0.2f, %0.2f, %0.2f e %0.2f precisa
na final de %0.2f", Bim1, Bim2, Bim3, Bim4, NotaMinima);
}
else
printf("O aluno com notas bimestrais %0.2f, %0.2f, %0.2f e %0.2f nao esta
na prova final");
}

Capítulo 4
1. 5
15
45
4
12
36
3
9
27
2
6
18
1
3
9
2. /*Programa para calcular a soma dos quadrados dos n primeiros inteiros
positivos, n dado*/
#include <stdio.h>
main()
{
int n, i, Soma;
printf("Digite o valor de n: \n");

179
Respostas dos exercícios propostos

scanf("%d", &n);
Soma = 0;
for (i = 1; i <= n; i++)
Soma = Soma + i*i;
printf("A soma dos quadrados dos %d primeiros numeros naturais e %d \n", n,
Soma);
}
3.a) /*Programa para calcular a soma dos n primeiros termos de uma sequencia*/
#include <stdio.h>
main()
{
float Soma, Num, Den;
int n, i;
printf("Digite o valor de n: \n");
scanf("%d", &n);
Num = 1;
Den = 2;
Soma = 0.5;
for (i = 1; i < n; i++)
{
Num = Num + 2;
Den = Den + 3;
Soma = Soma + Num/Den;
}
printf("A soma dos %d primeiros numeros da sequencia dada e %f \n", n, Soma);
}
3.b) /*Programa para calcular a soma dos n primeiros termos de uma sequencia*/
#include <stdio.h>
main()
{
float Soma;
int n, i;
printf("Digite o valor de n: \n");
scanf("%d", &n);
Soma = 0;
for (i = 1; i <= n; i++)
if (i % 2 == 1)
Soma = Soma + 1.0/i;
else
Soma = Soma - 1.0/i;
printf("A soma dos %d primeiros numeros da sequencia dada e %f \n", n, Soma);
}

180
Respostas dos exercícios propostos

4. /*Programa para determinar o minimo multiplo comum de dois numeros


positivos*/
#include <stdio.h>
main()
{
int x, y, a, b, Mmc;
printf("Digite os dois numeros \n");
scanf("%d %d", &x, &y);
a = x;
b = y;
if (x < y)
{
a = y;
b = x;
}
Mmc = a;
while (Mmc % b != 0)
Mmc = Mmc + a;
printf("mmc(%d, %d) = %d \n", x, y, Mmc);
}
5. /*Programa para determinar os numeros perfeitos menores que um inteiro
dado*/
#include <stdio.h>
main()
{
int Num, i, Soma, j;
Num = 1;
while (Num != -1)
{
printf("Digite o numero (-1 p/ encerrar) \n");
scanf("%d", &Num);
printf("Numeros perfeitos menores que %d: \n", Num);
for (i = 2; i < Num; i = i + 1)
{
Soma = i + 1;
for (j = 2; j <= i/2; j++)
if (i % j == 0)
Soma = Soma + j;
if (Soma == 2 * i)
printf("%d \n", i);
}
}
}

181
Respostas dos exercícios propostos

6. /*Programa para listar todos so numeros xyzt tais (xy + zt)*(xy + zt) = xyzt*/
#include <stdio.h>
main()
{
int Mil, Cen, i;
printf("Numeros xyzt tais (xy + zt)*(xy + zt) = xyzt");
for (i = 1000; i < 10000; i++)
{
Cen = i % 100;
Mil = i/100;
if ((Cen + Mil)*(Cen + Mil) == i)
printf("%d \n", i);
}
}
7. /*Programa para listar todos os numeros com dois algarismos cujo produto nao
se altera se os algarismos sao invertidos*/
#include <stdio.h>
main()
{
int InvertI, InvertJ, i, j;
printf("Numeros com dois algarismos cujo produto nao se altera os algarismos sao
invertidos");
for (i = 10; i < 100; i++)
{
InvertI = (i % 10)*10 + (i/10);
for (j = 10; j < 100; j++)
{
InvertJ = (j % 10)*10 + (j/10);
if (i*j == InvertI*InvertJ)
printf("%d*%d = %d*%d = %d \n", i, j, InvertI, InvertJ, i*j);
}
}
}
8. /*Programa para determinar o numero de algarismos de um inteiro positivo*/
#include <stdio.h>
#include <math.h>
main()
{
int Num, NumAlg, n;
Num = 1;
while (Num != -1)
{
printf("Digite o numero (-1 p/ encerrar) \n");

182
Respostas dos exercícios propostos

scanf("%d", &Num);
NumAlg = 1;
while (pow10(NumAlg) < Num)
NumAlg = NumAlg + 1;
printf("%d possui %d algarismo(s) \n", Num, NumAlg);
}
}
9. /*Programa para verificar se um inteiro maior que 2 e' um produto de dois
primos*/
#include <stdio.h>
#include <math.h>
main()
{
int Num, Fator1, Fator2, r, i;
Num = 1;
while (Num != -1)
{
printf("Digite o numero (-1 p/ encerrar) \n");
scanf("%d", &Num);
r = sqrt(Num);
Fator1 = 2;
while ((Num % Fator1 != 0) && (Fator1 <= r))
Fator1 = Fator1 + 1;
if (Fator1 > r)
printf("%d é primo\n", Num);
else
{
Fator2 = Num/Fator1;
r = sqrt(Fator2);
i = 2;
while ((Fator2 % i != 0) && (i <= r))
i = i + 1;
if (i <= r)
printf("%d nao e produto de dois primos \n", Num);
else
printf("%d e' produto dos primos %d e %d \n", Num, Fator1,
Fator2);
}
}
}
10. /*Programa para determinar a decomposição em fatores primos de um inteiro*/
#include <stdio.h>
main()
{
183
Respostas dos exercícios propostos

int Num, i, n, Mult;


Num = 1;
while (Num != -1)
{
printf("Digite o numero (-1 p/ encerrar) \n");
scanf("%d", &Num);
n = Num;
printf("Decomposicao em fatores primos de %d: \n", Num);
i = 2;
while (n > 1)
{
Mult = 0;
while (n % i == 0)
{
n = n/i;
Mult = Mult + 1;
}
if (Mult > 0)
printf("%d com multiplicidade %d \n", i, Mult);
i = i + 1;
}
}
}
11. Ver solução na seção 5.6 do capítulo 5.
12. /* Programa que determina o n-esimo termo da sequencia de Fibbonaci*/
#include <stdio.h>
main()
{
int n, i, Ant1, Ant2, Termo;
printf("Digite n \n");
scanf("%d", &n);
Termo = 1;
Ant1 = 1;
Ant2 = 0;
i = 1;
while (i < n)
{
Termo = Ant1 + Ant2;
Ant2 = Ant1;
Ant1 = Termo;
i = i + 1;
}
printf(" O termo de ordem %d da sequencia de Fibbonaci e' %d \n", n, Termo);

184
Respostas dos exercícios propostos

}
13. /*Programa para determinar o menor inteiro n tal que a soma dos n primeiros
termos da serie harmonica e' maior que um real k dado*/
#include <stdio.h>
main()
{
int i;
float Soma, k;
k = 1;
while (k != -1)
{
printf("Digite o valor de k (-1 p/ encerrar) \n");
scanf("%f", &k);
Soma = 0;
i = 0;
while (Soma <= k)
{
i = i + 1;
Soma = Soma + 1.0/i;
}
printf("O menor inteiro n tal que a soma dos n primeiros termos da serie
harmonica e' maior que %.2f e' %d \n", k, i);
}
}
14. /*Programa para determinar os pares de números amigos menores que um
número dado*/
#include <stdio.h>
long SomaDivisoresMenores(long int n) /* Ver capítulo 5*/
{
int i, s
s = 1;
m = n/2;
for (i = 2; i <= m; i++)
if (n % i == 0)
s = s + i;
return s;
}

main()
{
long int j, x, y;
const long m = 10000;
printf("Amigos menores que %ld \n", m);
for (j = 2; j < m; j++)
185
Respostas dos exercícios propostos

{
x = SomaDivisoresMenores(j);
if (j < x)
if (j == SomaDivisoresMenores(x))
printf("%-10ld %-20ld\n", j, x);
}
}
15. /*Programa que gera subconjuntos com tres elementos*/
#include <stdio.h>
main()
{
int n, i, j, k;
printf("Digite o valor de n: \n");
scanf("%d", &n);
for (i = 1; i <= n - 2; i++)
for (j = i + 1; j <= n - 1; j++)
for (k = j + 1; k <= n; k++)
printf("{%d, %d, %d}\n", i, j, k);
}

Capítulo 5
1. /*Funcao para determinar o k-esimo digito de um numero*/
/*Funcao que retorna o numero de algarismos de um inteiro*/
int NumAlg(int n)
{
int i = 1;
while (pow10(i) < n)
i = i + 1;
return (i);
}

int KDigito(int n, int k)


{
int i, Potencia, Resto;
if (k <= NumAlg(n))
{
Potencia = pow10(k);
Resto = n % Potencia;
Potencia = pow10(k – 1);
return (Resto/Potencia);
}

186
Respostas dos exercícios propostos

else
return(-1);
}
2. /*Funcao iterativa para o calculo do fatorial impar*/
long int FatImpar(int n)
{
long int Fat;
int i;
if (n % 2 == 1)
{
Fat = 1;
for (i = 3; i <= n; i = i + 2)
Fat = Fat * i;
return (Fat);
}
else
return(-1);
}

/*Funcao recursiva para o calculo do fatorial impar*/


long int FatImparRec(int n)
{
if (n % 2 == 1)
if (n == 1)
return(1);
else
return(n*FatImparRec(n - 2));
else
return(-1);
}
3. /*Programa para determinar o fatorial primo de um inteiro dado*/
#include <stdio.h>
#include <math.h>
/*Funcao que recebe um inteiro e verifica se ele e primo*/
int Primo(int x)
{
int j;
j = 2;
while ((x % j != 0) && (j <= sqrt(x)))
j = j + 1;
if (j <= sqrt(x))
return(0);
else

187
Respostas dos exercícios propostos

return(1);
}
main()
{
int Num, j;
long FatPrim;
printf("Digite o inteiro \n");
scanf("%d", &Num);
if (Primo(Num))
{
FatPrim = 2;
for (j = 3; j <= Num; j++)
if (Primo(j))
FatPrim = FatPrim*j;
printf("Fatorial primo de %d e igual a %d \n", Num, FatPrim);
}
else
printf("%d nao e primo \n", Num);
}
4. /*Funcao que retorna a soma dos algarismos de um inteiro dado*/
/*Funcao que retorna o numero de algarismos de um inteiro dado*/
int NumAlg(int n)
{
int i = 1;
while (pow10(i) < n)
i = i + 1;
return (i);
}
int SomaAlgarismos(int n)
{
int i, Potencia, NumAlgarismos, Soma;
NumAlgarismos = NumAlg(n);
Soma = n % 10;
for (i = 1; i < NumAlgarismos; i++)
{
n = n/10;
Soma = Soma + n%10;
}
return (Soma);
}
5. /*Função recursiva para determinar o n-esimo termo da sequencia de
Fibbonaci*/
int FibbRec(int x)

188
Respostas dos exercícios propostos

{
if ((x == 1) || (x == 2))
return(1);
else
return(FibbRec(x - 1) + FibbRec(x - 2));
}
6. /*Função recursiva para gerar uma tabuada de multiplicação*/
void Tabuada(int x)
{
int y;
static a = 1, b = 1;
if (x <= 9)
{
for (y = 2; y <= 9; y++)
{
gotoxy(a, b);
printf("%d x %d = %d", x, y, x*y);
b++;
}
if (x % 5 != 0)
{
a = a + 13;
b = b - 8;
}
else
{
a = 1;
b = 10;
}
Tabuada(x + 1);
}
}

Capítulo 6
1. /*Função que exibe um vetor na ordem inversa*/
void InverteVetor(int *v, int t)
{
int i;
for (i = t - 1; i >= 0; i--)
printf("%d ", v[i]);
}

189
Respostas dos exercícios propostos

2. /*Função que verifica se um vetor é palíndromo*/


int VerificaVetorPalindromo(int *v, int t)
{
int i;
i = 0;
while((v[i] == v[t - i - 1]) && (i < t/2))
i = i + 1;
if (i == t/2)
return (1);
else
return (0);
}
3. /*Função que decompõe um vetor nas componentes de ordem ímpar e de ordem
par*/
void DecompVetor(int *v, int t, int *u, int *w)
{
int i;
for (i = 0; i < t; i++)
if (i % 2 == 0)
u[i/2] = v[i];
else
w[((i+1)/2) - 1] = v[i];
}
4. /Função que decompõe um vetor nas componentes de valor par e de valor
ímpar*/
void DecompVetor(int *v, int t, int *u, int *k, int *w, int *j)
{
int i;
*k = *j = 0;
for (i = 0; i < t; i++)
if (v[i] % 2 == 0)
{
u[*k] = v[i];
*k = *k + 1;
}
else
{
w[*j] = v[i];
*j = *j + 1;
}
}
5. /*Função que determina a norma de um vetor*/
float NormaVetor(int *v, int t)

190
Respostas dos exercícios propostos

{
int i;
float Norma = 0;
for (i = 0; i < t; i++)
Norma = Norma + v[i]*v[i];
return(sqrt(Norma));
}
6. /Função que determina o produto escalar de dois vetores*/
int ProdutoEscalar(int *v, int *w, int t)
{
int i, ProdEscalar = 0;
for (i = 0; i < t; i++)
ProdEscalar = ProdEscalar + v[i]*w[i];
return(ProdEscalar);
}
7. /*Função que determina a amplitude de uma tabela*/
/*Função que determina a maior componente de um vetor*/
float Maior(float *v, int t)
{
int i;
float m;
m = v[0];
for (i = 1; i < t; i++)
if (v[i] > m)
m = v[i];
return(m);
}
/*Função que determina a menor componente de um vetor*/
float Menor(float *v, int t)
{
int i;
float m;
m = v[0];
for (i = 1; i < t; i++)
if (v[i] < m)
m = v[i];
return(m);
}

float Amplitude(float *v, int t)


{
return(Maior(v, t) - Menor(v, t));
}

191
Respostas dos exercícios propostos

8. /Função que determina o desvio padrão de uma tabela*/


/*Função que determina a média aritmética de uma tabela*/
float Media(float *v, int t)
{
int i;
float Soma = 0.0;
for (i = 0; i < t; i++)
Soma = Soma + v[i];
return(Soma/t);
}

float DesvioPadrao(float *v, int t)


{
int i;
float *d;
float Med;
Med = Media(v, t);
for (i = 0; i < t; i++)
d[i] = (v[i] - Med)*(v[i] - Med);
return(sqrt(Media(d, t)));
}
9. /*Função para determinar as componentes distintas de um vetor*/
/*Função que verifica se um dado valor está armazenado num vetor*/
int PesqSequencial(int v1[30], int t1, int x)
{
int i;
i = 0;
while ((v1[i] != x) && (i < t1))
i = i + 1;
if (v1[i] == x)
return (1);
else
return (0);
}

int ComponentesDistintas(int v2[30], int Aux[30], int t2)


{
int i, k;
Aux[0] = v2[0];
k = 0;
for (i = 1; i < t2; i++)
if (PesqSequencial(Aux, k + 1, v2[i]) == 0)
{
k = k + 1;
192
Respostas dos exercícios propostos

Aux[k] = v2[i];
}
return(k + 1);
}
10. /*Função que realiza um sorteio a partir dos algarismos das unidades dos
números sorteados pela loteria*/
void LeSorteio(int v[5])
{
int i;
for (i = 0; i < 5; i++)
scanf("%d", &v[i]);
}

long NumeroSorteado(int v[5])


{
int i;
long n = 0;
for (i = 4; i >= 0; i--)
n = n + (v[i] % 10)*(int)pow10(4 - i);
return (n);
}
11. /*Função que insere um valor num vetor numa posição dada*/
void InsereComponente(int *v, int *t, int x, int p)
{
int i;
*t = *t + 1;
for (i = *t; i > p; i--)
v[i] = v[i - 1];
v[p] = x;
}
12. /*Função que insere uma componente num vetor ordenado de modo que ele se
mantém ordenado*/
void InsereOrdenado(int *v, int *t, int x)
{
int i, j;
i = 0;
while ((v[i] < x) && (i < *t))
i = i + 1;
*t = *t + 1;
for (j = *t; j > i; j--)
v[j] = v[j - 1];
v[i] = x;
}

193
Respostas dos exercícios propostos

13. /*Função que exclui uma componente de um vetor*/


void DeletaComponente(int *v, int *t, int p)
{
int i;
if (p < *t)
{
for (i = p; i < *t; i++)
v[i] = v[i + 1];
*t = *t - 1;
}
}
14. /*Função que determina as componentes comuns de dois vetores*/
int PesqSequencial(int *v, int t, int x)
{
int i;
i = 0;
while ((v[i] != x) && (i < t))
i = i + 1;
if (v[i] == x)
return (1);
else
return (0);
}

void InterseptaVetores(int *v1, int t1, int *v2, int t2, int *v, int *t)
{
int i;
*t = 0;
for (i = 0; i < t1; i++)
if (PesqSequencial(v2, t2, v1[i]) == 1)
{
v[*t] = v1[i];
*t = *t + 1;
}
}
15. /*Função que determina a maior diferença entre duas componentes
consecutivas de um vetor ordenado*/
int Maior(int *v, int t, int *p)
{
int i;
float m;
m = v[0];
*p = 0;

194
Respostas dos exercícios propostos

for (i = 1; i < t; i++)


if (v[i] > m)
{
m = v[i];
*p = i;
}
return(m);
}

int MaiorDiferenca(int v[20], int t, int *p)


{
int i, Diferencas[20];
for (i = 0; i < t - 1; i++)
Diferencas[i] = v[i+1] - v[i];
return(Maior(Diferencas, t - 1, p));
}
16. /*Função que corrige um teste objetivo*/
int Corrige(int v1[50], int v2[50], int t)
{
int Acerto, i;
Acerto = 0;
for (i = 0; i < t; i++)
if (v1[i] == v2[i])
Acerto = Acerto + 1;
return (Acerto);
}
17. /*Função que calcula o valor numérico de um polinomio*/
float ValNumerico(float *v, int g, float x)
{
int i;
float ValorNumerico;
ValorNumerico = v[g];
for (i = 0; i < g; i++)
ValorNumerico = ValorNumerico + v[i]*pow(x, g - i);
return (ValorNumerico);
}
18. /*Função que determina uma moda de uma relação armazenada num vetor*/
float PosicaoMaior(float v[50], int t)
{
int i, m;
m = 0;
for (i = 1; i < t; i++)
if (v[i] > m)

195
Respostas dos exercícios propostos

m = i;
return (m);
}

float Moda(float v[50], int t)


{
float Freq[50];
int i, j, p;
for (i = 0; i < t; i++)
{
Freq[i] = 0;
for (j = 0; j < t; j++)
if (v[j] == v[i])
Freq[i] = Freq[i] + 1;
}
p = PosicaoMaior(Freq, t);
return (v[p]);
}
19. /*Função para inverter um numero dado*/
long InverteNumero(int m)
{
int i, j, n, Alg[20];
long Invert;
i = 0;
n = m;
while (pow10(i) < m)
{
Alg[i] = n % 10;
n = n/10;
i = i + 1;
}
i = i - 1;
Invert = 0;
for (j = 0; j <= i; j++)
Invert = Invert + Alg[i-j]*pow10(j);
return (Invert);
}
20. /*Função que converte um numero decimal para o sistema binário*/
void DecBin(int n)
{
int i, j, *Bin;
i = 0;
while (n > 0)

196
Respostas dos exercícios propostos

{
Bin[i] = n % 2;
n = n/2;
i = i + 1;
}
for (j = i - 1; j >= 0; j--)
printf("%d", Bin[j]);
printf("\n");
}
21. /*Função para decompor um número inteiro em fatores primos, armazenando
os fatores e suas multiplicidades uma matriz*/
void DecFatPrimos(int n, int Fatores[10][10], int *q)
{
int i, Mult;
i = 2;
*q = 0;
while (n > 1)
{
Mult = 0;
while (n % i == 0)
{
n = n/i;
Mult = Mult + 1;
}
if (Mult > 0)
{
Fatores[*q][0] = i;
Fatores[*q][1] = Mult;
*q = *q + 1;
}
i = i + 1;
}
}
22. /*Função que verifica a aprovação de um aluno da Universidade Federal de
Alagoas*/
float Menor(float v[4], int *b)
{
int i, m;
m = v[0];
*b = 0;
for (i = 1; i < 4; i++)
if (v[i] < m)
{

197
Respostas dos exercícios propostos

m = v[i];
*b = i;
}
return (m);
}

void Reavaliacao(float v[4])


{
int b, MenorNota;
char s;
float Reav;
MenorNota = Menor(v, &b);
if (MenorNota < 7)
{
printf("O aluno fez reavaliacao (S/N)?");
fflush(stdin);
scanf("%c", &s);
if (toupper(s) == 'S')
{
printf("Digite a nota da reavaliacao");
scanf("%f", &Reav);
v[b] = Reav;
}
}
}
float Media(float v[4])
{
int i;
float m = 0;
for (i = 0; i < 4; i++)
m = m + v[i];
return(m/4);
}

float MediaFinal(float v[4], char *Sit)


{
float NotaFin, MediaFin, MediaBim;
char s;
Reavaliacao(v);
MediaBim = Media(v);
if ((MediaBim < 5) || (MediaBim >= 7))
MediaFin = MediaBim;
else
{

198
Respostas dos exercícios propostos

printf("O aluno fez prova final(S/N)?");


scanf("%c", &s);
if (toupper(s) == 'S')
{
printf("Digite a nota da prova final");
scanf("%f", &NotaFin);
MediaFin = (6*MediaBim + 4*NotaFin)/10;
}
}
if (MediaFin < 5.5)
*Sit = 'R';
else
*Sit = 'A';
return(MediaFin);
}
23. /*Função que retorna a transposta de uma matriz*/
void Transposta(float Mat[10][10], float Transp[10][10], int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
for(j = 0; j < n; j++)
Transp[j][i] = Mat[i][j];
}
24. /*Função que permuta duas linhas de uma matriz*/
void TrocaLinha(float v[10][10], int n, int Lin1, int Lin2)
{
int i;
float Aux;
for (i = 0; i < n; i++)
{
Aux = v[Lin1][i];
v[Lin1][i] = v[Lin2][i];
v[Lin2][i] = Aux;
}
}
25. /*Função que verifica se uma matriz é triangular*/
int VerificaMatrizTriangular(float v[10][10], int n)
{
int i = 0, j, Sim = 1;
while ((i < n) && (Sim == 1))
{
j = i + 1;
while((j < n) && (Sim == 1))

199
Respostas dos exercícios propostos

if (v[i][j] != 0)
Sim = 0;
else
j = j + 1;
i = i + 1;
}
return (Sim);
}
26. /*Função que verifica se uma matriz é simétrica*/
int VerificaMatrizSimetrica(float v[10][10], int n)
{
int i = 0, j, Sim = 1;
while ((i < n) && (Sim == 1))
{
j = 0;
while((j < n) && (Sim == 1))
if (v[i][j] != v[j][i])
Sim = 0;
else
j = j + 1;
i = i + 1;
}
return (Sim);
}
27. /*Função que multiplica duas matrizes*/
void MultMat(float m1[5][5], float m2[5][5], float m3[5][5], int l1, int c1, int l2, int
c2)
{
int i = 0, j = 0, k = 0;
if (c1 == l2)
{
while (i < l1)
{
m3[i][j] = 0;
while (j < c2 && k < l2)
{
m3[i][j] = m3[i][j] + m1[i][k]*m2[k][j];
k = k +1;
}
k = 0;
j = j + 1;
if (j == c2)
{

200
Respostas dos exercícios propostos

i = i + 1;
j = 0;
}
}
}
}
28. /*Função que determina as médias das linhas de uma matriz, armazenando-as
numa nova coluna da matriz*/
float Media(float v[10], int t)
{
int i;
float Soma = 0.0;
for (i = 0; i < t; i++)
Soma = Soma + v[i];
return(Soma/t);
}

void MediaLinha(float v[10][10], int m, int n)


{
int i;
for (i = 0; i < m; i++)
v[i][n] = Media(v[i], n);
}
29. /*Função que fornece o menor elemento de cada linha de uma matriz,
indicando sua coluna*/
float Menor(float v[10], int t, int *p)
{
int i;
float m;
m = v[0];
*p = 0;
for (i = 1; i < t; i++)
if (v[i] < m)
{
m = v[i];
*p = i;
}
return (m);
}
void MenorElemento(float v[10][10], int m, int n)
{
int i, Col;
float Men;

201
Respostas dos exercícios propostos

for (i = 0; i < m; i++)


{
Men = Menor(v[i], n, &Col);
printf("%d \b %.2f \b %d \n", i + 1, Men, Col + 1);
}
}
30. /*Programa que determina uma escala para uma viagem entre duas cidades
dadas*/
#include <stdio.h>
#include <conio.h>
void LeDistancias(int v[20][20], int n)
{
int i, j;
printf("Digite as distancias entre as cidades\n");
for (i = 0; i < n; i++)
for (j = i; j < n; j++)
if (i == j)
v[i][j] = 0;
else
{
scanf("%d", &v[i][j]);
v[j][i] = v[i][j];
}
}
void EscreveDistancias(int v[20][20], int n)
{
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%d \t", v[i][j]);
printf("\n");
}
}

int Escala(int v[20][20], int n, int o, int d)


{
int i, Esc, Menor;
Esc = 1;
Menor = v[o][o + 1] + v[0][d];
for (i = 0; i < n; i++)
if (v[o][i] + v[i][d] < Menor)
{
Menor = v[o][i] + v[i][d];
202
Respostas dos exercícios propostos

Esc = i + 1;
}
return(Esc);
}
main()
{
int NumCidades, x, y, Origem, Destino, Esc;
int Distancias[20][20];
clrscr();
printf("Digite o numero de cidades");
scanf("%d", &NumCidades);
LeDistancias(Distancias, NumCidades);
EscreveDistancias(Distancias, NumCidades);
printf("Digite a origem e o destino \n");
scanf("%d %d", &Origem, &Destino);
if (Origem < Destino)
{
x = Origem;
y = Destino;
}
else
{
x = Destino;
y = Origem;
}
if (Distancias[x-1][y-1] > 400)
{
Esc = Escala(Distancias, NumCidades, x, y) + 1;
printf("A viagem de %d para %d deve ter uma escala em %d", Origem,
Destino, Esc);
}
else
if (Distancias[x-1][y-1] == 0)
printf("\aOrigem e destino iguais!");
else
printf("A viagem de %d para % deve ser feita sem escalas");
}
31. /*Função recursiva para a exibição das combinações de n com taxa k, n e k
dados*/
#include <stdio.h>
void Combinacoes(int n, int k, int i, int s, int v[30])
{
int m, j;
if (i < k)
203
Respostas dos exercícios propostos

for (j = s; j <= n - k + i + 1 ; j++)


{
v[i] = j;
s = j + 1;
Combinacoes(n, k, i + 1, s, v);
if (i == k - 1)
{
for (m = 0; m < k; m++)
printf("%d ", v[m]);
printf("\n");
}
}
}

Capítulo 7
1. /*Fumção que realiza uma pesquisa "pessimista" num vetor*/
int PesquisaPessimista(int *v, int t, int x)
{
int i;
i = 0;
while((v[i] != x) && (v[t - i - 1] != x) && (i < t/2))
i = i + 1;
if (v[i] == x)
return (i);
else
if (v[t - i - 1] == x)
return (t - i - 1);
else
return(-1);
}
2. /*Função que implementa o insertsort*/
/*Função que insere um elemento num vetor ordenado*/
void InsereOrdenado(int v1[500], int *t, int x)
{
int i, j;
i = 0;
while ((v1[i] < x) && (i < *t ))
i = i + 1;
*t = *t + 1;
for (j = *t; j > i; j--)
v1[j] = v1[j - 1];
v1[i] = x;
204
Respostas dos exercícios propostos

}
void InsertSort(int v[500], int t) {
int Aux[500];
int i, j = 1;
Aux[0] = v[0];
for (i = 1; i < t; i++)
InsereOrdenado(Aux, &j, v[i]);
for (i = 0; i < t; i++)
v[i] = Aux[i];
}

Capítulo 8
1. /*Programa que verifica se uma palavra é palídroma*/
#include <stdio.h>
#include <string.h>
char *InverteString(char *s)
{
int i, Comp;
char *St;
Comp = strlen(s);
for (i = 0; i < Comp; i++)
St[i] = s[Comp - 1- i];
St[Comp] = '\0';
return (St);
}

main()
{
char Str[40], Aux[40];
printf("Digite a string\n");
gets(Str);
strcpy(Aux, Str);
strupr(Aux);
if (strcmp(InverteString(Aux), Aux) == 0)
printf("%s e palindroma", Str);
else
printf("%s nao e palindroma", Str);
}
2. /*Programa que determina o número de palavras de um texto dado*/
#include <stdio.h>
#include <string.h>
int ContaPalavras(char s[1000])

205
Respostas dos exercícios propostos

{
int i, Cont = 0, Comp;
Comp = strlen(s);
for (i = 1; i <= Comp; i++)
if ((s[i - 1] != ' ') && (s[i] == ' '|| s[i] == '\0'))
Cont = Cont + 1;
return (Cont);
}
main()
{
int NumPalavras;
char Texto[1000];
printf("Digite o texto\n");
gets(Texto);
NumPalavras = ContaPalavras(Texto);
printf("O texto %s tem %d palavras\n", Texto, NumPalavras);
}
3. /*Programa para converter um número do sistema decimal para o sistema
binário*/
#include <stdio.h>
char *InverteString(char *s)
{
int i, Comp;
char *St;
Comp = strlen(s);
for (i = 0; i < Comp; i++)
St[i] = s[Comp - 1- i];
St[Comp] = '\0';
return (St);
}

char *DecBin(int n)
{
int i, j;
char *Bin;
i = 0;
while (n > 0)
{
if (n % 2 == 1)
Bin[i] = '1';
else
Bin[i] = '0';
n = n/2;
i = i + 1;
206
Respostas dos exercícios propostos

}
Bin[i] ='\0';
Bin = InverteString(Bin);
return(Bin);
}

main()
{
int Num;
char *Binario;
printf("Digite o numero \n");
scanf("%d", &Num);
Binario = DecBin(Num);
printf("%d = (%s)2 \n", Num, Binario);
}
4. /*Programa para converter um número do sistema binário para o sistema
decimal*/
#include <stdio.h>
#include <math.h>
int *BinDec(char *s)
{
int i, Comp, Dec = 0;
Comp = strlen(s);
for (i = 0; i < Comp; i++)
if (s[i] == '1')
Dec = Dec + pow(2, Comp - i - 1);
return (Dec);
}
main()
{
char *Binario;
int Decimal;
printf("Digite o numero do sistema binario");
gets(Binario);
Decimal = BinDec(Binario);
printf("%s no sistema decimal: %d \n", Binario, Decimal);
}
5. /*Função que verifica se uma conta foi digitada corretamente*/
int ArmazenaDigitos(char *s, int *v)
{
int i, Comp;
Comp = strlen(s);
for (i = 0; i < Comp; i = i + 1)

207
Respostas dos exercícios propostos

v[i] = (int) (s[i] - '0');


return(i);
}

char CalculaDigito(char *s)


{
char c;
int t, i, j, Digito, *v;
t = ArmazenaDigitos(s, v);
Digito = 0;
j = 2;
for (i = t - 1; i >= 0; i = i - 1, j = j + 1)
Digito = Digito + v[i]*j;
Digito = Digito % 11;
Digito = 11 - Digito;
if ((Digito == 10) || (Digito == 11))
Digito = 0;
c = (char) Digito + '0';
return (c);
}

int VerificaConta(char *s)


{
int Comp;
char Dv, UltDigito;
char Conta[30];
Comp = strlen(s);
UltDigito = s[Comp - 1];
strncpy(Conta, s, Comp - 1);
Conta[Comp - 1] = '\0';
Dv = CalculaDigito(Conta);
if (UltDigito == Dv)
return (1);
else
return (0);
}
6. /*Programa que substitui uma palavra de um texto*/
#include <stdio.h>
#include <string.h>
int Pos(char *s1, char *s2)
{
char *Aux;
Aux = strstr(s1, s2);
if (Aux != NULL)
208
Respostas dos exercícios propostos

return(Aux - s1);
else
return (-1);
}

void DeletaCaracteres(char *s, int n, int p)


{
int i, Comp;
char *Aux;
Comp = strlen(s);
if (p + n <= Comp)
{
i = p;
while (i <= Comp - n)
{
s[i] = s[i + n];
i = i + 1;
}
}
else
s[p + 1] = '\0';
}

void InsereCadeia(char *s1, char *s2, int p)


{
int Comp;
char Aux1[50], Aux2[50];
Comp = strlen(s1);
if (p <= Comp)
{
Comp = strlen(s1);
strncpy(Aux1, s1, p);
Aux1[p] = '\0';
strcpy(Aux2, s1);
DeletaCaracteres(Aux2, p, 0);
strcat(Aux1, s2);
strcat(Aux1, Aux2);
strcpy(s1, Aux1);
}
}

int Substituir(char *s1, char *s2, char *s3)


{
int p, Comp;

209
Respostas dos exercícios propostos

p = Pos(s1, s2);
if (p != -1)
{
Comp = strlen(s2);
DeletaCaracteres(s1, Comp, p);
InsereCadeia(s1, s3, p);
}
else
printf("Palavra nao encontrada");
return (p);
}

main()
{
char Texto[1000], Str1[1000], Str2[1000];
int i;
printf("Digite o texto: ");
gets(Texto);
printf("Substituir: ");
gets(Str1);
printf("Substituir por: ");
gets(Str2);
printf("Texto original: %s \n", Texto);
i = Substituir(Texto, Str1, Str2);
if (i != -1)
printf("Novo texto: %s \n", Texto);
}
7. /*Programa que converte um nome para o formato ultimo sobrenome/nome*/
#include <stdio.h>
#include <string.h>
char *Copia(char *s, int n, int p)
{
char *Aux;
int Comp;
Comp = strlen(s);
if (p < Comp)
{
Aux = &s[p];
if (p + n < Comp)
Aux[n] = '\0';
else
Aux[strlen(Aux)] = '\0';
return(Aux);
}
210
Respostas dos exercícios propostos

else
return (NULL);
}

char *UltimoSobrenome(char *s)


{
int i, n, Comp;
char *Aux;
Comp = strlen(s);
i = Comp - 1;
n = 0;
while (s[i] != ' ')
{
i = i - 1;
n = n + 1;
}
Aux = Copia(s, n, i + 1);
return (Aux);
}

char *PrimeiroNome(char *s)


{
int i, n;
char *Aux;
i = 0;
while (s[i] != ' ')
i = i + 1;
Aux = Copia(s, i, 0);
return (Aux);
}

main()
{
char Nome[40], Identificacao[40];
int i;
printf("Digite o nome: ");
gets(Nome);
strcpy(Identificacao, UltimoSobrenome(Nome));
strcat(Identificacao, "/");
strcat(Identificacao, PrimeiroNome(Nome));
printf("Identificacao: %s \n", Identificacao);
}
8. /*Programa que converte um nome dado para o formato de referência
bibliográfica*/
211
Respostas dos exercícios propostos

#include <stdio.h>
#include <string.h>
char *Copia(char *s, int n, int p)
{
char *Aux;
int Comp;
Comp = strlen(s);
if (p < Comp)
{
Aux = &s[p];
if (p + n < Comp)
Aux[n] = '\0';
else
Aux[strlen(Aux)] = '\0';
return(Aux);
}
else
return (NULL);
}

/*Funcao que retorna o ultimo sobrenome de um nome de uma pessoa*/


char *UltimoSobrenome(char *s)
{
int i, n, Comp;
char *Aux;
Comp = strlen(s);
i = Comp - 1;
n = 0;
while (s[i] != ' ')
{
i = i - 1;
n = n + 1;
}
Aux = Copia(s, n, i + 1);
return (Aux);
}

/*Funcao que retorna as iniciiais dos nomes e dos sobrenomens, exceto do último
sobrenome*/
char *Iniciais(char *s)
{
int i, k, Comp;
char Aux[40];
Aux[0] = s[0];

212
Respostas dos exercícios propostos

Aux[1] = '.';
Aux[2] = ' ';
Comp = strlen(s);
k = 3;
for (i = 1; i < Comp - 1; i++)
if ((s[i] == ' ') && (s[i+1] != ' '))
{
Aux[k] = s[i+1];
Aux[k+1] = '.';
Aux[k+2] = ' ';
k = k + 3;
}
Aux[k - 3] = '\0';
return (Aux);
}

void DeletaCaracteres(char *s, int n, int p)


{
int i, Comp;
char *Aux;
Comp = strlen(s);
if (p + n <= Comp)
{
i = p;
while (i <= Comp - n)
{
s[i] = s[i + n];
i = i + 1;
}
}
else
s[p + 1] = '\0';
}

/*Funcao para verificar se uma string esta contida num outra string*/
int Pos(char *s1, char *s2)
{
char *Aux;
Aux = strstr(s1, s2);
if (Aux != NULL)
/*return(strlen(s1) - strlen(Aux));*/
return(Aux - s1);
else
return (-1);

213
Respostas dos exercícios propostos

/*Funcao para deletar as particulas de, do, dos, da, das, e*/
void DeletaParticulas(char *s, char *Part)
{
int Comp, p;
p = Pos(s, Part);
Comp = strlen(Part);
if ((p != -1) && (s[p-1] == ' ') && (s[p + Comp] == ' '))
DeletaCaracteres(s, Comp, p);
}

main()
{
char Nome[40], Aux[40], Autor[40];
printf("Digite o nome: ");
gets(Nome);
strcpy(Aux, Nome);
DeletaParticulas(Aux, "de");
DeletaParticulas(Aux, "da");
DeletaParticulas(Aux, "das");
DeletaParticulas(Aux, "do");
DeletaParticulas(Aux, "dos");
DeletaParticulas(Aux, "e");
strcpy(Autor, UltimoSobrenome(Aux));
strcat(Autor, ", ");
strcat(Autor, Iniciais(Aux));
printf("Referencia bibliografica: %s \n", Autor);
}
9. /*Função que retorna uma palavra no formato P A L A V R A*/
#include <string.h>
#include <ctype.h>
char *Aviso(char *s)
{
int c, i;
c = strlen(s);
for (i = c; i >= 0; i--)
{
s[2*i] = toupper(s[i]);
s[2*i - 1] = ' ';
}
s[2*i + 1] = '\0';
return s;
}

214
Respostas dos exercícios propostos

Capítulo 9
2. /*Funcao que reune dois arquivos de mesma estrutura (definida em struct
TRegistro)*/
void ReuneArquivos(char s[12], char s1[12], char s2[12])
{
FILE *p, *p1, *p2;
struct TRegistro r;
int Tam;
Tam = sizeof(r);
p1 = fopen(s1, "rb");
p2 = fopen(s2, "rb");
p = fopen(s, "wb");
fread(&r, Tam, 1, p1);
while (!feof(p1))
{
fwrite(&r, Tam, 1, p);
fread(&r, Tam, 1, p1);
}
fread(&r, Tam, 1, p2);
while (!feof(p2))
{
if (ConsultaRegistro(r.Mat, s1) == -1)
fwrite(&r, Tam, 1, p);
fread(&r, Tam, 1, p2);
}
fclose(p);
fclose(p1);
fclose(p2);
}
3. /*Programa que gera, a partir de um arquivo contendo nomes e salarios, um
arquivo com os salarios maiores que 5.000,00*/
#include <stdio.h>
#include <string.h>
struct TRegistro
{
char Nome[40];
float SalBruto;
};

void GeraArquivoAltosSalarios(char *s, char *s1)


{

215
Respostas dos exercícios propostos

FILE *p, *t;


struct TRegistro r;
t = fopen(s, "rb");
if (t == NULL)
printf("Arquivo %s nao existe", s);
else
{
p = fopen(s1, "rb");
if (p != NULL)
printf("Arquivo ja existe");
else
{
p = fopen(s1, "wb");
fread(&r, sizeof(r), 1, t);
while (!feof(t))
{
if (r.SalBruto > 5000)
fwrite(&r, sizeof(r), 1, p);
fread(&r, sizeof(r), 1, t);
}
}
}
fclose(p);
fclose(t);
}
main()
{
char NomeArq[12];
printf("Digite o nome do arquivo\n");
scanf("%s", NomeArq);
GeraArquivoAltosSalarios(NomeArq, "AltoSal.arq");
}
4. /*Função que inclui um registro num arquivo ordenado de modo que ele se
mantém ordenado*/
/*Função que copia o último registro no final do arquivo*/
void CopiaUltimoRegistro(char s[12])
{
struct TRegistro r;
FILE *p;
long Byte, Tam;
p = fopen(s, "rb");
Tam = sizeof(r);
fseek(p, -Tam, SEEK_END);
fgetpos(p, &Byte);
216
Respostas dos exercícios propostos

fsetpos(p, &Byte);
fread(&r, Tam, 1, p);
fclose(p);
p = fopen(s, "ab");
fwrite(&r, Tam, 1, p);
fclose(p);
}

/*Função que determina a posição de inclusão do novo arquivo*/


long PosicaoDeInclusao(struct TRegistro Reg, char s[12])
{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
p = fopen(s, "rb");
Byte = 0;
Tam = sizeof(r);
fread(&r, Tam, 1, p);
while ((!feof(p)) && (strcmp(Reg.Mat, r.Mat) > 0))
{
Byte = Byte + Tam;
fread(&r, Tam, 1, p);
}
fclose(p);
return (Byte);
}

void IncluiRegistroOrdenado(struct TRegistro Reg, char s[12])


{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
Tam = sizeof(r);
Byte = PosicaoDeInclusao(Reg, s);
CopiaUltimoRegistro(s);
p = fopen(s, "rb+");
fseek(p, -3*Tam, SEEK_END);
fgetpos(p, &Pos);
while (Pos >= Byte)
{
fsetpos(p, &Pos);
fread(&r, Tam, 1, p);
fgetpos(p, &Pos);
fsetpos(p, &Pos);

217
Respostas dos exercícios propostos

fwrite(&r, Tam, 1, p);


fgetpos(p, &Pos);
Pos = Pos - 3*Tam;
}
fsetpos(p, &Byte);
fwrite(&Reg, Tam, 1, p);
fclose(p);
}
5. /*Programa para reunir dois arquivos ordenados num terceiro arquivo também
ordenado*/
#include <stdio.h>
#include <string.h>
struct TRegistro
{
char Mat[4];
char Nome[40];
};

/*Funcao que verifica se um registro com matricula dada pertence ao arquivo*/


int ConsultaRegistro(char s[12], char s1[12])
{
FILE *p;
int Achou = 0;
struct TRegistro r;
fpos_t Byte;
p = fopen(s1, "rb");
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
while (!feof(p) && !Achou)
if (strcmp(s, r.Mat) == 0)
Achou = 1;
else
{
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
}
if (Achou == 0)
return (-1);
else
return(Byte);
}

/*Funcao que copia o ultimo registro no final do arquivo*/


void CopiaUltimoRegistro(char s[12])
218
Respostas dos exercícios propostos

{
struct TRegistro r;
FILE *p;
long Byte, Tam;
p = fopen(s, "rb");
Tam = sizeof(r);
fseek(p, -Tam, SEEK_END);
fgetpos(p, &Byte);
fsetpos(p, &Byte);
fread(&r, Tam, 1, p);
fclose(p);
p = fopen(s, "ab");
fwrite(&r, Tam, 1, p);
fclose(p);
}
/*Funcao que determina a posicao de inclusao de um novo registro*/
long PosicaoDeInclusao(struct TRegistro Reg, char s[12])
{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
p = fopen(s, "rb");
Byte = 0;
Tam = sizeof(r);
fread(&r, Tam, 1, p);
while ((!feof(p)) && (strcmp(Reg.Mat, r.Mat) > 0))
{
Byte = Byte + Tam;
fread(&r, Tam, 1, p);
}
fclose(p);
return (Byte);
}

/*Funcao que inclui um registro num arquivo ordenado de modo que ele se
mantenha ordenado*/
void IncluiRegistroOrdenado(struct TRegistro Reg, char s[12])
{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
Tam = sizeof(r);
Byte = PosicaoDeInclusao(Reg, s);
CopiaUltimoRegistro(s);

219
Respostas dos exercícios propostos

p = fopen(s, "rb+");
fseek(p, -3*Tam, SEEK_END);
fgetpos(p, &Pos);
while (Pos >= Byte)
{
fsetpos(p, &Pos);
fread(&r, Tam, 1, p);
fgetpos(p, &Pos);
fsetpos(p, &Pos);
fwrite(&r, Tam, 1, p);
fgetpos(p, &Pos);
Pos = Pos - 3*Tam;
}
fsetpos(p, &Byte);
fwrite(&Reg, Tam, 1, p);
fclose(p);
}

void GeraArquivoOrdenado(char s1[12], char s2[12], char s[12])


{
int Tam;
FILE *p1, *p2, *p;
struct TRegistro r;
Tam = sizeof(r);
p1 = fopen(s1, "rb");
p = fopen(s, "wb");
fread(&r, Tam, 1, p1);
while (!feof(p1))
{
fwrite(&r, Tam, 1, p);
fread(&r, Tam, 1, p1);
}
fclose(p);
p2 = fopen(s2, "rb");
while (!feof(p2))
{
fread(&r, Tam, 1, p2);
if (ConsultaRegistro(r.Mat, s) == -1)
IncluiRegistroOrdenado(r, s);
}
fclose(p1);
fclose(p2);
fclose(p);
}

220
Respostas dos exercícios propostos

main()
{
struct TRegistro NovoRegistro;
char NomeArq1[12], NomeArq2[12], NomeArq[12];
printf("Digite os nomes dos arquivos a serem reunidos\n");
scanf("%s", NomeArq1);
scanf("%s", NomeArq2);
printf("Digite o nome do novo arquivo");
scanf("%s", NomeArq);
GeraArquivoOrdenado(NomeArq1, NomeArq2, NomeArq);
}
6. /*Programa que gera um arquivo contendo os registros comuns a dois arquivos
de mesma estrutura*/
#include <stdio.h>
#include <string.h>
struct TRegistro
{
char Cpf[12];
char Nome[40];
};
/*Funcao que verifica se um registro com matricula dada pertence ao arquivo*/
int ConsultaRegistro(char s[12], char s1[12])
{
FILE *p;
int Achou = 0;
struct TRegistro r;
fpos_t Byte;
p = fopen(s1, "rb");
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
while (!feof(p) && !Achou)
if (strcmp(s, r.Cpf) == 0)
Achou = 1;
else
{
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
}
if (Achou == 0)
return (-1);
else
return(Byte);
}
221
Respostas dos exercícios propostos

void CruzaArquivos(char s1[12], char s2[12], char s[12])


{
long Tam;
struct TRegistro r;
FILE *p, *p1, *p2;
Tam = sizeof(r);
p1 = fopen(s1, "rb");
p2 = fopen(s2, "rb");
p = fopen(s, "wb");
fread(&r,Tam, 1, p1);
while (!feof(p1))
{
if (ConsultaRegistro(r.Cpf, s2) != -1)
fwrite(&r,Tam, 1, p);
fread(&r,Tam, 1, p1);
}
fclose(p1);
fclose(p2);
fclose(p);
}

main()
{
char NomeArq1[12], NomeArq2[12];
printf("Digite o nome do arquivo \n");
scanf("%s", NomeArq1);
printf("Digite o nome do segundo arquivo");
scanf("%s", NomeArq2);
CruzaArquivos(NomeArq1, NomeArq2, "RegCom");
}
7. /*Funcao que permuta as posições de dois registros de um arquivo*/
int ConsultaRegistro(char s[12], char s1[12])
{
FILE *p;
int Achou = 0;
struct TRegistro r;
fpos_t Byte;
p = fopen(s1, "rb");
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
while (!feof(p) && !Achou)
if (strcmp(s, r.Mat) == 0)
Achou = 1;
222
Respostas dos exercícios propostos

else
{
fgetpos(p, &Byte);
fread(&r, sizeof(r), 1, p);
}
if (Achou == 0)
return (-1);
else
return(Byte);
}

void TrocaRegistros(char s1[12], char s2[12], char s[12])


{
int Tam;
FILE *p;
struct TRegistro r1, r2;
fpos_t Byte1, Byte2;
p = fopen(s, "rb+");
Tam = sizeof(r1);
Byte1 = ConsultaRegistro(s1, s);
if (Byte1 != -1)
{
Byte2 = ConsultaRegistro(s2, s);
if (Byte2 != -1)
{
fsetpos(p, &Byte1);
fread(&r1, Tam, 1, p);
fsetpos(p, &Byte2);
fread(&r2, Tam, 1, p);
fsetpos(p, &Byte1);
fwrite(&r2, Tam, 1, p);
fsetpos(p, &Byte2);
fwrite(&r1, Tam, 1, p);
}
else
printf("Registro nao encontrado");
}
else
printf("Registro nao encontrado");
fclose(p);
}
8. /*Função para ordenar um arquivo*/
/*Funcao que copia o ultimo registro no final do arquivo*/
void CopiaUltimoRegistro(char s[12])
223
Respostas dos exercícios propostos

{
struct TRegistro r;
FILE *p;
long Byte, Tam;
p = fopen(s, "rb");
Tam = sizeof(r);
fseek(p, -Tam, SEEK_END);
fgetpos(p, &Byte);
fsetpos(p, &Byte);
fread(&r, Tam, 1, p);
fclose(p);
p = fopen(s, "ab");
fwrite(&r, Tam, 1, p);
fclose(p);
}

/*Funcao que determina a posicao de inclusao de um novo registro*/


long PosicaoDeInclusao(struct TRegistro Reg, char s[12])
{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
p = fopen(s, "rb");
Byte = 0;
Tam = sizeof(r);
fread(&r, Tam, 1, p);
while ((!feof(p)) && (strcmp(Reg.Mat, r.Mat) > 0))
{
Byte = Byte + Tam;
fread(&r, Tam, 1, p);
}
fclose(p);
return (Byte);
}
/*Funcao que inclui um registro num arquivo ordenado de modo que ele se
mantenha ordenado*/
void IncluiRegistroOrdenado(struct TRegistro Reg, char s[12])
{
struct TRegistro r;
long Tam, Byte, Pos;
FILE *p;
Tam = sizeof(r);
Byte = PosicaoDeInclusao(Reg, s);
CopiaUltimoRegistro(s);

224
Respostas dos exercícios propostos

p = fopen(s, "rb+");
fseek(p, -3*Tam, SEEK_END);
fgetpos(p, &Pos);
while (Pos >= Byte)
{
fsetpos(p, &Pos);
fread(&r, Tam, 1, p);
fgetpos(p, &Pos);
fsetpos(p, &Pos);
fwrite(&r, Tam, 1, p);
fgetpos(p, &Pos);
Pos = Pos - 3*Tam;
}
fsetpos(p, &Byte);
fwrite(&Reg, Tam, 1, p);
fclose(p);
}

void OrdenaArquivo(char s[12])


{
int Tam;
FILE *p, *Aux;
struct TRegistro r;
Tam = sizeof(r);
p = fopen(s, "rb");
Aux = fopen("Temp.tmp", "wb");
fread(&r, Tam, 1, p);
while (!feof(p))
{
IncluiRegistroOrdenado(r, "Temp.tmp");
fread(&r, Tam, 1, p);
}
fclose(p);
fclose(Aux);
remove(s);
rename("Temp.tmp", s);
}
9. /*Função que remove comentarios, linha a linha, de um programa em C*/
void DeletaComentarios(char *s)
{
int Pos1, Pos2;
FILE *p, *Aux;
char Linha[80];
int i, Tamanho;
225
Respostas dos exercícios propostos

p = fopen(s, "rt");
Aux = fopen("Temp.tmp", "wt");
Tamanho = TamanhoArquivo(s);
i = 0;
while (i < Tamanho)
{
fgets(Linha, 80, p);
i = i + strlen(Linha);
Pos1 = Pos(Linha, "/*");
if (Pos1 != -1)
{
Pos2 = Pos(Linha, "*/");
if (Pos2 != -1)
DeletaCaracteres(Linha, Pos2 - Pos1 + 3, Pos1);
}
fwrite(Linha, strlen(Linha), 1, Aux);
}
fclose(p);
fclose(Aux);
remove(s);
rename("Temp.tmp", s);
}

Capítulo 10
1. /*Função que calcula a média aritmética de uma relação de números armazenada
num lista*/
float Media()
{
float Md = 0;
int i = 0;
Prox = Inicio;
while (Prox != NULL)
{
i = i + 1;
Md = Md + (*Prox).Valor;
Prox = (struct TElemento *) (*Prox).p;
}
return(Md/i);
}
2. /*Função que insere um elemento numa lista ordenada*/

226
Respostas dos exercícios propostos

/*Função que determina a posição de inserção de um elemento numa lista


ordenada*/
struct TElemento *Posicao(int n)
{
struct TElemento *Aux, *Ant;
int i = 0;
Prox = Inicio;
Ant = Inicio;
while ((Prox != NULL) && ((*Prox).Valor > n))
{
Ant = Prox;
i = i + 1;
Prox = (*Prox).p;
}
return(Prox);
}

void Insere(int n)
{
struct TElemento *Aux, *Ant;
Aux = Posicao(n);
if (Aux == NULL)
{
Prox = (struct TElemento *)malloc(5);
(*Prox).Valor = n;
(*Ant).p = Prox;
(*Prox).p = NULL;
}
else
if (Ant != Prox)
{
Prox = (struct TElemento *)malloc(5);
(*Prox).Valor = n;
(*Prox).p = (*Ant).p;
(*Ant).p = Prox;
}
else
{
Prox = (struct TElemento *)malloc(5);
(*Prox).Valor = n;
(*Prox).p = Inicio;
Inicio = Prox;
}
}

227
Bibliografia
Dijkstra, E. W., A Discipline of Programiming. Prentice-Hall. New Jersey, 1975.
Evaristo, J. e Crespo, S, Aprendendo a Programar Programando numa Linguagem
Algorítmica Executável (ILA). Editora Book Express, Rio de Janeiro, 2000.
Evaristo, J., Programando com Pascal (Terceira Edição). Editora Book Express, Rio de
Janeiro, 2004
Evaristo, J., Introdução `a Algebra Abstrata. Editora da Universidade Federal de
Alagoas (EDUFAL). Alagoas, 2002.
Hancock, L e Krieger M., Manual de Linguagem C. Editora Campus. Rio de Janeiro,
1985.
Kernighan, B. W. e Ritchie, D. M., C A Linguagem de Programação Padrão ANSI.
Editora Campus. Rio de Janeiro, 1989.
Knuth, D. E., The Art of Computer Programming, volume 2, Seminumerical Algorithms,
Addison-Wesley Publishing Company. USA, 1988.
Kowaltowski, T. & Lucchesi, C., Conceitos Fundamentais e Teoria da Computação.
Anais do II WEI. Minas Gerais, 1994
Mizrahi, V. V., Treinamento em Linguagem C. McGraw-Hill. São Paulo, 1990.
Norton, P., Introdução à Informática. Makron Books. Sãp Paulo, 1996.
Rangel, J. L., Os Programas de Graduação em Linguagens de Programação. Anais do II
WEI. Minas Gerais, 1994.
Schildt, H., C Avançado Guia do Usuário. McGraw-Hill. São Paulo, 1987.
Schildt, H., Linguagem C Guia do Usuário. McGraw-Hill. São Paulo, 1986.
Szwarcfiter, J. L. & Markenzon, Estruturas de Dados e seus Algoritmos. LTC Editora.
Rio de Janeiro, 1994.
Wirth, N., Algorithms & Data Structures. Prentice-Hall. New-Jersey, 1986.

228
Índice Remissivo
A D
algoritmo de Euclides................................77 decomposição em fatores primos ......81, 123
alocação dinâmica da memória ..............163 desvio médio ...........................................110
amplitude.................................................122 desvio padrão...........................................122
apontadores ...............................................91 diagonal principal ...................................118
argumentos ................................................83 dígito verificador.....................................140
arquivo binário ........................................145 divisor próprio ...........................................65
arquivo de registros .................................145 double........................................................20
ativação de uma função .............................84
E
B
endentação.................................................47
binary digit ..................................................2 endereço de uma variável ..........................91
bit 2 estrutura...................................................143
BubbleSort...............................................130 estrutura de decisão ...................................45
busca........................................................126 estrutura de seleção ...................................45
byte ..............................................................2 expressão de recorrência............................96
expressões lógicas .....................................23
C
F
cadeia de caracteres .................................132
campos ....................................................143 fatorial .......................................................96
caractere nulo ..........................................133 FILE ........................................................146
central processing unit ................................1 float ...........................................................20
char............................................................20 fluxo ........................................................146
chave .......................................................146 função atof...............................................136
função atoi...............................................136
C
função atol...............................................136
código ASCII ...............................................2 função calloc ...........................................163
códigos de conversão ................................27 função fclose............................................148
códigos especiais .......................................33 função feof ...............................................149
comando de atribuição ..............................34 função fgetpos..........................................151
comando de seleção...................................45 função fopen ............................................146
comando do while .....................................71 função fread.............................................148
comando for ..............................................62 função free...............................................164
comando if.................................................45 função fseek .............................................153
comando if else .........................................47 função fsetpos ..........................................152
comando switch.........................................56 função ftell...............................................151
comando while ..........................................67 função fwrite............................................147
comentários ...............................................51 função main ...............................................85
compiladores .............................................14 função malloc ..........................................163
componentes de um vetor........................106 função printf ..............................................28
condição de escape ....................................96 função realloc..........................................163
constante....................................................21 função scanf...............................................26
consulta ...................................................126 função strcat ............................................135
função strcmp ..........................................134

229
função strcpy ...........................................135 P
função strlen............................................134
parâmetros .................................................83
função strlwr ...........................................135
passagem de parâmetro por valor ..............88
função strncpy .........................................135
pesquisa ...................................................126
função strstr ............................................135
pesquisa binária .......................................127
função strupr ...........................................135
pesquisa sequencial .................................126
G pilha de memória .......................................97
ponteiro de arquivo..................................146
gets ..........................................................133
ponteiros ....................................................91
H produto escalar ........................................122
programa fonte ..........................................14
hardware ...................................................16
programa objeto........................................14
I protótipo de uma função............................83
identificador ..............................................19 R
InsertSor ..................................................131
recursividade .............................................95
int 20
registro.....................................................145
interpretadores ..........................................14
Resolução de problemas ..............................4
L return .........................................................83
rewind......................................................154
laços ..........................................................62
linguagem de alto nível .............................14 S
linguagem de máquina ................................2
SEEK_CUR .............................................154
long............................................................20
SEEK_END .............................................154
M SEEK_SET...............................................154
SelectSort ................................................128
matriz identidade de ordem n..................118 semânticade um comando..........................15
matriz quadrada ......................................118 seqüência de Fibbonaci.............................81
máximo divisor comum.............................77 série harmônica.........................................82
membros..................................................143 sintaxe de um comando .............................15
memória.......................................................1 sistema binário de numeração.....................3
mínimo múltiplo comum ...........................78 sistema operacional...................................16
modularização de um programa ................86 sizeof........................................................107
módulo ......................................................23 software .....................................................16
molde.........................................................91 solução iterativa.........................................98
multiplicidade............................................81 static ........................................................101
N stdou ........................................................158
stdprn.......................................................158
norma de um vetor...................................122 string .......................................................132
notação científica.......................................21
NULL ........................................................92 T
número primo ............................................68 tipo de dado ...............................................19
O Torres de Hanói .........................................98
traço ........................................................118
operador condicional ternário....................48 typedef .....................................................144
operador de endereço ..........................26, 91
operadores aritméticos...............................22 U
operadores lógicos.....................................23 unidade de entrada.......................................1
operadores relacionais ...............................23

230
unidade de processamento central ..............1 variáveis locais ..................................83, 101
unidade de saída ..........................................1 variáveis simples .......................................19
variável global .........................................101
V
vetores .....................................................106
variáveis automáticas ..............................101 void............................................................86
variáveis dinâmicas .................................101
variáveis estáticas ....................................101

231

Você também pode gostar