Você está na página 1de 261

Sumario

1 Introducao 6
1.1 Historico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Arquitetura de Computadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.1 Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.2 Processador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3 Algoritmos e Programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.4 Tecnica de Desenvolvimento de Programas . . . . . . . . . . . . . . . . . . . . . 11
1.5 Partes de um Programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.6 Traducao de Programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.1 Compilacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.6.2 Interpretacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.7 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.8 Exerccios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2 Conceitos Basicos 18
2.1 Variaveis e Celulas de Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.2 Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.3 Comando de Atribuicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4 Tipos de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.4.1 Declaracao de Variaveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.4.2 Tipo Inteiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.4.3 Tipo Ponto Flutuante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4.4 Tipo Booleano . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.4.5 Tipo Caractere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.4.6 Conversao de Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.5 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.6 Expressoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6.1 Expressoes Aritmeticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6.2 Expressoes Relacionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.6.3 Expressoes Logicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.7 Comando de Entrada de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

1
2 SUMARIO

2.8 Comando de Sada de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36


2.9 Comandos de Selecao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.9.1 Comando de selecao simples . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.9.2 Comando de selecao dupla . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.9.3 Comando de selecao multipla . . . . . . . . . . . . . . . . . . . . . . . . . 46
2.10 Comandos de Repeticao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.10.1 Comando de repeticao com pre-condicao . . . . . . . . . . . . . . . . . . . 49
2.10.2 Comando de repeticao com pos-condicao . . . . . . . . . . . . . . . . . . . 52
2.10.3 Comando de repeticao condensado . . . . . . . . . . . . . . . . . . . . . . 55
2.11 Problema dos Lotes Encaixantes . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
2.12 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
2.13 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.14 Exerccios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68

3 Modularizacao 70
3.1 Introducao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.2 Subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3.3 Partes de um Subprograma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.3.1 Cabecalho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.3.2 Dicionario de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.3.3 Corpo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
3.3.4 Comentarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.4 Chamada de subprogramas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.5 Passagem de parametros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.6 Retorno de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.6.1 Encerramento antecipado de execucao . . . . . . . . . . . . . . . . . . . . 84
3.7 Funcoes sem lista de parametros . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
3.8 Funcoes sem retorno de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
3.9 Recursividade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3.9.1 Implementacao nao recursiva equivalente . . . . . . . . . . . . . . . . . . 90
3.10 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
3.11 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
3.12 Exerccios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
3.13 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

4 Tipos Abstratos de Dados 109


4.1 Tecnicas de Programacao Top-down e Bottom-up . . . . . . . . . . . . . . . . . . 109
4.2 Tipos Compostos Heterogeneos (Estruturas) . . . . . . . . . . . . . . . . . . . . . 110
4.2.1 Definicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.2.2 Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
4.3 Tipos Abstratos de Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
4.3.1 Definicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
SUMARIO 3

4.3.2 Definicao de Atributos de um TAD . . . . . . . . . . . . . . . . . . . . . . 120


4.3.3 Definicao de Operacoes de um TAD . . . . . . . . . . . . . . . . . . . . . 120
4.3.4 Uso do TAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
4.3.5 Tipos de TADs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
4.4 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
4.5 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
4.6 Lista de Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
4.7 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

5 Vetores 135
5.1 Vetores e sua importancia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
5.2 Representacao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
5.3 Definicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
5.3.1 Definicao do Tamanho do Vetor . . . . . . . . . . . . . . . . . . . . . . . . 139
5.4 Operacoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
5.4.1 Acesso indevido . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
5.5 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
5.6 TAD Implementacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
5.6.1 Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
5.6.2 Operacoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
5.7 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
5.8 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
5.9 Lista de Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
5.10 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

6 Matrizes 164
6.1 Introducao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
6.2 Definicao e Acesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
6.2.1 Definicao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
6.2.2 Acesso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
6.3 O TAD implementacional tMatriz . . . . . . . . . . . . . . . . . . . . . . . . . . 168
6.3.1 Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
6.3.2 Operacoes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
6.4 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
6.5 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.6 Exerccios Propostos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.7 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
6.8 Topicos Avancados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
4 SUMARIO

7 Apontadores 187
7.1 Variaveis Apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
7.2 A Sintaxe dos Apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
7.2.1 Operador Endereco de Memoria . . . . . . . . . . . . . . . . . . . . . . . 190
7.2.2 O Operador Seta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
7.3 Acesso a Variavel por Meio de Apontadores . . . . . . . . . . . . . . . . . . . . . 191
7.4 Uso de Apontadores nas Passagens de Parametros . . . . . . . . . . . . . . . . . 192
7.5 Alocacao Dinamica de Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
7.6 Problemas Gerados por Apontadores . . . . . . . . . . . . . . . . . . . . . . . . . 198
7.6.1 Apontadores Nao Inicializados . . . . . . . . . . . . . . . . . . . . . . . . 198
7.6.2 Objetos Pendentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
7.6.3 Referencia Pendente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
7.6.4 Programacao Macarronica . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
7.7 TAD Implementacional Lista Encadeada tLista . . . . . . . . . . . . . . . . . . . 200
7.7.1 Definicao do Tipo tNo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
7.7.2 Atributos de tLista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
7.7.3 Operacoes de tLista . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
7.7.4 Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
7.8 Exerccios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
7.9 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
7.10 Lista de Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
7.11 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228

8 Arquivos 235
8.1 Variaveis Transientes X Variaveis Persistentes . . . . . . . . . . . . . . . . . . . . 235
8.2 Tipos de Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
8.2.1 Tipos de Arquivos - Arquivos Texto . . . . . . . . . . . . . . . . . . . . . 236
8.2.2 Tipos de Arquivos - Arquivos Binarios . . . . . . . . . . . . . . . . . . . . 237
8.3 Definicao de arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
8.4 Operacao sobre arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
8.4.1 Abertura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
8.4.2 Fechamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
8.5 Operacoes sobre arquivos texto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
8.5.1 Leitura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
8.5.2 Escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
8.6 Operacoes sobre arquivos binarios . . . . . . . . . . . . . . . . . . . . . . . . . . 244
8.6.1 Leitura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
8.6.2 Escrita . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
8.7 Outras funcoes uteis para arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . 246
8.7.1 feof() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
8.7.2 fseek() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
8.8 Exercicios Resolvidos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
SUMARIO 5

8.8.1 O Tipo Abstrato de Dados TDicionario . . . . . . . . . . . . . . . . . . . 250


8.9 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
8.10 Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
8.11 Trabalhos Sugeridos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
Captulo 1

Introducao
Co-autor:

Andre Boechat

Objetivos:

Apresentar um breve historico da Computacao;

Apresentar nocoes sobre arquitetura de computadores, como funcionam o processador e a


memoria;

Definir o que sao algoritmos e programas, alem de apresentar algumas tecnicas para de-
senvolve-los;

Definir as partes de um programa e a importancia da documentacao;

Introduzir o conceito de traducao de programas.

Basicamente, pode-se considerar a programacao de computadores como a forma que se da


o relacionamento entre a maquina e o homem. Para programar de forma correta e eficiente,
muitas vezes nao basta conhecer apenas os comandos de determinadas operacoes, e necessario
saber como a maquina, um ser nao pensante, faz para compreender e executa-los. Assim, este
captulo visa apresentar alguns conceitos fundamentais para o entendimento da programacao,
desenvolvidos juntamente com a area mais recente da ciencia, a Computacao.

1.1 Historico
Dado o alto grau tecnologico dos computadores atuais, pode ser difcil imaginar que os primeiros
computadores eram totalmente mecanicos. Diversos tipos foram projetados e construdos ao
longo da evolucao, chegando aos modernos computadores digitais; porem, alguns se destacam
pela inovacao e complexidade que marcaram suas epocas.

6
1.1. HISTORICO 7

A primeira maquina programavel que se tem notcia foi construda pelo professor de ma-
tematica da Universidade de Cambridge, Charles Babbage (1792-1871). A maquina analtica,
como ficou conhecida, era totalmente mecanica, composta basicamente por engrenagens que
formavam quatro componentes: a memoria, a unidade de calculo ou computacao, a unidade
de entrada e a de sada. A unidade de computacao recebia operandos da memoria para reali-
zar sobre eles as operacoes de soma, subtracao, multiplicacao ou divisao, e depois armazenar o
resultado na memoria.
Como a maquina analtica executava as instrucoes lidas pela unidade de entrada, era possvel
executar diferentes sequencias de calculos, bastando para isso que um programa diferente fosse
utilizado. Assim, a primeira pessoa no mundo a programar um computador foi a jovem Ada
Augusta Lovelace, contratada pelo proprio Babbage a fim de produzir o software necessario para
o funcionamento da maquina.
A procura por maquinas calculadoras cresceu com a Segunda Guerra Mundial, estimulando
o surgimento dos primeiros computadores eletronicos. O professor de fsica da Universidade da
Pensilvania, John Mauchley, junto com seu aluno de mestrado, J. Presper Eckert, construiu
um computador chamado ENIAC (Electronic Numerical Integrator And Computer )[2], o qual
detinava-se ao computo de trajetorias taticas que exigissem conhecimento substancial em ma-
tematica. O ENIAC tinha 18.000 valvulas e 1.500 reles, pesava 30 toneladas e consumia 140
quilowatts de energia eletrica. Para programar o ENIAC, era necessario ajustar a posicao de
6.000 chaves de varias posicoes e conectar um numero imenso de soquetes por meio de uma ver-
dadeira floresta de cabos. Este gigantesco computador so ficou pronto em 1946, apos o termino
da guerra.
Um dos pesquisadores envolvidos no projeto do ENIAC, John von Neumann, construiu para
o Instituto de Estudos Avancado de Princeton (Princeton Institute of Advanced Studies IAS )
a maquina IAS[3], a qual ainda e a base de praticamente todas as maquina atuais. Ele imaginou
que os programas poderiam ser representados em formato digital na memoria, junto com os
dados.
A invencao do transstor e o desenvolvimento de circuitos integrados revolucionaram os pro-
jetos de computadores do final da decada de 1950, tornando obsoletos os computadores valvula-
dos. Nas decadas de 1960 e 1970, as famlias de computadores da IBM1 (International Business
Machines), System/360, e da DEC2 (Digital Equipament Corporation), PDP-11, dominavam o
mercado.
Nesse perodo surgiu uma das linguagens de programacao mais usadas para o desenvolvi-
mento de softwares e tambem adotada neste livro, a linguagem C[1]. Ela e, ainda hoje, muito uti-
lizada para a criacao de programas diversos, como processadores de texto, planilhas eletronicas,
programas para a solucao de problemas de engenharia, e muitos outros.
Diversos fatores, como a criacao de linguagens de programacao mais semelhantes a linguagem
humana3 , o que facilitava a vida dos programadores, e a integracao de circuitos em escala muito
alta, o que aumentou o desempenho e diminuu o tamanho das maquinas, deram incio a era
1
http://www.ibm.com
2
http://www.hp.com
3
Essas linguagens sao chamadas de linguagens de alto nvel
8 CAPITULO 1. INTRODUCAO

dos computadores pessoais. E foi nesse contexto, na decada de 80, que a IBM construiu o
computador mais vendido de toda a historia, o Personal Computer o famoso PC.
O processador utilizado no PC foi construdo por uma promissora empresa da epoca, a Intel4 .
E a versao inicial desse computador vinha com o sistema operacional MS-DOS fornecido por
outra empresa, a recem-criada Microsoft Corporation.

1.2 Arquitetura de Computadores


Para conhecer um pouco mais sobre o funcionamento dos computadores, e apresentada nesta
Secao uma nocao basica sobre processadores e memorias.

1.2.1 Memoria
A memoria e a parte do computador onde as operacoes a serem executadas pelo computador
(instrucoes) e as informacoes a serem processadas pelo mesmo (dados) sao armazenados.
Na memoria, o processador le e escreve informacoes ao executar as instrucoes de um pro-
grama. Existem dois tipos de memoria: a memoria principal e a secundaria. A principal,
conhecida tambem como RAM (Random Access Memory), possui as seguintes caractersticas:

Armazena dados e instrucoes do programa em execucao;

Proporciona ao computador acesso rapido aos dados e instrucoes armazenados por ela;

So mantem as informacoes armazenadas enquanto o computador estiver ligado.

A memoria secundaria geralmente possui maior capacidade de armazenamento de dados e


instrucoes, porem o tempo necessario para acessa-los e bem maior quando comparado ao da
memoria principal. As informacoes permanecem armazenadas mesmo apos o desligamento do
computador. Discos rgidos e CD-ROMs sao exemplos de memorias secundarias.
A unidade basica da memoria e o digito binario, conhecido como bit. Um bit pode assumir
apenas dois valores, normalmente 0 ou 1.
A memoria e formada por um conjunto de celulas, ou posicoes, sendo que cada uma pode
guardar uma informacao e possui um numero de reconhecimento, conhecido como endereco
de memoria. E por meio desse numero de endereco que os programas podem acessar uma
determinada celula. Se uma celula possuir k bits, ela podera armazenar qualquer uma das 2k
combinacoes possveis para os bits. A Figura 1.1 mostra um exemplo de memoria com seis
celulas enderecaveis, onde cada celula possui dezesseis bits.

1.2.2 Processador
O processador e o cerebro do computador. Basicamente, o processador e responsavel por
buscar instrucoes na memoria, decodifica-las para determinar seus operandos (dados que serao
4
http://www.intel.com
1.3. ALGORITMOS E PROGRAMAS 9

Figura 1.1: Exemplo de uma memoria de 16 bits e 6 enderecos.

usados ao processar a instrucao) e quais operacoes devem ser realizadas com os mesmos, e
executar tais operacoes. Essas tarefas compoem o processo de execucao de um programa.

1.3 Algoritmos e Programas


Apesar do nome pouco comum, diversos algoritmos sao executados por pessoas comuns todos os
dias. Ao escovar os dentes, fazer um bolo ou trocar o pneu de um carro, diversos procedimentos
sao feitos em sequencia, seguindo, muitas vezes, uma certa ordem logica.
Para exemplificar a execucao de um algoritmo, descreve-se a seguir alguns passos necessarios
para fazer algo simples, cotidiano, como o ato de escovar os dentes.

1. Pegar a escova e a pasta de dentes;

2. Colocar um pouco de pasta sobre as cerdas da escova;

3. Escovar os dentes do maxilar inferior;

4. Escovar os dentes do maxilar superior;

5. Expelir da boca o excesso de espuma;

6. Bochechar um pouco de agua;

7. Lavar a escova e guarda-la;

8. Enxugar o rosto.

Apos conhecer um exemplo comum de algoritmo, torna-se mais facil compreender a sua definicao:

Algoritmo e uma sequencia de operacoes que deve ser executada em uma


ordem definida e nao-ambgua, com o proposito de solucionar um deter-
minado problema.
10 CAPITULO 1. INTRODUCAO

Apesar de conseguirem executar operacoes muito complexas, os computadores nao sao do-
tados da mesma capacidade de compreensao que os humanos. Uma sequencia de tarefas consi-
derada simples e obvia para uma pessoa qualquer pode ser incompreensvel e pouco detalhada
para um computador.
Voltando ao exemplo da escovacao, um computador poderia considera-lo incompleto, pois
ha diversas questoes sobre como algum passo pode ser executado, por exemplo:

1. Onde esta a escova a ser usada?

2. Quanto, exatamente, se deve colocar de pasta sobre as cerdas?

3. O que fazer se nao houver pasta? Escovar mesmo sem a pasta ou interromper o processo
de escovacao?

Questoes como essas sao facilmente contornadas por um humano, decididas instantaneamente
de acordo com o ambiente que o cerca. Entao, para que uma maquina tambem possa decidir
o que fazer em situacoes semelhantes, e necessario que se estabelecam as condicoes iniciais
(condicoes de entrada) e as finais (condicoes de sada) do problema. Dessa forma, ela sabera
quais ferramentas poderao ser usadas para atingir a condicao de sada desejada.
Assim, o problema da escovacao seria mais bem definido da seguinte maneira:

Condicoes de Entrada: Dentes sujos com restos de alimentos, uma escova dental em condicoes
de uso, 90 gramas de creme dental e 300 mililitros de agua tratada.

Condicoes de Sada: Dentes limpos (sem restos de alimentos visveis), uma escova dental em
condicoes de uso e 85 gramas de creme dental. Toda a quantidade de agua deve ser
utilizada.

Portanto, para um computador, os algoritmos definem o conjunto de atividades que ele deve
desempenhar para solucionar um problema.
Contudo, tao importante quanto saber o que escrever para a maquina e saber como
escrever. Para que um computador possa executar um algoritmo, e necessario que esse algoritmo
seja traduzido para uma linguagem de programacao, geralmente incompreensvel para a maioria
das pessoas. Ao se traduzir um algoritmo para uma linguagem de programacao, obtem-se um
programa.

Embora os computadores modernos sejam capazes de executar operacoes difceis e


complexas, eles sao maquinas doceis e de uma inteligencia restrita. Eles devem ser
ensinados exatamente o que fazer, e as instrucoes devem ser feitas em uma linguagem
simples, precisa e limitada que eles possam compreender. Estas instrucoes sao mais
conhecidas como programa (Darmell,1988).
1.4. TECNICA DE DESENVOLVIMENTO DE PROGRAMAS 11

1.4 Tecnica de Desenvolvimento de Programas


Na maioria das vezes, o ato de programar nao e uma tarefa simples e facil. A sintaxe das
linguagens de programacao e bem rgida e deve ser respeitada fielmente para que o computador a
compreenda. Esquecer de um ; no lugar onde e necessario, por exemplo, significa comprometer
a execucao de todo o programa (na verdade, o computador pode nem comecar a executa-lo). Ou
pior, um programa teoricamente correto, que nao apresente erros de sintaxe, pode fornecer um
resultado incorreto. Encontrar o erro torna-se um enorme problema a medida que o programa
cresce em tamanho e complexidade.
Para contornar a complexidade dos programas, os construtores de software costumam fazer
uso de uma poderosa tecnica de programacao, conhecida como tecnica dos refinamentos suces-
sivos5 . Ela consiste basicamente em dividir um processo complexo em processos-componentes
menores, especificando apenas a entrada e a sada de cada um deles. E se, mesmo assim,
alguns processos menores continuarem complexos, repete-se a divisao no interior dos processos-
componentes, gerando processos cada vez mais simplificados.
Alem de facilitar a depuracao de erros, essa tecnica pode tornar um complexo programa em
uma uniao de programas menores e mais simples. A divisao tambem possibilita outra grande
vantagem, o reuso de codigo, ou seja, o uso de um mesmo trecho de programa para realizar o
mesmo tipo de tarefa em diferentes ocasioes dentro de um programa maior.
Um exemplo pratico do uso dos refinamentos sucessivos segue abaixo:

= Algoritmo Escovacao dentaria:

1. Pegar a escova e a pasta de dentes;

2. Colocar um pouco de pasta sobre as cerdas da escova;

3. Escovar os dentes do maxilar inferior;

4. Escovar os dentes do maxilar superior;

5. Expelir da boca o excesso de espuma;

6. Bochechar um pouco de agua;

7. Lavar a escova e guarda-la;

8. Enxugar o rosto.

= Processo-componente pegar a escova e a pasta de dentes:

1. Enquanto nao encontrar a escova e o tubo de pasta, continuar procurando por cada gaveta
do armario;
5
Tambem conhecida como dividir para conquistar.
12 CAPITULO 1. INTRODUCAO

2. Caso tenham acabado as gavetas e nao tenha encontrado a escova e o tubo, interromper a
tarefa.

= Processo-componente escovar os dentes do maxilar inferior:

1. Com as cerdas da escova na posicao vertical, fazer movimentos de vai-e-vem na regiao


superior dos dentes;

2. Com as cerdas na posicao horizontal, escovar a regiao frontal dos dentes;

3. Com as cerdas na posicao horizontal, escovar a regiao detras dos dentes.

= Processo-componente escovar a regiao detras dos dentes:

1. Abrir bem a boca;

2. Afastar a lngua da regiao a ser escovada;

3. Esfregar as cerdas da escova atras dos dentes.

O exemplo anterior mostra apenas parte do processo de refinamentos, que deve prosseguir
ate que cada atividade esteja suficientemente detalhada, possibilitando que o computador as
reconheca e execute.

1.5 Partes de um Programa


Como dito anteriormente, os programas podem ser comparados a diversas atividades do dia-a-
dia de qualquer pessoa. Um exemplo e o de receitas culinarias. Geralmente, essas seguem uma
determinada estrutura, como abaixo:

Nome da receita;

Ingredientes: descreve todo o material necessario para o preparo da receita;

Modo de preparo: descreve a forma de trabalhar com os ingredientes para que se obtenha
o resultado esperado;

Comentarios sobre certos procedimentos ou ingredientes a fim de detalhar alguma peculi-


aridade que o cozinheiro poderia nao conhecer previamente.

A estrutura de um bom programa segue um modelo semelhante. Basicamente, um programa


deve conter quatro partes:

Cabecalho: contem informacoes sobre o programa, como o seu nome;


1.5. PARTES DE UM PROGRAMA 13

Dicionario de dados: define quais sao os dados manipulados pelo programa;

Corpo: define os procedimentos que o programa deve executar;

Documentacao: explica certos aspectos nao muito claros do programa, tanto no corpo do
programa quanto no cabecalho ou no dicionario de dados.

Um dos principais objetivos deste tipo de estrutura e aumentar a legibilidade do programa, ou


seja, facilitar o entendimento dos diversos processos que o programa executa. Um programa deve
valorizar a formatacao, disposicao fsica das linhas de codigo, para facilitar seu entendimento.
Colocar cada comando em uma linha e inserir uma linha em branco entre blocos de comandos
de finalidade comum sao regras basicas para uma boa formatacao. Os programas devem ser
lidos e entendidos por quem os escreveu e por outras pessoas, uma vez que podem necessitar de
correcao, manutencao, modificacao ou apenas de serem compreendidos por um simples usuario.
Para ilustrar essa estrutura, apresenta-se a seguir um programa, Pseudocodigo 1.1, que efetua
o calculo das razes reais de uma equacao de segundo grau. Os dados de entrada do programa
sao os tres coeficientes da equacao e ele devera apresentar ao usuario as razes reais da mesma,
caso existam.

Pseudocodigo 1.1 Calculo das razes reais de uma equacao de segundo grau.
Descricao: programa que calcula as razes reais de uma equacao de segundo grau.
Dados de Entrada: os tres coeficientes da equacao.
Sada do Programa: razes reais da equacao.
Leitura dos coeficientes da equac~ao;
Calculo do discriminante (delta);
Calculo das razes pelo processo componente "Calculo das Razes";

Utilizando a tecnica dos refinamentos sucessivos, na ultima linha do Pseudocodigo 1.1 ocorre
a execucao do processo componente Calculo das Razes, que, a partir dos valores dos coeficien-
tes lidos e do discriminante calculado, encontra os valores das razes reais da equacao. No geral,
o processo componente Calculo das Razes deve conter os passos descritos no Pseudocodigo
1.2.

Pseudocodigo 1.2 Processo componente para o calculo das razes.


Processo-componente "Calculo das Razes":
Verificar a exist^encia de razes reais
Se possui razes reais:
Exiba para o usuario os valores da razes;
Sen~
ao:
Exiba um alerta sobre a n~ ao exist^
encia de razes reais;
FIM-Processo componente "Calculo das Razes"
14 CAPITULO 1. INTRODUCAO

No Exemplo 1.1, o programa para o calculo das razes esta transcrito para a linguagem de
programacao C. Deve-se destacar que o texto escrito entre os smbolos /* e */ e sempre
ignorado pelo computador, nao alterando em nada o funcionamento do programa. Porem, esse
texto serve para comentar o codigo e deixa-lo mais claro. Pode-se faze-los tambem utilizando o
smbolo //, que transforma em comentario tudo aquilo que estiver a sua direita, ate o final
da linha.

1 /*
2 Programa para calculo das razes de segundo grau .
3 Dados de entrada : os coeficientes a , b e c de uma equac~ ao
4 da forma ax ^2 + bx + c = 0
5 Dados de sada : imprime na tela as razes reais da equac~a o , caso existam .
6 Restric~
a o : N~
a o se considera o zero como um possvel valor de a .
7 */
8
9 # include < math .h >
10 # include < stdio .h >
11
12 main () {
13 float a ; // Coeficiente angular .
14 float b ; // Coeficiente linear .
15 float c ; // Termo independente .
16 float delta ; // Discriminante .
17 float raiz1 ; // A primeira raiz .
18 float raiz2 ; // A segunda raiz .
19
20 // Leitura dos coeficientes .
21 scanf ( " % f % f % f " ,&a , &b , & c ) ;
22
23 // Calculo do discriminante ( delta ) .
24 delta = b * b - 4 * a * c ;
25
26 // Calculo das razes .
27 if ( delta < 0) {
28 printf ( " A equac~
a o n~
a o possui razes reais " ) ;
29 } else {
30 raiz1 = ( - b + sqrt ( delta ) ) / (2* a ) ;
31 raiz2 = ( - b - sqrt ( delta ) ) / (2* a ) ;
32 printf ( " As razes da equac~ao s~
a o : % f e % f " , raiz1 , raiz2 ) ;
33 }
34 }

Exemplo 1.1: Programa para o calculo das razes reais de uma equacao de segundo grau.

Na linguagem C, o cabecalho dos programas sempre tem o nome main() e o incio do corpo
do programa e marcado pela chave {, assim como o final e marcado pela ultima chave }.
As linhas 9 e 10 permitem o uso dos comandos sqrt e printf (o significado desses comandos e
explicado no proximo captulo).
1.6. TRADUCAO DE PROGRAMAS 15

De acordo com a estrutura de programa apresentada no incio desta secao, as linhas 13 a


18 compoem o dicionario de dados e o restante compoe o corpo do programa. A documentacao
esta inserida no programa em forma de comentarios, como as linhas 1 a 7, 20, 23 e 26.
Vale destacar a importancia da disposicao fsica das linhas de codigo para o facil entendimento
do programa. A hierarquizacao de elementos por meio de espacos em branco e chamada de
indentacao. O trecho de programa relacionado ao calculo das razes no Exemplo 1.1 demonstra
como a indentacao pode facilitar a visualizacao da mudanca do fluxo do programa.
Normalmente, linhas de codigo com mesma indentacao sao executadas de maneira contnua,
uma apos a outra. Uma mudanca na indentacao indica uma mudanca nesse fluxo de execucao
do programa. No proximo captulo sao apresentadas diversas maneiras de mudar esse fluxo.

1.6 Traducao de Programas


Apesar do que foi dito na Secao 1.3, para ser executado pelo computador, um programa ainda
precisa ser traduzido para uma linguagem mais simples que as linguagens de alto nvel: a lingua-
gem de maquina. Chama-se de codigo fonte o arquivo que contem o conjunto de instrucoes
escritas em uma determinada linguagem de programacao, normalmente familiares ao programa-
dor. Ja o arquivo que as contem traduzidas para linguagem de maquina e chamado de codigo
objeto.
Para efetuar essa traducao, e necessario aplicar um programa, ou conjunto de programas,
que receba o codigo fonte e gere o codigo objeto. A seguir, descrevem-se dois metodos basicos
para um programa tradutor efetuar essa tarefa: a compilacao e a interpretacao de um codigo
fonte.

1.6.1 Compilacao
O processo de compilacao efetua a traducao integral do programa fonte para o codigo de maquina.
Uma vez traduzido, o programa em linguagem de maquina pode ser executado diretamente. A
Figura 1.2 ilustra esse processo.
A grande vantagem desse metodo de traducao e a rapidez na execucao dos programas, pois
nao e necessario fazer qualquer traducao durante a execucao. Porem, o codigo objeto gerado pela
compilacao e, geralmente, especfico para um determinado tipo de arquitetura de computador.
Assim, e necessario uma nova compilacao do programa para cada tipo diferente de computador.
A linguagem C, adotada neste livro, e um exemplo de linguagem de programacao compilada.

1.6.2 Interpretacao
No processo de interpretacao, cada instrucao do codigo fonte e traduzida no instante imedia-
tamente anterior a execucao dela. Desse modo, em contraste com a compilacao, a traducao e
execucao de programas interpretados podem ser vistas como um processo unico (veja as Figuras
1.2 e 1.3).
16 CAPITULO 1. INTRODUCAO

Figura 1.2: Metodo de compilacao. Figura 1.3: Metodo de interpretacao.

Apesar de ser um processo mais lento quando comparado ao metodo anterior, a interpretacao
apresenta maior flexibilidade na construcao de programas e facilidade de depuracao de codigo.
A presenca do interpretador durante a execucao permite, por exemplo, a execucao de trechos
de programas criados, alterados ou obtidos durante a propria execucao.

1.7 Resumo
O processador e o componente do computador responsavel por executar os programas
armazenados na memoria.

A memoria e responsavel por armazenar os programas e os dados. A unidade basica da


memoria e o bit.

Algoritmo e uma sequencia de operacoes que deve ser executada em uma ordem definida
e nao-ambgua, com o proposito de solucionar um determinado problema. Ao traduzir-se
um algoritmo para uma linguagem de programacao, obtem-se um programa.

A Tecnica de Refinamentos Sucessivos consiste, basicamente, em dividir sucessivamente


problemas complexos em diversos problemas menores e mais simples.

Um programa bem estruturado deve conter as seguintes partes bem definidas: cabecalho,
dicionario de dados, corpo e documentacao.
1.8. EXERCICIOS PROPOSTOS 17

Basicamente, existem dois mecanismos de traducao de programas para linguagem de


maquina: compilacao e interpretacao. Na compilacao, o codigo fonte e totalmente tradu-
zido antes de ser executado. Ja na interpretacao, a traducao do codigo fonte e a execucao
do programa ocorrem quase simultaneamente, como um processo unico.

1.8 Exerccios Propostos


1. Qual e o papel do processador?

2. Qual e o papel da memoria?

3. Considere uma memoria que possua celulas de 8 bits cada uma. Qual o numero de possveis
informacoes diferentes cada celula pode armazenar?

4. Utilizando o conceito de algoritmo apresentado no captulo, descreva, atraves de um al-


goritmo, o ato de trocar um pneu furado de um carro. Nao se esqueca de estabelecer as
condicoes iniciais do problema (por exemplo, carro com um pneu dianteiro furado, se a
chave de roda esta no carro, se o carro esta parado em uma ladeira, etc) e as condicoes de
sada (por exemplo, carro parado e frenado, etc).

5. Use a Tecnica de Refinamentos Sucessivos para detalhar as seguintes atividades:

(a) Montagem de uma barraca de camping;


(b) Preparacao de um bolo de aniversario;
(c) Calculo da distancia entre dois pontos no plano cartesiano.
Captulo 2

Conceitos Basicos
Co-autores:

Andre Boechat
Bruno Pandolfi

Objetivos:
Apresentar o conceito de variavel;

Estudar os principais tipos de dados basicos e expressoes validas;

Mostrar como e feita a definicao de variaveis e constantes;

Apresentar os comandos elementares usados em programas.


A ideia de resolver problemas formalizando a solucao atraves do emprego de algoritmos e a
espinha dorsal da programacao procedural, na qual as tarefas sao prescritas ao computador por
meio de uma sequencia de operacoes basicas.
Este captulo trata do estudo dos conceitos e operacoes elementares da programacao, forne-
cendo uma base solida que, mais adiante, serve para construir algoritmos progressivamente mais
complexos e resolver problemas cada vez mais elaborados.

2.1 Variaveis e Celulas de Memoria


Qualquer algoritmo tem por finalidade produzir algum resultado util para compor a solucao de
um problema. Os algoritmos computacionais, em sua maioria, operam sobre um conjunto de
dados para produzir novos resultados de acordo com a necessidade da tarefa em questao.
Quando corretamente transcritos para uma linguagem de programacao, os algoritmos sao
chamados de programas e podem ser executados pelo computador. E necessario, todavia, arma-
zenar as informacoes utilizadas pelos programas em um local organizado e seguro, para que as
consultas e alteracoes sejam efetuadas de maneira coerente e livre de erros. Os computadores
utilizam a memoria para armazenar os dados de um programa em execucao.

18
2.1. VARIAVEIS E CELULAS DE MEMORIA 19

Como dito na Secao 1.2.1, a memoria pode ser entendida como uma sequencia de celulas,
cada celula podendo armazenar uma porcao dos dados de um programa. Gracas a essa ordenacao
sequencial, cada celula possui um numero identificador chamado endereco, que esta relacionado
com a posicao que a celula ocupa na sequencia. Por meio dos enderecos e possvel acessar
qualquer celula para ler ou alterar seu conteudo. A Figura 2.1 esboca essa ideia, porem, em
contraste com a Figura 1.1, omite a representacao dos bits.

Figura 2.1: Modelo para a estrutura da memoria do computador, mostrando uma fracao de seis
celulas, seus enderecos e conteudos.

As seis celulas de memoria da Figura 2.1 podem guardar valores numericos ou letras em seus
interiores. Cada celula e apontada por um endereco unico e sequencial.
A estrutura da memoria favorece a execucao de suas duas operacoes basicas: leitura e escrita.
A leitura e o processo de consulta do conteudo da celula. Essa operacao nao altera o valor
guardado na celula.
A escrita e o processo de armazenamento de um novo dado na celula indicada por um
endereco conhecido. Apos a execucao da escrita, o valor que estava anteriormente armazenado
na celula e perdido.
Um algoritmo para o calculo do valor da conta mensal de energia eletrica e um bom exemplo
para compreender as operacoes basicas da memoria. Os dados de entrada desse algoritmo sao: a
leitura atual do medidor, a leitura registrada no mes anterior e o preco do quilowatt-hora (kWh)
unidade comercial de energia eletrica.
Na pratica, o que se faz para calcular o valor da conta e encontrar a diferenca entre a leitura
atual do medidor e a leitura do mes anterior e multiplicar esse valor pelo preco do quilowatt-hora.
O algoritmo computacional, por sua vez, e descrito no Pseudocodigo 2.1.
No Pseudocodigo 2.1, o computador le os conteudos das celulas 43 e 46 (que guardam os
valores das leituras atual e anterior, respectivamente), realiza a subtracao e o resultado e guar-
dado na celula 41. Posteriormente, essa diferenca e lida e seu valor multiplicado pelo conteudo
da celula 44, que guarda o valor do preco unitario do kWh. Finalmente, o valor da conta e
guardado na celula 42.
Acompanhando a descricao acima e possvel chegar a situacao encontrada na Figura 2.2.
Nos primordios da programacao, percebeu-se que acessar a memoria por meio de enderecos
era trabalhoso demais e causa constante de erros. Isso porque o programador deveria escolher os
enderecos das celulas com as quais iria trabalhar, tanto das celulas que teriam valores a serem
lidos quanto das que seriam usadas para a escrita de resultados.
Essa situacao confusa e observavel no exemplo da conta de energia eletrica. Sem o comentario
20 CAPITULO 2. CONCEITOS BASICOS

Pseudocodigo 2.1 Calculo da conta de energia eletrica utilizando os enderecos das celulas de
memoria.
Descricao: programa que calcula o valor da conta de energia eletrica.
Dados de Entrada: celulas 43 e 46 contendo, respectivamente, a leitura atual e a anterior; e valor
unitario do kWh na celula 44.
Sada do Programa: valor da conta, armazenada na celula 42.
Subtrair o conteudo da celula 43 do conteudo da celula 46 e guardar na
celula 41;
Multiplicar o conteudo da celula 41 pelo conteudo da celula 44 e guardar na
celula 42;

Figura 2.2: Conteudo da memoria apos execucao do algoritmo que calcula o valor de uma conta de
energia eletrica.

no incio do Pseudocodigo 2.1, seria impossvel ter a mnima ideia do que o programa faz em
cada passo. Isso torna difcil nao so a escrita, mas tambem a correcao dos programas.
Para resolver essa questao, o conceito de variavel foi criado. Uma variavel nada mais e do
que uma abstracao para o endereco de memoria. Com o emprego de variaveis, as celulas de
memoria sao referenciadas nos programas por meio de rotulos, definidos com ajuda do bom-
senso do programador. O compilador fica encarregado do trabalho de transformar rotulos em
enderecos para que as operacoes de acesso a memoria sejam realizadas.
O Pseudocodigo 2.2 mostra como fica o programa que calcula o valor da conta de energia
eletrica, agora escrito utilizando variaveis.

Pseudocodigo 2.2 Calculo da conta de energia eletrica utilizando variaveis.


Descricao: programa que calcula o valor da conta de energia eletrica.
Dados de Entrada: a leitura atual, a anterior e o preco unitario do kWh.
Sada do Programa: valor da conta.
Subtrair o conteudo da variavel leituraAtual do conteudo da variavel
leituraAnterior e guardar na variavel diferenca;
Multiplicar o conteudo da variavel diferenca pelo conteudo da variavel
valorUnitario e guardar na variavel valorConta;

A partir do Pseudocodigo 2.2, um compilador pode converter os rotulos para posicoes quais-
quer na memoria, evitando que o programador tenha que se preocupar em manipula-las. Uma
2.2. IDENTIFICADORES 21

Figura 2.3: Uma possvel traducao para enderecos de memoria, a partir dos rotulos do codigo que utiliza
variaveis.

possvel traducao e exibida na Figura 2.3.


Como visto nos exemplos desta secao, o uso de variaveis em um algoritmo reduz a quantidade
de comentarios explicativos, pois sua leitura e mais simples e seu entendimento e mais facil. Em
caso de erros, a correcao do programa e muito mais rapida e eficiente.
Apresentados os conceitos de variavel e celulas de memoria, e valido abordar um outro
significado de programa. Trata-se de considerar os programas como sendo processos de mudanca
de estados.
Nessa abordagem, os dados de entrada, escritos em suas respectivas variaveis, sao conside-
rados o estado inicial do programa. A partir da, realiza-se uma sequencia de operacoes para
chegar a um estado final. A cada operacao, diz-se que o programa esta em um novo estado in-
termediario. O estado final e atingido quando a tarefa em questao e considerada como realizada.
A transicao do estado inicial para o estado final pode ser efetuada de diversas maneiras.
Conforme pode ser visto mais adiante neste captulo, e possvel escolher entre duas ou mais
sequencias de comandos, bem como repeti-las, de acordo com o estado intermediario em que o
programa se encontra.

2.2 Identificadores
Em geral, as linguagens de alto nvel possuem dois tipos de elementos: os elementos definidos
pela propria linguagem smbolos para operadores, nome de comandos etc e os elementos
definidos pelo programador identificadores, comentarios etc.
Um identificador e um smbolo que pode representar alguma entidade criada pelo progra-
mador, como uma variavel, por exemplo. Cada linguagem define uma regra para formacao de
identificadores. Em geral, sempre e possvel utilizar uma sequencia de caracteres alfanumericos
letras ou dgitos, sem acentos e sem cedilha sendo que o primeiro caractere deve ser obri-
gatoriamente alfabetico. Os Exemplos 2.1 e 2.2 apresentam, respectivamente, nomes corretos e
nomes invalidos de variaveis na linguagem C.
Algumas linguagens, como C, fazem diferenciacao entre letras maiusculas e minusculas.
Desta forma, uma variavel de nome Saldo e considerada diferente de outra de nome saldo,
ou, ainda, de nome SALDO. E importante ressalvar que nao e uma boa pratica de programacao
criar identificadores que apenas se diferenciem pelo formato das letras, como no exemplo do
saldo, pois isso tem impacto negativo na legibilidade dos programas.
22 CAPITULO 2. CONCEITOS BASICOS

1 abc
2 x1
3 y2
4 letra
5 SOMA_TOTAL
6 B_32

Exemplo 2.1: Nomes validos de variaveis, concordando com as regras de nomenclatura.

1 fim ? // ? n~
a o e um caractere alfanumerico
2 % percentual % // % n~
a o e um caractere alfanumerico
3 123 quatro // Iniciado por numero
4 ! hola ! // ! n~
a o e um caracter alfanumerico
5 @ARROBA // @ n~ a o e um caractere alfanumerico

Exemplo 2.2: Nomes invalidos de variaveis.

Normalmente, em grandes projetos de software, sao adotados padroes para a escrita dos
identificadores a fim de que os programadores possam trocar seus codigos, entende-los e altera-
los sem grande dificuldade. Neste texto, e adotado como regra na escrita:

Nomes simples: comecados com letra minuscula e demais caracteres minusculos;

Nomes compostos: primeira parte e iniciada por letra minuscula e as demais partes inici-
adas por letra maiuscula. Os demais caracteres sao minusculos.

Como dito na Secao 1.5, um bom programa deve necessariamente ser legvel e de facil com-
preensao. A escolha dos nomes dos identificadores influenciam diretamente esses dois aspectos.
Logo, e muito importante que os nomes sejam significativos, deixando bem clara a sua referencia.
A fim de contrastar com a declaracao de variaveis do Exemplo 2.1, o Exemplo 2.3 ilustra
a convencao proposta, apresentando nomes significativos para variaveis. Neste, o rotulo das
variaveis ja e suficiente para informar a funcionalidade das mesmas, tornando rapida a compre-
ensao do programa que as utiliza e dispensando a necessidade de comentarios.

1 delta
2 raiz1
3 idade
4 letra
5 perc entualDeLucro
6 primeiraLetra
7 indiceBovespa

Exemplo 2.3: Nomes significativos para variaveis.


2.3. COMANDO DE ATRIBUICAO 23

2.3 Comando de Atribuicao


Os pseudocodigos da Secao 2.1 nao mostram como os conteudos das variaveis de entrada sao
alterados para que o calculo seja efetuado coerentemente. Em outras palavras, nao se mostra
como as variaveis leituraAtual, leituraAnterior e valorUnitario sao inicializadas. O responsavel
pela alteracao dessas variaveis, assim como de qualquer outra, e o comando de atribuicao.
O comando de atribuicao e muito importante e, com certeza, e o mais utilizado. E por meio
dele que as variaveis podem ter seus valores (conteudos) alterados. Por isso, o comando de
atribuicao esta diretamente ligado as operacoes de memoria.
No Exemplo 2.4 e transcrito o algoritmo do calculo da conta de energia eletrica para o codigo
de um programa na linguagem C.

1 main () {
2 leituraAtual = 125;
3 leituraAnterior = 25;
4 valorUnitario = 2;
5 diferenca = leituraAtual - leituraAnterior ;
6 valorConta = diferenca * valorUnitario ;
7 }

Exemplo 2.4: Calculo do valor da conta de energia eletrica.

No Exemplo 2.4, o smbolo = representa o comando de atribuicao. Em seu lado esquerdo


sempre havera a variavel destino, na qual sera escrito o resultado da expressao do lado direito
do comando. As expressoes mais simples sao as que contem valores constantes, tais como as que
ocorrem nas linhas 1, 2 e 3 do Exemplo 2.4.
Essas tres linhas sao omitidas propositalmente do Pseudocodigo 2.2 para torna-lo mais sim-
ples. Entretanto, elas nao sao difceis de compreender, pois apenas informam ao computador
quais valores ele deve manipular para encontrar o resultado esperado: o valor da conta mensal
de energia eletrica.
Dessa forma, o comando da linha 1 do Exemplo 2.4 simplesmente realiza a atribuicao do valor
125 a variavel leituraAtual. A partir da efetivacao desse comando, o conteudo dessa variavel
pode ser usado para os calculos. O mesmo vale para as demais variaveis.
As linhas 4 e 5 do Exemplo 2.4 sao transcricoes diretas dos dois passos do algoritmo da
conta de energia. O que ocorre de diferente e que o valor a ser atribudo as variaveis do lado
esquerdo e primeiramente calculado na expressao para, em seguida, ser guardado na variavel
correspondente. A Figura 2.4 representa graficamente essa passagem.
O comando de atribuicao e executado sempre da mesma maneira pelo computador. Em
primeiro lugar a expressao que esta do lado direito do comando e calculada e, para isso, todas as
variaveis que aparecem tem seus valores lidos e lancados na expressao. Apos o fim dos calculos na
expressao, o computador escreve o resultado na variavel destino, no lado esquerdo. Basicamente,
a forma geral do comando de atribuicao e a seguinte:

<variavel> = <express~
ao>;
24 CAPITULO 2. CONCEITOS BASICOS

Figura 2.4: Representacao grafica da linha 4 do Exemplo 2.4.

E muito importante compreender a diferenca significativa existente entre o comando de atri-


buicao e a igualdade matematica. Em muitas linguagens de programacao (C, por exemplo) a
atribuicao e tambem representada pelo smbolo =, o que pode provocar interpretacoes equi-
vocadas. O Exemplo 2.5 mostra um trecho de programa que geralmente causa confusao para os
programadores iniciantes.

1 contador = 10;
2 contador = contador + 1;

Exemplo 2.5: Incremento de uma variavel usando o comando de atribuicao.

No Exemplo 2.5 ocorrem duas atribuicoes. A primeira ocorre na linha 1, sendo mais um
exemplo simples de inicializacao de uma variavel, como ja discutido anteriormente. Na linha 2,
a variavel contador aparece em ambos os lados da expressao, o que pode parecer um absurdo
matematico. Porem, e necessario lembrar que, nesse contexto, o smbolo = atribui o valor da
expressao da direita a variavel que aparece a esquerda. Ou seja, avalia-se primeiro o resultado
da expressao a direita (10 + 1 = 11) e, posteriormente, atribui-se o valor a variavel a esquerda
(contador = 11). Isso e representado graficamente na Figura 2.5.

Figura 2.5: Representacao grafica do incremento de uma variavel.

Mais uma observacao e necessaria: O programador deve ficar atento aos tipos de dados
manipulados em um comando de atribuicao, pois o tipo da expressao a direita deve ser compatvel
com o tipo da variavel a esquerda. A manipulacao dos tipos de dados e detalhada na Secao 2.4.
2.4. TIPOS DE DADOS 25

2.4 Tipos de Dados


Um tipo de dados delimita o conjunto de valores possveis que uma determinada variavel pode
representar. Alem disso, define as operacoes basicas possveis para suas variaveis.
Sua necessidade decorre do fato de que as celulas de memoria, sozinhas, conseguem represen-
tar apenas conjuntos de dados muito limitados. Entretanto, a maioria dos valores de interesse
pertence a conjuntos extensos, os quais nao podem ser representados com o uso de apenas uma
celula de memoria.
Os programadores, entao, passaram a utilizar multiplas celulas para representar um unico
valor. Essa atitude resolveu a dificuldade de armazenar dados complexos, mas criou um outra
questao: os processos de leitura e escrita para valores de multiplas celulas, bem como as de-
mais operacoes sobre tais valores, exigiam codigos que, alem de nao-triviais, fugiam do foco do
problema principal.
Nao demorou para que as linguagens de programacao incorporassem a definicao dos tipos
de dados e suas operacoes essenciais, poupando os programadores da tarefa de defini-los em seu
codigo e tornando-o mais enxuto e legvel.
Tipos de dados, portanto, abstraem a forma de implementacao e disponibilizam operacoes
sobre os elementos do conjunto para que o programador possa utiliza-las sem ter que implementa-
las.
Os tipos basicos mais importantes sao o tipo inteiro, o ponto flutuante, o booleano e o tipo
caractere. Cada linguagem de programacao possui certas particularidades em relacao aos tipos
de dados.
Primeiramente, e preciso abordar um conceito imprescindvel da escrita de programas em
linguagem C: a declaracao de variaveis. Em seguida, sao descritas as caractersticas mais comuns
dos tipos basicos principais.

2.4.1 Declaracao de Variaveis


De acordo com o problema abordado, diferentes conjuntos de dados sao necessarios. Conforme
visto, os tipos de dados representam os conjuntos de valores possveis para suas variaveis, assim
como as operacoes validas sobre elas. Tambem ja foram abordadas a necessidade e as vantagens
do uso de variaveis em um programa. Entretanto, ainda nao foi mencionado como as variaveis
sao criadas, ou seja, como o programador declara formalmente uma variavel em seu codigo.
Na linguagem C, o ato de declarar uma variavel e simples e imprescindvel. Afinal, sem
esse procedimento, nao e possvel usa-la. Quando a declaracao de variaveis e esquecida, uma
mensagem de erro e apresentada ao programador, e a compilacao do codigo e interrompida. Por
isso, toda variavel deve ser declarada antes de ser utilizada.
A declaracao de variaveis tem uma estrutura simples e algumas regras basicas. Em primeiro
lugar, escreve-se o nome do tipo de dados ao qual a variavel pertence. Em seguida, separado
por um espaco em branco, escreve-se o identificador da variavel.

<tipo> <identificador>;
26 CAPITULO 2. CONCEITOS BASICOS

E possvel declarar mais de uma variavel em uma mesma linha, desde que todas sejam do mesmo
tipo, conforme o Exemplo 2.6.

1 float valorFracionado , saldo , salarioMinimo ;


2 char letra , ultimaLetra ;
3 int entrada1 , entrada2 , entrada3 ;

Exemplo 2.6: Declaracao multipla de variaveis em uma mesma linha de codigo.

2.4.2 Tipo Inteiro


O tipo inteiro, como o proprio nome sugere, representa um intervalo finito do conjunto ma-
tematico dos numeros inteiros.
De fato, uma linguagem de programacao pode definir varios tipos inteiros, cada um com
intervalo diferente. Na linguagem C, por exemplo, existem varios tipos inteiros, tais como o int,
long int, unsigned int, short int. Neste livro, para bem da simplicidade, usa-se apenas o tipo
int.
O intervalo de valores para o tipo int depende do compilador que esta sendo empregado.
Assumimos aqui que o intervalo do tipo int sempre varia de entre 32768 a 32767.
Em C, como na maioria das linguagens, as operacoes sobre as variaveis do tipo inteiro ja
estao implementadas e a disposicao do programador. As operacoes disponveis sao as de soma,
subtracao, multiplicacao, divisao e resto de divisao, conforme o Exemplo 2.7.
O uso dessas funcoes e bastante simples e intuitivo, pois ha ate certa relacao com os smbolos
da escrita matematica. No Exemplo 2.7, as variaveis x e y sao relacionadas entre si e os resultados
sao armazenados nas variaveis de a a f.
Os operadores de soma e subtracao funcionam tal qual na aritmetica. O operador de sub-
tracao pode ser aplicado para um unico valor quando se deseja inverter seu sinal. A linha 12 do
Exemplo 2.7 mostra essa situacao.
Um destaque deve ser dado ao operador de multiplicacao. Seu unico smbolo e * (nao ha
o smbolo ou o ). Alem disso, em oposicao ao que ocorre na aritmetica, a escrita do *
nao pode ser omitida. Logo, a operacao da linha 13 do Exemplo 2.7 geraria erro se nao estivesse
comentada, pois o compilador trataria xy como uma variavel nao declarada.

1 main () {
2 int x , y , a , b , c , d , e , f ;
3
4 x = 10;
5 y = 3;
6
7 a = x + y; // a armazena 13
8 b = x - y; // b armazena 7
9 c = x * y; // c armazena 30
10 d = x / y; // d armazena 3
11 e = x % y; // e armazena 1
2.4. TIPOS DE DADOS 27

12 f = -a ; // f armazena -13
13 // c = xy ;
14 }

Exemplo 2.7: Operacoes basicas com o tipo inteiro int.

Tambem e importante destacar o resultado da operacao de divisao. No caso do Exemplo


2.7, o resultado correto da divisao de x que armazena o valor 10 por y cujo valor e 3
e a dzima periodica 3, 333 . . . Entretanto, como as variaveis envolvidas na linha 10 sao todas
do tipo inteiro, o compilador trata de truncar o resultado, desconsiderando a parte decimal e
fornecendo 3 como resultado da operacao.
Ainda no Exemplo 2.7, deve-se notar o uso do operador de resto de divisao inteira. Seu
smbolo % deve ficar entre os valores do dividendo e do divisor. A linha 11 mostra a
obtencao do resto da divisao de x por y. Apos a execucao dessa operacao, a variavel e guardara
o valor 1, que e o resto da divisao de 10 por 3.

2.4.3 Tipo Ponto Flutuante


Esse tipo de dados representa um intervalo dos numeros racionais. Assim como o tipo inteiro,
ha mais do que um intervalo possvel, dependendo da precisao escolhida.
Na linguagem C, os tipos de ponto flutuante sao o float e o double. Neste livro, apenas o
tipo float e empregado.
Dependendo do compilador utilizado, os tipos de dados de ponto flutuante apresentam dife-
rentes precisoes. Aqui se considera que o tipo float prove seis casas decimais de precisao.
As operacoes pre-definidas sao as mesmas que as disponveis para o tipo int, exceto, e claro,
pela inexistencia do operador de resto de divisao. O Exemplo 2.8 mostra o uso de variaveis do
tipo float em um programa.

1 main () {
2 float p , q , x , z ;
3 int y ;
4
5 x = 10;
6 y = 4;
7
8 p = 50 / 30; // p guardara 1
9 q = 10.0 / 4; // q guardara 2.5
10
11 z = x / y; // z guardara 2.5
12 }

Exemplo 2.8: Operacoes com variaveis do tipo float.

As operacoes entre variaveis do tipo float sao escritas com os mesmos smbolos das operacoes
do tipo int. O Exemplo 2.8 mostra particularidades do processo de compilacao que podem gerar
erros nos programas.
28 CAPITULO 2. CONCEITOS BASICOS

A linha 8 do Exemplo 2.8 possui um comentario explicando que a variavel p recebe 1 como
resultado da operacao de divisao. Tal erro nao ocorre para a divisao da linha seguinte. A
justificativa para essa situacao e que, conforme e mostrado na Secao 2.3, o computador primeiro
calcula o resultado da expressao do lado direito do comando de atribuicao para, em seguida,
escrever esse valor na correspondente variavel de destino.
Durante a compilacao, o compilador checa os tipos das variaveis envolvidas nos calculos
para realizar as operacoes correspondentes. Entretanto, quando ha apenas valores numericos
envolvidos, o compilador considera os numeros escritos sem ponto decimal como sendo do tipo
inteiro. E por isso que a divisao realizada na linha 8 do Exemplo 2.8 resulta em 1. O computador
entende a operacao 50/30 como uma divisao inteira, descartando a parte decimal do resultado.
Ja na linha 9, o compilador enxerga o valor 10.0 como sendo do tipo float devido a presenca
explcita do ponto decimal, e realiza a divisao, resultando no valor esperado.
No caso da ultima linha do Exemplo 2.8, nenhum resultado inesperado ocorre porque o
tipo mais abrangente tipo float da variavel x encontrado na expressao e considerado nas
operacoes, garantindo o resultado esperado. Caso a variavel x tambem fosse do tipo int, entao
o mesmo incoveniente da linha 8 ocorreria.

2.4.4 Tipo Booleano


O tipo booleano e o tipo de dados mais simples. Ele contem apenas dois valores possveis:
verdadeiro e falso. E usado principalmente quando se precisa verificar condicoes no programa,
em expressoes logicas (Secao 2.6.3) e relacionais (Secao 2.6.2).
Na linguagem C nao ha uma representacao especfica para esse tipo de dados e sao utilizados
valores inteiros para codifica-lo. Desta forma, todo valor inteiro diferente de zero e considerado
valor verdadeiro. O valor zero e considerado falso.

2.4.5 Tipo Caractere


Como o proprio nome indica, o tipo caractere registra os codigos para letras, numeros e caracteres
especiais ($, &, #, @ etc). Normalmente, esses codigos e caracteres variam para as diversas
regioes do planeta, conforme suas particularidades lingusticas. Um padrao largamente utilizado
atualmente, pricipalmente no Ocidente, e o ASCII (American Standard Code of Information
Interchange Padrao Americano para Intercambio de Informacoes), especificado pelo ANSI
(American National Standars Institute Instituto Americano Nacional de Padroes).
O padrao ASCII e uma tabela. Cada posicao da tabela simboliza um caractere. Na linguagem
C, o tipo char e empregado para conter o ndice para a posicao da tabela que codifica um
determinado caractere.
Originalmente, essa tabela era composta por apenas 128 smbolos, os quais incluam todos os
caracteres alfanumericos do Ingles, sinais de pontuacao e alguns outros smbolos. Os primeiros 32
caracteres eram utilizados para controle de fluxo de dados (em comunicacoes entre o computador
e seus perifericos) e nao imprimveis. Atualmente, esses caracteres caram em desuso. A Tabela
2.1 mostra os 128 caracteres da tabela ASCII original.
2.4. TIPOS DE DADOS 29

Codigo Caractere Codigo Caractere Codigo Caractere Codigo Caractere


0 NUL (null) 32 SPACE 64 @ 96 `
1 SOH (start of heading) 33 ! 65 A 97 a
2 STX (start of text) 34 66 B 98 b
3 ETX (end of text) 35 # 67 C 99 c
4 EOT (end of transmission) 36 $ 68 D 100 d
5 ENQ (enquiry) 37 % 69 E 101 e
6 ACK (acknowledge) 38 & 70 F 102 f
7 BEL (bell) 39 71 G 103 g
8 BS (backspace) 40 ( 72 H 104 h
9 HT (horizontal tab) 41 ) 73 I 105 i
10 LF (NL line feed,new line) 42 * 74 J 106 j
11 VT (vertical tab) 43 + 75 K 107 k
12 FF (NP from feed,new page) 44 , 76 L 108 l
13 CR (carriage return) 45 - 77 M 109 m
14 SO (shift out) 46 . 78 N 110 n
15 SI (shift in) 47 / 79 O 111 o
16 DLE (data link escape) 48 0 80 P 112 p
17 DC1 (device control 1) 49 1 81 Q 113 q
18 DC2 (device control 2) 50 2 82 R 114 r
19 DC3 (device control 3) 51 3 83 S 115 s
20 DC4 (device control 4) 52 4 84 T 116 t
21 NAK (negative acknowledge) 53 5 85 U 117 u
22 SYN (synchronous idle) 54 6 86 V 118 v
23 ETB (end of trans. block) 55 7 87 W 119 w
24 CAN (cancel) 56 8 88 X 120 x
25 EM (end of medium) 57 9 89 Y 121 y
26 SUB (substitute) 58 : 90 Z 122 z
27 ESC (escape) 59 ; 91 [ 123 {
28 FS (file separator) 60 < 92 \ 124 |
29 GS (group separator) 61 = 93 ] 125 }
30 RS (record separator) 62 > 94 126
31 US (unit separator) 63 ? 95 127 DEL

Tabela 2.1: Os 128 caracteres da tabela ASCII original.

Com o passar do tempo, a tabela ASCII sofreu modificacoes. Ela foi atualizada e incorporou
mais 128 novos smbolos, os quais incluem, por exemplo, os caracteres acentuados e o cedilha,
do Portugues. Essa nova versao e conhecida como tabela ASCII estendida.
Para fazer com que uma variavel do tipo char aponte para um smbolo de interesse, o
programador precisa apenas atribuir a essa variavel o caractere desejado, colocando-o entre
aspas simples. O Exemplo 2.9 exibe essa situacao.

1 main () {
2 char letraA , letraFMaiuscula , simboloSoma ;
3
4 letraA = a ;
30 CAPITULO 2. CONCEITOS BASICOS

5 letraFMaiuscula = F ;
6 simboloSoma = + ;
7 }

Exemplo 2.9: Uso de variaveis do tipo caractere.

2.4.6 Conversao de Tipos


Agora, conhecidas algumas informacoes sobre os tipos de dados basicos, torna-se mais simples
compreender o problema existente na atribuicao entre tipos diferentes, mencionada na Secao
2.3.
Considera-se que os tipos int, float e char ocupam a quantidade de memoria representada
na Figura 2.6.

Figura 2.6: Tamanho da memoria utilizada pelos tipos de dados basicos.

As diferencas nos tamanhos sao justificaveis, uma vez que, quanto maior o conjunto a ser
representado, mais celulas de memoria sao necessarias para acomodar uma variavel. Desta
forma, sendo o conjunto dos caracteres o menor, e preciso apenas um byte (uma celula) para
representa-lo. Ja o conjunto do tipo float, que e o maior dos tres, precisa de 4 bytes (4 celulas)
para uma representacao coerente de seus valores.
E preciso ter em mente que, devido a essas restricoes de implementacao, nao se deve utili-
zar operacoes de atribuicao entre variaveis sem tomar algum cuidado. Se elas forem de tipos
diferentes, problemas podem acontecer.
Nao ha problemas em atribuir uma variavel do tipo int a outra do tipo float. Tal qual ocorre
na matematica, toda variavel do tipo int e possvel de ser representada por outra do tipo float,
pois o conjunto dos numeros racionais contem o conjunto dos inteiros. Um raciocnio similar
pode ser empregado para justificar as atribuicoes de variaveis char a variaveis de tipo int ou
float, que tambem ocorrem sem erros.
O problema e quando se tenta atribuir uma variavel de um conjunto mais amplo a uma de
um conjunto mais restrito. Veja o Exemplo 2.10.
Observando o Exemplo 2.10, um programador distrado pode pensar que houve uma atri-
buicao correta. Entretanto, o valor armazenado pela variavel teste e 125. Nao havendo condicoes
de armazenar corretamente (um codigo de 4 bytes nao pode ser escrito num espaco de apenas
2 bytes), o compilador simplesmente descarta a parte decimal do numero, armazenando apenas
2.5. CONSTANTES 31

sua parte inteira na variavel. Logo, e preciso permanecer atento as atribuicoes desse tipo para
nao se equivocar sobre o valor armazenado por uma variavel.

1 main () {
2 int teste ;
3
4 teste = 125.568;
5 }

Exemplo 2.10: Atribuicao entre tipos diferentes com perda de informacao.

2.5 Constantes
Em algumas situacoes surge a necessidade de se utilizar um determinado valor constante em
diversas partes do codigo. Para simplificar a alteracao dessas constantes e facilitar a leitura do
codigo, e possvel definir um nome signicativo para elas de maneira simples, por meio de uma
estrutura especfica.
Essa estrutura e iniciada pela diretiva #define, seguida pelo identificador da constante e
pelo seu respectivo valor, todos separados por um espaco em branco.
#define <identificador> <valor>
Na linguagem C, a declaracao de constantes deve ser feita no incio do codigo, antes da funcao
main. O Exemplo 2.11 apresenta declaracoes validas de constantes.

1 # define PI 3.141593
2 # define FALSO 0
3 # define VERDADEIRO 1
4 # define RAIZDEDOIS 1.414214
5
6 main () {
7 float raio , comprimento , area ;
8
9 raio = 10;
10 area = PI * raio * raio ;
11 comprimento = 2 * PI * raio ;
12 }

Exemplo 2.11: Definicao de constantes.

Na declaracao de constantes, a escolha dos nomes segue as mesmas regras para a escrita dos
nomes de variaveis. Geralmente, para diferenciar as constantes no codigo, opta-se por escrever
seus nomes com todas as letras maiusculas.
O Exemplo 2.11 mostra um exemplo simples onde diversas constantes sao declaradas de
acordo com a convencao proposta. Durante a compilacao do codigo, o compilador troca tocas
as ocorrencias da constante PI por seu respectivo valor, definido na linha 1.
32 CAPITULO 2. CONCEITOS BASICOS

2.6 Expressoes
As variaveis e constantes podem ser combinadas com os operadores associados a cada tipo de
dados, gerando expressoes.

2.6.1 Expressoes Aritmeticas


A expressao aritmetica e aquela cujos operadores e operandos sao de tipos numericos (int ou
float, por exemplo).
Nas operacoes aritmeticas, e guardada sempre a seguinte relacao de prioridade:

1. Multiplicacao, divisao e resto de divisao;

2. Adicao e subtracao.

Para se obter uma sequencia diferente de calculos, varios nveis de parenteses podem ser
usados para quebrar as prioridades definidas. Nao e permitido o uso de colchetes e chaves, uma
vez que estes smbolos ja sao utilizados com outras finalidades. O Exemplo 2.12 exibe uma serie
de expressoes aritmeticas validas.

1 main () {
2 int x , y , z ;
3
4 x = 10 * 10 + 25; // x = 125
5 y = 5 * (7 + 3) ; // y = 50
6 z = 10 * (( x - 25) / y + ( x - 25) * 2) ; // z = 2020
7 }

Exemplo 2.12: Expressoes aritmeticas e o uso de parenteses para determinar a precedencia das
operacoes.

No Exemplo 2.12, e importante notar que, no comando de atribuicao a variavel x, a multi-


plicacao 10 10 e executada primeiro. Na linha seguinte, a soma 7 + 3 e executada primeiro
e seu valor e, em seguida, multiplicado por 5 e atribudo a variavel y. A analise da linha 6 e
deixada para o leitor.
Alem das operacoes basicas ja citadas, e possvel usar, nas expressoes aritmeticas, outras
operacoes bastante comuns na matematica, definidas dentro da linguagem na forma de funcoes.
A sintaxe geral para o uso dessas funcoes e a seguinte:

<nome da func~
ao>(<valor>)

Algumas funcoes matematicas sao mostradas na Tabela 2.2.


O Exemplo 2.13 exibe varios usos de funcoes matematicas e expressoes aritmeticas.
2.6. EXPRESSOES 33

Nome Descricao
abs Modulo ou valor absoluto de um valor inteiro
fabs Modulo ou valor absoluto de um valor racional
sin Seno de um angulo em radianos
cos Cosseno de um angulo em radianos
sqrt Raiz quadrada de um numero
pow Potenciacao
floor Maior inteiro nao maior que o numero de entrada
ceil Menor inteiro nao menor que o numero de entrada
log Logaritmo neperiano
exp Exponencial

Tabela 2.2: Principais funcoes matematicas ja definidas na linguagem C.

1 # include < math .h >


2
3 # define PI 3.1415
4
5 main () {
6 float a , b , c , delta , raiz1 , raiz2 ;
7 float x , y , z ;
8
9 a = 10;
10 b = 50;
11 c = 30;
12
13 delta = b * b - 4 * a * c ;
14 raiz1 = -b + sqrt ( delta ) / (2 * a ) ;
15 raiz2 = -b - sqrt ( delta ) / (2 * a ) ;
16
17 x = sin ( - PI / 2) ;
18 y = fabs ( x ) ;
19 z = pow (y ,2) ; // y elevado a pot^
e ncia 2
20 }

Exemplo 2.13: Uso de funcoes matematicas pre-definidas.

Para o uso das funcoes matematicas apresentadas no Exemplo 2.13 e necessario a inclusao
da biblioteca math.h, como na linha 1. Os mesmos calculos para a procura das razes de uma
equacao de segundo grau sao executados nas linhas 13 a 15, onde a funcao sqrt calcula a raiz
quadrada do seu operando a variavel delta. Ja as linhas 15 a 17 apresentam calculos avulsos:
a linha 17 executa o calculo do seno de /2 e armazena o resultado na variavel x; a linha
18 armazena, na variavel y, o modulo do valor armazenado em x; e a linha 19 calcula o valor
armazenado em y elevado a potencia de 2 e armazena o resultado na variavel z.
34 CAPITULO 2. CONCEITOS BASICOS

2.6.2 Expressoes Relacionais


As expressoes relacionais comparam valores entre si. Tal qual se faz na pratica, o operador
relacional trabalha com dois valores: o da expressao que esta a sua direita e o da expressao que
esta a sua esquerda. E importante destacar que uma variavel ou uma constante isolada, por si
so, pode ser considerada uma expressao.
A Tabela 2.3 mostra exemplos de operadores relacionais da linguagem C e alguns casos de
uso.
Smbolo Nome Exemplo
< menor que a < 10
<= menor ou igual a x <= y
== igual a 4 == 2*2
> maior que t>0
>= maior ou igual a delta >= 0
!= diferente de x != 9+8*8

Tabela 2.3: Operadores relacionais.

As expressoes relacionais resultam sempre em 0 (zero) para uma comparacao falsa ou 1 para
uma comparacao verdadeira.
As variaveis do tipo char tambem podem ser comparadas entre si, respeitando a ordenacao
do padrao de codificacao utilizado.
E importante atentar que, embora sejam escritos de maneira parecida, ha uma grande dife-
renca entre o operador de comparacao == e o comando de atribuicao =. Essa semelhanca e
uma fonte comum de erros de programacao, geralmente difceis de detectar.

2.6.3 Expressoes Logicas


As expressoes logicas sao utilizadas para relacionar os resultados de um conjunto de operacoes
relacionais. Elas realizam as principais operacoes da logica booleana e, na linguagem C, tem
como operadores:

&& (AND): Realiza o E logico;

|| (OR): Realiza o OU logico;

! (NOT): Realiza a negacao.

Os resultados obtidos das expressoes logicas tambem sao valores do tipo inteiro: 0 para falso
e 1 para verdadeiro.
Para melhor entender o que cada operador realiza, sao levados em conta dois valores P e Q
de entrada, que podem ser verdadeiros (V) ou falsos (F). A Tabela 2.4 resume todos os casos
possveis.
2.7. COMANDO DE ENTRADA DE DADOS 35

P Q P && Q P || Q !P !Q
V V V V F F
V F F V F V
F V F V V F
F F F F V V

Tabela 2.4: Todas as combinacoes possveis para os operadores logicos.

O uso dos operadores relacionais e logicos fica bem mais claro adiante, neste captulo, quando
os comandos de selecao e repeticao sao estudados.

2.7 Comando de Entrada de Dados


O comando de entrada de dados serve para captar do usuario do programa um ou mais valores
necessarios para a execucao das tarefas. Na verdade, o que se faz e ler os dados de uma fonte
externa, normalmente do teclado, para depois usa-los de alguma maneira dentro do programa.
Por meio desse comando de leitura de dados, uma ou mais variaveis podem ter seus valores
lidos durante a execucao do programa. Essa caracterstica permite criar programas mais flexveis.
Na linguagem C, a sintaxe para o uso do comando e a seguinte:

scanf("<formato1> <formato2> ... <formatoN>", &var1, &var2, ..., &varN);

Essa estrutura e dividida em duas partes distintas. A primeira, colocada entre aspas duplas,
contem os formatos, os quais sao relacionados diretamente com os tipos das variaveis a serem
lidas. A segunda parte e uma lista dos nomes dessas variaveis, com uma relacao direta entre
a posicao nessa lista e o respectivo formato descrito na primeira parte do comando. Conforme
mostra a descricao da sintaxe, os nomes das variaveis devem ser precedidos pelo caractere &.
A Tabela 2.5 mostra alguns dos possveis formatos.

Codigo Significado
%c Le um unico caracter
%d Le um inteiro decimal
%f Le um numero em ponto flutuante

Tabela 2.5: Codigos para formatos de dados do comando de entrada scanf .

Um exemplo claro da utilidade do comando de entrada de dados e o calculo da conta de


energia eletrica. O exemplo apresentado ate agora tinha os valores de consumo e do preco do
quilowatt-hora escritos diretamente no codigo. Isso e bastante incomodo, pois, quando se quer
calcular o valor de uma conta diferente, o codigo deve ser alterado e recompilado.
Uma maneira de tornar o programa mais flexvel e utilizar o comando de entrada de dados.
Com ele, e possvel digitar os valores desejados para as variaveis e efetuar os calculos para
36 CAPITULO 2. CONCEITOS BASICOS

diferentes contas, bastando, para isso, executar novamente o programa. O codigo modificado e
exibido no Exemplo 2.14.
De acordo com o formato descrito no comando de leitura, os valores das variaveis leituraA-
tual, leituraAnterior e valorUnitario devem ser digitados pelo usuario, nesta ordem, separados
por espacos em branco.

1 # include < stdio .h >


2 main () {
3 int leituraAtual , leituraAnterior , diferenca ;
4 float valorUnitario , valorConta ;
5
6 scanf ( " % d % d % f " , & leituraAtual , & leituraAnterior , & valorUnitario ) ;
7 diferenca = leituraAtual - leituraAnterior ;
8 valorConta = diferenca * valorUnitario ;
9 }

Exemplo 2.14: Emprego do comando de entrada de dados para o algoritmo do Exemplo 2.4.

Observando o codigo do Exemplo 2.14, e importante ressaltar que nao ha mais a parte de
inicializacao das variaveis. Essa tarefa foi substituda pelo comando de entrada de dados e os
valores sao, agora, passados pelo usuario do programa. Esses valores podem ser alterados a cada
execucao do programa para que varias contas diferentes possam ser calculadas.

2.8 Comando de Sada de Dados


Ate agora, os exemplos de codigos e os conceitos estudados nao mostravam como os programas
comunicavam seus resultados ao usuario ou ate mesmo a outros programas. Esse e o papel do
comando de sada de dados.
O comando de sada de dados serve para escrever mensagens e exibir valores de variaveis,
proporcionando mais informacao e legibilidade tanto para o usuario quanto para o proprio pro-
gramador.
Na linguagem C, a sintaxe para o uso do comando de sada e mostrada a seguir.

printf("<formato1> <formato2> ... <formatoN>", var1, var2, ..., varN);

A estrutura do comando de sada de dados e bastante semelhante a do comando de entrada.


A principal diferenca e sobre o uso do caractere &. No comando de sada, o caractere &
nao deve ser escrito a frente dos identificadores das variaveis. A segunda parte do comando e
opcional, conforme e mostrado logo adiante nesta secao.
A Tabela 2.6 mostra alguns dos possveis formatos a serem usados.
Conforme ja mencionado, o comando de sada e muito utilizado para exibir mensagens para
o usuario. Nao e obrigatorio que as mensagens contenham apenas valores ou caracteres armaze-
nados em variaveis, sendo possvel escrever textos como mensagem. O Exemplo 2.15 demonstra
essa possibilidade.
2.8. COMANDO DE SAIDA DE DADOS 37

Codigo Significado
%c Caracter
%d Inteiros decimais com sinal
%e Notacao cientfica
%f Ponto flutuante decimal
%% Escreve o smbolo %

Tabela 2.6: Codigos para formatos de dados para o comando de sada printf .

1 # include < stdio .h >


2
3 main () {
4 printf ( " Bom dia a todos ! " ) ;
5 printf ( " Outra mensagem " ) ;
6 }

Exemplo 2.15: Escrevendo mensagens para o usuario do programa.

Pode-se, ainda, mesclar uma mensagem de texto com valores de variaveis. O Exemplo 2.16
ilustra essa ideia. Nele, e possvel notar que, para cada especificador de formato na primeira
parte do comando, deve existir uma variavel de tipo correspondente posicionada adequadamente
na segunda parte.

1 printf ( " Valor do saldo : % f " , saldo ) ;


2 printf ( " A metade de % d vale % d " , numero , metade ) ;
3 printf ( " O menor numero digitado foi % d " , menor ) ;

Exemplo 2.16: Escrevendo mensagens que incluem valores de variaveis.

Uma versao mais completa do codigo para o calculo da conta de energia eletrica e exibida no
Exemplo 2.17. Nele, o comando de sada e usado antes de cada comando de entrada de dados
(linhas 5, 8 e 11), escrevendo mensagens para indicar ao usuario do programa qual valor ele
devera informar. A ultima linha de codigo utiliza o comando de sada para exibir o valor do
calculo da conta. Trata-se de uma mensagem mista, em que o especificador %f sera substitudo
pelo valor da variavel valorConta. Supondo que, apos os calculos, essa variavel armazene o valor
187, a ultima mensagem exibida na tela seria:

Valor da conta: R$ 187.000000

1 # include < stdio .h >


2
3 main () {
4 int leituraAtual , leituraAnterior , diferenca ;
5 float valorUnitario , valorConta ;
38 CAPITULO 2. CONCEITOS BASICOS

6
7 printf ( " Digite o valor da leitura ATUAL : " ) ;
8 scanf ( " % d " , & leituraAtual ) ;
9
10 printf ( " Digite o valor da leitura ANTERIOR : " ) ;
11 scanf ( " % d " , & leituraAnterior ) ;
12
13 printf ( " Digite o preco do Quilowatt - hora : " ) ;
14 scanf ( " % f " , & valorUnitario ) ;
15
16 diferenca = leituraAtual - leituraAnterior ;
17 valorConta = diferenca * valorUnitario ;
18
19 printf ( " Valor da conta R$ : % f " , valorConta ) ;
20 }

Exemplo 2.17: Algoritmo mais interativo para o exemplo de calculo da conta de energia eletrica.

Quando se usa o comando printf , o caractere especial \n e bastante util. Ele serve para
quebrar a linha exatamente na posicao onde for inserido no texto. A Tabela 2.7 explica melhor
o funcionameto desse caractere.

Codigo Resultado
printf("OI.\nComo vai?"); OI.
Como vai?
int x; O valor de x eh:
x = 10; 10
printf("O valor de x eh:\n%d", x);
int a; Primeira linha.
a = 20; Segunda linha (a = 20).
printf("Primeira linha.\nSegunda linha (a = %d). \n\nFIM.", a);
FIM.

Tabela 2.7: Exemplos de uso do caractere especial \n.

2.9 Comandos de Selecao

O comando de selecao permite que um programa possa realizar diferentes alternativas de sequencias
de instrucoes durante sua execucao. Dependendo do valor de uma expressao ou de uma variavel,
o programa segue executando uma ou outra sequencia de comandos.
Existem tres categorias distintas: selecao simples, selecao dupla e selecao multipla, as quais
sao abordadas a seguir.
2.9. COMANDOS DE SELECAO 39

2.9.1 Comando de selecao simples


E possvel imaginar diversas situacoes cotidianas que se assemelham ao comportamento do
comando de selecao simples. Uma comparacao classica leva em conta a tomada de um desvio
durante uma viagem.
Quando se vai de carro para algum lugar, e normal se conhecer uma rota sequenciada de
lugares intermediarios pelos quais se passa antes de chegar ao destino. Entretanto, e possvel
que ocorra algum imprevisto e seja necessario tomar um desvio.
Ha tarefas computacionais que tambem se deparam com tais cenarios. Antes de exemplificar,
e melhor apresentar a estrutura do comando de selecao simplificado. Ela pode ser observada a
seguir.
if(<express~ao logica>){
<sequ^
encia de comandos>
}
Nao e difcil entende-la. Quando o computador executa esse comando, o que e realizado em
primeiro lugar e o teste da expressao logica. Se ela resultar verdadeiro, entao a sequencia de
comandos e executada. Caso contrario, o programa segue executando normalmente as instrucoes
posteriores.
Desta forma, a decisao de desviar a execucao normal do programa e condicionada ao resultado
da expressao logica escrita no incio do comando.
O uso do comando de selecao simples e exibido no Pseudocodigo 2.3, o qual descreve um
algoritmo que faz a leitura de dois numeros inteiros e os imprime como foram digitados e,
tambem, em ordem crescente.

Pseudocodigo 2.3 Ordena dois numeros inteiros digitados pelo usuario.


Descricao: Algoritmo para imprimir, em ordem crescente, dois numeros inteiros digitados pelo usuario.
Dados de Entrada: dois numeros inteiros.
Sada do Programa: numeros inteiros impressos na ordem em que foram digitados e em ordem cres-
cente.
Func~
ao Principal:
Leia dois valores inteiros.
Imprima os valores coletados.
SE o primeiro numero lido for maior que o segundo ENT~
AO
Ordene os valores.
FIM-SE
Imprima os valores ordenados.
FIM-Func~
ao Principal

No Pseudocodigo 2.3, a operacao SE-ENTAO e executada tal qual o comando de selecao


simples if. O Exemplo 2.18 apresenta um codigo em linguagem C para o algoritmo do referido
pseudocodigo.
40 CAPITULO 2. CONCEITOS BASICOS

1 # include < stdio .h >


2 main () {
3 int num1 , num2 , aux ;
4
5 printf ( " Digite dois numeros inteiros separados por espacos : " ) ;
6 scanf ( " % d % d " , & num1 , & num2 ) ;
7 printf ( " Valores digitados : % d % d .\ n " , num1 , num2 ) ;
8
9 if ( num1 > num2 ) {
10 aux = num1 ;
11 num1 = num2 ;
12 num2 = aux ;
13 }
14 printf ( " Valores ordenados : % d % d .\ n " , num1 , num2 ) ;
15 }

Exemplo 2.18: Comando if simplificado.

O Exemplo 2.18 inicia exibindo uma mensagem ao usuario (linha 5), solicitando que sejam
digitados dois numeros. Esses valores sao armazenados em variaveis correspondentes (linha 6)
e, em seguida, sao mostrados ao usuario por meio de uma mensagem (linha 7). A expressao
logica do comando de selecao da linha 9 determina se ha necessidade de inverter os numeros
antes de exibi-los ordenadamente. E importante ressaltar que nao ha necessidade de ordenar os
numeros caso o usuario ja os tenha digitado na sequencia correta. Nesse caso, o programa pode
seguir normalmente e exibir na tela os valores das variaveis. Por ultimo, os valores sao exibidos
em ordem crescente (linha 14).
Ainda no Exemplo 2.18, as linhas 10, 11 e 12 sao importantes o bastante para serem destaca-
das. Elas tratam da operacao de troca de valores entre duas variaveis, a qual e muito utilizada
e deve ser bem entendida.
O comando de atribuicao e ideal para copiar o valor de uma variavel para outra. A questao e
que a variavel de destino tem seu valor alterado permanentemente apos a realizacao do comando.
Ou seja, o valor que estava escrito nessa variavel antes da execucao da atribuicao e apagado e o
novo valor e escrito em seu lugar.
No caso da operacao de troca de valores, se fossem usadas apenas as duas variaveis envolvidas,
uma delas teria seu valor perdido quando a primeira atribuicao fosse realizada. Esse problema
ocorre no Exemplo 2.19, no qual o valor inicial da variavel var1 e perdido.

1 main () {
2 int var1 , var2 ;
3
4 var1 = 100;
5 var2 = 200;
6
7 var1 = var2 ; // var1 guarda 200
8 var2 = var1 ; // var2 guarda 200
9 }
2.9. COMANDOS DE SELECAO 41

Exemplo 2.19: Tentativa equivocada de trocar os valores de duas variaveis.

A solucao para o problema da perda de valores e simples e pode ser encontrada nas linhas
10, 11 e 12 do Exemplo 2.18. Basta usar uma variavel auxiliar e copiar para ela o valor da
primeira variavel de destino usada nas atribuicoes. Naquele exemplo, o valor da variavel num1
foi salvo na variavel aux para nao ser perdido na atribuicao da linha 11. Em seguida, esse valor
pode ser copiado corretamente para a variavel num2 na linha 12.

2.9.2 Comando de selecao dupla


O comando de selecao dupla pode ser comparado com a bifurcacao de um caminho. E preciso
escolher uma das alternativas e segui-la.
Em programacao, o comando de selecao dupla serve para executar sempre uma de duas
alternativas de sequencias de instrucoes, de acordo com a verificacao de uma condicao. A
estrutura do comando na linguagem C e mostrada a seguir.

if (<express~ao logica>){
<Sequ^
encia de comandos 1>
}else{
<Sequ^
encia de comandos 2>
}

Primeiramente, o computador testa a expressao logica. Caso resulte verdadeiro, entao a


sequencia de comandos 1 e executada. Caso contrario, a sequencia de comandos 2 e realizada.
Assim, apenas uma e sempre uma das sequencias de comandos e executada durante uma mesma
realizacao do comando de selecao dupla.
Apos a operacao de uma das sequencias de comando, o programa continua sua execucao na
primeira linha de codigo apos o conjunto de instrucoes do comando de selecao.
Para exemplificar o uso do comando de selecao dupla, o algoritmo do Pseudocodigo 2.4
mostra como se faz para decidir se um aluno esta aprovado em uma disciplina, com base na
media de suas notas semestrais.
O comando SE-ENTAO-SENAO do Pseudocodigo 2.4 deve ser interpretado da mesma ma-
neira que e o comando de selecao dupla if. No Exemplo 2.20, e listado o codigo em C para o
algoritmo do pseudocodigo recem-mencionado.

1 # include < stdio .h >


2 main () {
3 int matricula ;
4 float n1 , n2 , n3 , mediaParcial ;
5
6 printf ( " Digite a matricula e as notas parciais do aluno : " ) ;
7 scanf ( " % d % f % f % f " , & matricula , & n1 , & n2 , & n3 ) ;
8 mediaParcial = ( n1 + n2 + n3 ) / 3.0;
42 CAPITULO 2. CONCEITOS BASICOS

Pseudocodigo 2.4 Informa se um aluno esta aprovado ou nao, de acordo com a media das
notas obtidas no semestre.
Descricao: Algoritmo para decidir se um aluno esta aprovado ou nao, com base na media das notas
semestrais.
Dados de Entrada: matrcula do aluno, notas.
Sada do Programa: mensagem dizendo se o aluno esta aprovado ou nao.
Func~
ao Principal:
Leia a matrcula e as notas do aluno.
Calcule a media parcial.
SE a media parcial for menor que 7 ENT~
AO
Imprima uma mensagem dizendo que o aluno deve fazer prova final.
SEN~
AO
Imprima uma mensagem dizendo que o aluno esta aprovado.
FIM-SE.
FIM-Func~
ao Principal

9
10 if ( mediaParcial < 7.0) {
11 printf ( " \ nO aluno % d deve fazer prova final . " , matricula ) ;
12 } else {
13 printf ( " \ nO aluno % d foi aprovado com media % f " , matricula , media ) ;
14 }
15 }

Exemplo 2.20: Uso do comando de selecao if para a verificacao da media parcial de um aluno.

No Exemplo 2.20, as linhas 3 e 4 correspondem a declaracao das variaveis; a linha 7, a


inicializacao delas. Apos o calculo da media parcial das notas (linha 8), o comando de selecao
verifica se tal media e menor que 7, 0 e, assim, executa a instrucao da linha 11 ou da linha 13.
Como pode ser visto no Exemplo 2.21, e possvel usar comandos de selecao de forma ani-
nhada, ou seja, pode-se colocar um comando de selecao dentro de uma das sequencias de co-
mandos de outro. Nesse programa, compara-se dois numeros inteiros digitados pelo usuario,
informando se foram digitados em ordem crescente, decrescente ou se sao iguais. Antes, entre-
tanto, e bom observar o Pseudocodigo 2.5, que corresponde ao codigo do Exemplo 2.21.

1 # include < stdio .h >


2 main () {
3 int num1 , num2 ;
4
5 printf ( " Digite dois numeros inteiros quaisquer : " ) ;
6 scanf ( " % d % d " , & num1 , & num2 ) ;
7
8 if ( num1 > num2 ) {
9 printf ( " \ nOrdem decrescente . " ) ;
2.9. COMANDOS DE SELECAO 43

Pseudocodigo 2.5 Compara dois numeros e informa se foram digitados em ordem crescente,
decrescente ou se sao iguais.
Descricao: Algoritmo para decidir se dois numeros digitados estao em ordem crescente, decrescente ou
se sao iguais.
Dados de Entrada: matrcula do aluno, notas.
Sada do Programa: mensagem dizendo se o aluno esta aprovado ou nao.
Func~
ao Principal:
Leia dois numeros inteiros.
SE o primeiros for maior que o segundo ENT~ AO
Imprima uma mensagem dizendo que e ordem crescente.
SEN~
AO
SE o segundo for maior que o primeiro ENT~ AO
Imprima uma mensagem dizendo que e ordem decrescente.
SEN~
AO
Imprima uma mensagem dizendo que os numeros s~
ao iguais.
FIM-SE.
FIM-SE.
FIM-Func~
ao Principal

10
11 } else {
12 if ( num2 > num1 ) {
13 printf ( " \ nOrdem crescente . " ) ;
14
15 } else {
16 printf ( " \ nNumeros iguais . " ) ;
17 }
18 }
19 }

Exemplo 2.21: Comando de selecao if aninhado em outro comando if.

A estrutura dos comandos de selecao do Exemplo 2.21 permite testar todos os casos. Observe
que nao ha uma verificacao explcita para a igualdade dos numeros. Isso acontece porque todos
os demais casos possveis sao testados antes e, se nao se tratar de nenhum desses, entao os
numeros certamente sao iguais. Nesse caso, basta apenas exibir a mensagem correspondente.
Em muitos casos, alem de ser elegante, o uso da estrutura de selecao aninhada pode ser
mais eficiente. Verifica-se esse fato nos Exemplos 2.22 e 2.23. Ambos os programas leem tres
numeros digitados pelo usuario e identificam o maior deles, porem, um dos programas faz menos
verificacoes que o outro, ganhando eficiencia.

1 # include < stdio .h >


2 main () {
44 CAPITULO 2. CONCEITOS BASICOS

3 int n1 , n2 , n3 ;
4
5 printf ( " Entre com os tres numeros : " ) ;
6 scanf ( " % d % d % d " , & n1 , & n2 , & n3 ) ;
7
8 if (( n1 > n2 ) && ( n1 > n3 ) ) {
9 printf ( " O maior numero digitado foi : % d . " , n1 ) ;
10 }
11
12 if (( n2 > n1 ) && ( n2 > n3 ) ) {
13 printf ( " O maior numero digitado foi : % d . " , n2 ) ;
14 }
15
16 if (( n3 > n1 ) && ( n3 > n2 ) ) {
17 printf ( " O maior numero digitado foi : % d . " , n3 ) ;
18 }
19 }

Exemplo 2.22: Programa que identifica o maior de tres numeros sem o uso do comando de
selecao aninhado.

1 # include < stdio .h >


2 main () {
3 int n1 , n2 , n3 ;
4
5 printf ( " Entre com os tres numeros separados por espacos : " ) ;
6 scanf ( " % d % d % d " , & n1 , & n2 , & n3 ) ;
7
8 if ( n1 > n2 ) {
9 if ( n1 > n3 ) {
10 printf ( " O maior numero digitado foi : % d . " , n1 ) ;
11 } else {
12 printf ( " O maior numero digitado foi : % d . " , n3 ) ;
13 }
14 } else {
15 if ( n2 > n3 ) {
16 printf ( " O maior numero digitado foi : % d . " , n2 ) ;
17 } else {
18 printf ( " O maior numero digitado foi : % d . " , n3 ) ;
19 }
20 }
21 }

Exemplo 2.23: Programa que identifica o maior de tres numeros, usando o uso do comando de
selecao aninhado.

No caso do Exemplo 2.22, o uso de varios comandos if isolados acarreta em perda de


eficiencia. Quando o primeiro numero digitado pelo usuario for o maior, o programa realiza
dois testes desnecessarios (linhas 12 e 16). E quando o segundo numero for o maior, o programa
2.9. COMANDOS DE SELECAO 45

realiza um teste desnecessario (linha 16).


No Exemplo 2.23, apenas dois testes sao executados (linhas 8 e 9 ou linhas 8 e 15), diminuindo
significativamente o numero de comparacoes em relacao ao Exemplo 2.22.
O programa do Exemplo 2.24 recebe do usuario tres numeros inteiros e verifica se eles podem
ser tamanhos dos lados de um triangulo. Caso verdadeiro, o programa ainda indica a classificacao
do triangulo. O Pseudocodigo 2.6 lista o algoritmo para esse exemplo.

Pseudocodigo 2.6 Informa se tres numeros podem constituir os lados de um triangulo e o tipo
do triangulo que pode ser formado.
Descricao: Algoritmo para calcular se tres numeros podem representar os lados de um triangulo e,
caso seja possvel, o tipo do triangulo.
Dados de Entrada: tres valores inteiros.
Sada do Programa: mensagem dizendo o tipo do triangulo ou se nao pode ser formado um triangulo.
Func~
ao Principal:
Leia tr^
es numeros inteiros.
SE os tr^es valores forem iguais ENT~
AO
Imprima uma mensagem dizendo que e tri^
angulo equilatero.
~
SENAO
SE cada valor e menor do que a soma dos outros dois ENT~AO
es valores forem diferentes ENT~
SE os tr^ AO
Imprima uma mensagem dizendo que e tri^
angulo escaleno.
SEN~
AO
Imprima uma mensagem dizendo que e tri^
angulo isosceles.
FIM-SE.
SEN~
AO
Imprima uma mensagem dizendo que os valores n~ao podem ser lados de um tri^
angul
FIM-SE.
FIM-SE.
FIM-Func~
ao Principal

1 # include < stdio .h >


2 main () {
3 int lado1 , lado2 , lado3 ;
4 int soma1 , soma2 , soma3 ;
5
6 printf ( " Entre com tres numeros inteiros : " ) ;
7 scanf ( " % d % d % d " , & lado1 , & lado2 , & lado3 ) ;
8
9 soma1 = lado1 + lado2 ;
10 soma2 = lado1 + lado3 ;
11 soma3 = lado2 + lado3 ;
12
46 CAPITULO 2. CONCEITOS BASICOS

13 if (( lado1 == lado2 ) && ( lado2 == lado3 ) ) {


14 printf ( " Triangulo equilatero \ n " ) ;
15 } else {
16
17 if (( soma1 > lado3 ) && ( soma2 > lado2 ) && ( soma3 > lado1 ) ) {
18
19 if (( lado1 != lado2 ) && ( lado1 != lado3 ) && ( lado2 != lado3 ) ) {
20 printf ( " Triangulo escaleno \ n " ) ;
21 } else {
22 printf ( " Triangulo isosceles \ n " ) ;
23 }
24 } else {
25 printf ( " Os tres lados nao formam um triangulo \ n " ) ;
26 }
27 }
28 }

Exemplo 2.24: Programa que indica se tres numeros formam um triangulo.

No Exemplo 2.24, o comando if mais externo (linha 13) confere se os numeros formam um
triangulo equilatero, verificando se os tres sao iguais. Caso nao formem um triangulo equilatero,
na linha 17 e verificado se os tres numeros formam realmente um triangulo, comparando a soma
de dois lados com a medida do terceiro (para todas as possibilidades). Caso verdadeiro, o if mais
interno confere se os tres lados sao diferentes um do outro, formando um triangulo escaleno; o
resultado falsopara esse ultimo teste implica necessariamente na formacao de um triangulo
isosceles.

2.9.3 Comando de selecao multipla


O comando de selecao if e bastante poderoso e largamente utilizado. Entretanto, ele nao e o
unico comando de selecao existente. Ha um comando que, embora seja menos abrangente que o
comando if, resolve muitas situacoes de forma clara e elegante. Trata-se do comando de selecao
multipla.
O comando de selecao multipla sempre pode ser substitudo por um ou mais comandos if
aninhados, mas ele torna o codigo muito mais claro quando se quer executar varias sequencias
de comandos diferentes, sendo que cada sequencia deva corresponder a um determinado valor
de uma expressao. A estrutura seguinte facilita a compreensao do comando.

switch (<express~ao>){
case <valor1>:
<sequ^
encia de comandos 1>
break;
case <valor2>:
<sequ^
encia de comandos 2>
break;
.
2.9. COMANDOS DE SELECAO 47

.
.
case <valorN>:
<sequ^
encia de comandos N>
break;
default :
<sequ^
encia de comandos>
}

Na estrutura do comando de selecao multipla, a expressao normalmente e uma expressao


aritmetica ou uma variavel de tipo numerica, e associa-se o valor da expressao ao valor de um
determinado case. Assim, apenas a sequencia de comandos do bloco case correspondente sera
executada. E importante observar que cada sequencia de comandos e encerrada pelo comando
break.
A clausula default e opcional. Ela marca a sequencia de comandos padrao, que deve ser
executada quando nenhuma comparacao obtiver sucesso. Se nao estiver presente, nada sera
executado nesse caso.
Na linguagem C, cada case deve servir para comparar o resultado da expressao com um
valor especfico. Nao se permite comparacoes com o resultado de outras expressoes.
No Exemplo 2.25 usa-se o comando switch na construcao de um programa que simula o
funcionamento de uma urna eletronica. O Pseudocodigo 2.7 exibe o algoritmo desse exemplo.

Pseudocodigo 2.7 Algoritmo de uma urna eletronica simplificada.


Descricao: Algoritmo para simular uma urna eletronica simplificada. Nao ha a opcao para voto em
branco.
Dados de Entrada: o numero do candidato.
Sada do Programa: mensagem dizendo o nome do candidato escolhido ou se o voto foi anulado.
Func~
ao Principal:
Leia o numero do candidato.
CASO o numero do candidato SEJA:
1, ENT~AO imprima uma mensagem dizendo que Hort^ensia da Silva foi escolhida.
2, ENT~AO imprima uma mensagem dizendo que Jose dos Cravos foi escolhido.
3, ENT~AO imprima uma mensagem dizendo que Margarida S. Pereira foi escolhida.
OUTRO VALOR, ENT~AO imprima uma mensagem dizendo que o voto foi anulado.
FIM-CASO
FIM-Func~
ao Principal

O comportamento do comando CASO-SEJA do Pseudocodigo 2.7 deve ser pensado da mesma


maneira que o do comando de selecao multipla switch.

1 # include < stdio .h >


2 main () {
48 CAPITULO 2. CONCEITOS BASICOS

3 int numero ;
4
5 printf ( " URNA ELETR O ^ NICA - SEU VOTO PARA PREFEITO : " ) ;
6 scanf ( " % d " , & numero ) ;
7
8 switch ( numero ) {
9 case 1:
10 printf ( " Candidato escolhido : Hort^ e ncia da Silva " ) ;
11 break ;
12 case 2:
13 printf ( " Candidato escolhido : Jose dos Cravos " ) ;
14 break ;
15 case 3:
16 printf ( " Candidato escolhido : Margarida S . Pereira " ) ;
17 break ;
18 default :
19 printf ( " Numero digitado invalido . Voto anulado . " ) ;
20 }
21 }

Exemplo 2.25: Uso do comando switch para simular uma urna eletronica de eleicao.

No programa do Exemplo 2.25, os numeros de 1 a 3 estao associados a determinados candi-


datos e, para qualquer outro numero, a clausula default e associada.
Apos a inicializacao da variavel numero com o comando de entrada de dados (linha 6), o
comando de selecao compara o numero digitado com os numeros dos tres candidatos inscritos.
Em cada caso, se o numero digitado coincide com a constante, a sequencia de comandos corres-
pondente ao candidato escolhido e executada. Se nenhuma comparacao obtiver sucesso, entao
o usuario digitou um numero de candidato que nao existe. Nesse caso, o programa exibe uma
mensagem de alerta para o usuario, avisando-lhe que o numero digitado e invalido para a eleicao
(linha 19).
Quando o comando break nao termina uma sequencia de comandos cuja comparacao resultou
verdadeira, a sequencia de comandos seguinte e executada sem que o teste correspondente seja
efetuado. Na pratica, o que acontece e que quando uma comparacao do switch resultar em
verdadeiro, todas as sequencias de comando seguintes sao executadas ate que o primeiro comando
break seja encontrado.
E preciso, portanto, ficar atento e finalizar cada sequencia de comandos com o comando
break evitando, assim, erros de programacao muitas vezes difceis de detectar.

2.10 Comandos de Repeticao


A capacidade que as maquinas possuem de repetir tarefas exaustivamente com a mesma quali-
dade e uma das principais razoes do sucesso de sua invencao. Os computadores podem repetir
uma ou mais sequencia de comandos quantas vezes for necessario, e e o programador quem
decide o criterio de parada das repeticoes.
2.10. COMANDOS DE REPETICAO 49

As aplicacoes para os comandos de repeticao sao praticamente infinitas porque quase todas
as tarefas contem partes que devem ser executadas mais de uma vez.
Na linguagem C, existem tres principais estruturas de repeticao. A opcao pelo uso de uma
ou outra depende normalmente da preferencia do programador e do problema em questao,
normalmente buscando-se o maximo de clareza ou facilidade de escrita do codigo. Essas tres
estruturas sao abordadas a seguir.

2.10.1 Comando de repeticao com pre-condicao


A estrutura do comando while e a mais simples das tres e seu funcionamento e igualmente
simples de compreender. A estrutura e a seguinte:

while (<express~ao logica>){


<Sequ^
encia de comandos>
}

O computador inicia o comando testando a validade da expressao logica. Se for verdadeira,


entao ele executa a sequencia de comandos e retorna ao teste de validacao da expressao logica,
reiniciando o ciclo. O computador repete esse processo ate que o resultado da expressao logica
seja falso.
No caso em que o primeiro teste de validacao da expressao logica resulte em falso, a sequencia
de comandos nao e executada nenhuma vez.
O Pseudocodigo 2.8 representa um algoritmo que recebe um numero inteiro positivo n e
imprime na tela os n primeiros inteiros positivos.

Pseudocodigo 2.8 Imprimir os n primeiros numeros positivos.


Descricao: programa para imprimir na tela os n primeiros numeros positivos.
Dados de Entrada: n (quantidade de inteiros a serem impressos).
Sada do Programa: os numeros inteiros de 1 a n.
Func~
ao Principal:
Leia n.
Valor recebe 1.
ENQUANTO Valor for menor ou igual a n FACA:
Imprima Valor.
Incremente Valor.
FIM-ENQUANTO
FIM-Func~
ao Principal

O comando ENQUANTO do Pseudocodigo 2.8 funciona da mesma maneira que o comando


de repeticao com pre-condicao while. O programa do Exemplo 2.26 mostra um codigo em C
que realiza as operacoes do referido pseudocodigo.
50 CAPITULO 2. CONCEITOS BASICOS

1 # include < stdio .h >


2 main () {
3 int n , valor ;
4
5 printf ( " Digite um numero inteiro positivo : " ) ;
6 scanf ( " % d " , & n ) ;
7 valor = 1;
8
9 while ( valor <= n ) {
10 printf ( " % d " , valor ) ;
11 valor = valor + 1;
12 }
13 }

Exemplo 2.26: Comando de repeticao while para imprimir os n primeiros numeros inteiros.

As variaveis n e valor do Exemplo 2.26 representam a quantidade de numeros impressos e o


proprio numero, respectivamente. A variavel n e inicializada pelo usuario atraves do comando
de entrada de dados; valor e inicializada com o numero 1, para posteriormente ser incrementada
a cada iteracao.
Pela expressao logica do while, nota-se que a condicao de parada de iteracoes ocorre quando
a variavel valor atingir um numero maior que o numero n, ou seja, o bloco de comandos do
while (linhas 10 e 11) e executado enquanto o numero armazenado em valor for menor ou igual
ao numero armazenado em n.
A condicao de parada em qualquer comando de repeticao e um fator muito importante que
se deve escolher com bastante cuidado. E preciso garantir que o computador efetue um numero
finito de repeticoes para que, em algum momento, o programa possa seguir seu fluxo normal
de execucao. Alem de resultados incorretos, uma condicao de parada erronea pode acarretar
uma execucao de programa sem termino (nessa situacao, diz-se que o programa esta em loop
infinito).
A Tabela 2.8 apresenta o resultado da execucao do programa do Exemplo 2.26, quando o
usuario informa o valor 4 para a variavel n.

n valor valor <= n Tela


4 1 Sim 1
4 2 Sim 12
4 3 Sim 123
4 4 Sim 1234
4 5 Nao 1234

Tabela 2.8: Detalhes da execucao do comando while do Exemplo 2.26.

O proximo programa, Exemplo 2.27, imprime os n primeiros numeros mpares. Essa quan-
tidade n e definida pelo usuario.
2.10. COMANDOS DE REPETICAO 51

1 # include < stdio .h >


2 main () {
3 int iterador , valor , n ;
4
5 printf ( " Imprimir numeros impares . Entre com a quantidade de termos : " ) ;
6 scanf ( " % d " , & n ) ;
7
8 iterador = 1;
9 valor = 1;
10
11 while ( iterador <= n ) {
12 printf ( " \ n % d " , valor ) ;
13 valor = valor + 2;
14 iterador = iterador + 1;
15 }
16 }

Exemplo 2.27: Programa que imprime os n primeiros numeros mpares, utilizando o comando
de repeticao while.

No programa do Exemplo 2.27, a quantidade de numeros mpares a ser impresso e fornecido


pelo usuario atraves do comando scanf. A variavel iterador e usada para armazenar o numero a
ser impresso em cada iteracao do comando while. O bloco de instrucoes do comando de repeticao
(linhas 11 e 12) e executado enquanto o numero armazenado em iterador for menor ou igual ao
numero fornecido pelo usuario.
Com os comandos de repeticao e possvel melhorar o programa da urna eletronica proposto
no Exemplo 2.25, da secao anterior. Naquele exemplo, o programa nao confirma o voto do
usuario, impossibilitando que um erro de digitacao pudesse ser corrigido durante a execucao do
programa. O Pseudocodigo 2.9 lista o algoritmo melhorado.

Pseudocodigo 2.9 Urna eletronica com opcao de corrigir voto.


Descricao: programa para imprimir na tela os n primeiros numeros positivos.
Dados de Entrada: numero do candidato, resposta da confirmacao.
Sada do Programa: mensagem indicando o candidato escolhido.
Func~
ao Principal:
Resposta recebe zero.
ENQUANTO resposta for igual a zero FACA:
Leia o numero do candidato.
Imprima o nome do candidato escolhido.
Leia a resposta da confirmac~
ao.
FIM-ENQUANTO.
FIM-Func~
ao Principal
52 CAPITULO 2. CONCEITOS BASICOS

No codigo do Exemplo 2.28, o usuario tem a possibilidade de corrigir o seu voto sem que o
programa termine.

1 # include < stdio .h >


2 main () {
3 int numero , resposta ;
4
5 resposta = 0;
6
7 while ( resposta == 0) {
8 printf ( " URNA ELETRONICA - SEU VOTO PARA PREFEITO : " ) ;
9 scanf ( " % d " , & numero ) ;
10
11 switch ( numero ) {
12 case 1:
13 printf ( " Candidato escolhido : Hortencia da Silva " ) ;
14 break ;
15 case 2:
16 printf ( " Candidato escolhido : Jose dos Cravos " ) ;
17 break ;
18 case 3:
19 printf ( " Candidato escolhido : Margarida S . Pereira " ) ;
20 break ;
21 default :
22 printf ( " Numero digitado invalido . Voto NULO . " ) ;
23 }
24
25 printf ( " \ nDigite 1 para CONFIRMAR ou 0 para CORRIGIR " ) ;
26 scanf ( " % d " , & resposta ) ;
27 }
28 }

Exemplo 2.28: Comando de repeticao while utilizado para melhorar o codigo do Exemplo 2.25.

O criterio de parada escolhido no Exemplo 2.28 e a alteracao do valor da variavel resposta.


Enquanto essa variavel armazena o valor zero, o programa repetira o processo de votacao e
perguntara ao usuario se confirma o voto ou se gostaria de corrigi-lo. Quando o valor da
variavel resposta e alterado, a expressao logica da linha 7 retorna falso, encerrando o comando
de repeticao.

2.10.2 Comando de repeticao com pos-condicao


Em contraste com o comando de pre-condicao, o comando de repeticao com pos-condicao so
efetua o teste da expressao logica (condicao de parada) apos a primeira execucao da sequencia
de comandos. Logo, o bloco de comandos e executado pelo menos uma vez, independente da
expressao logica. A propria sintaxe do comando sugere essa diferenca:

do{
2.10. COMANDOS DE REPETICAO 53

<Sequ^
encia de comandos>
} while(<express~ao logica>);

O Pseudocodigo 2.9 pode ser modificado para melhorar sua legibilidade. Com o emprego do
comando FACA-ENQUANTO, o algoritmo fica mais simples.

Pseudocodigo 2.10 Urna eletronica com opcao de corrigir voto (versao melhorada).
Descricao: programa para imprimir na tela os n primeiros numeros positivos.
Dados de Entrada: numero do candidato, resposta da confirmacao.
Sada do Programa: mensagem indicando o candidato escolhido.
Func~
ao Principal:
FACA:
Leia o numero do candidato.
Imprima o nome do candidato escolhido.
Leia a resposta da confirmac~
ao.
ENQUANTO resposta for igual a zero.
FIM-Func~ao Principal

O programa do Exemplo 2.28 pode ser reescrito utilizando-se o comando do-while. Nesse
caso, o uso do do-while permite ao programador ter a opcao de nao inicializar a variavel resposta
antes da execucao da sequencia de comandos, responsabilizando o usuario pela inicializacao
(conforme se nota no Exemplo 2.29).

1 # include < stdio .h >


2 main () {
3 int numero , resposta ;
4 /* A variavel resposta n~ a o e mais inicializada pelo programador */
5 do
6 {
7 printf ( " URNA ELETRONICA - SEU VOTO PARA PREFEITO : " ) ;
8 scanf ( " % d " , & numero ) ;
9 switch ( numero )
10 {
11 case 1:
12 printf ( " Candidato escolhido : Hortencia da Silva " ) ;
13 break ;
14 case 2:
15 printf ( " Candidato escolhido : Jose dos Cravos " ) ;
16 break ;
17 case 3:
18 printf ( " Candidato escolhido : Margarida S . Pereira " ) ;
19 break ;
20 default :
21 printf ( " Numero digitado invalido . Voto NULO . " ) ;
22 }
54 CAPITULO 2. CONCEITOS BASICOS

23 printf ( " \ nDigite 1 para CONFIRMAR ou 0 para CORRIGIR " ) ;


24 scanf ( " % d " , & resposta ) ;
25 } while (! resposta ) ;
26 }

Exemplo 2.29: Aplicacao para o comando do-while, modificando o codigo do Exemplo 2.28.

No Exemplo 2.29, nota-se que nao ha mais a inicializacao explcita da variavel resposta.
Com o comando do-while, garante-se que a variavel sera inicializada pelo usuario antes de seu
valor ser testado na expressao logica da linha 25, utilizada como criterio de parada.
Pode parecer estranho, a primeira vista, como a expressao logica !resposta funciona. Na
verdade essa espressao e equivalente ao teste resposta == 0.
Para entender essa equivalencia, basta lembrar que o operador logico de negacao inverte o
valor logico da variavel ao qual ele e aplicado. E bom recordar tambem que, para os numeros
inteiros, o valor zero equivale a falso enquanto que qualquer numero diferente de zero e tomado
como valor logico verdadeiro. Assim sendo, sempre que o programador desejar testar se um
valor inteiro e igual a zero, basta testar o resultado do operador de negacao, pois o unico caso
em que ele retornara verdadeiro sera quando o valor numerico testado for zero.
Um outro exemplo simples para o uso do comando do-while e um algoritmo no qual o usuario
informa uma sequencia de valores positivos e, em seguida, a media desses valores e apresentada.
O usuario devera digitar zero quando quiser finalizar a sequencia e saber a media. O algoritmo
desse programa esta listado no Pseudocodigo 2.11.

Pseudocodigo 2.11 Calculo da media de numeros positivos digitados pelo usuario.


Descricao: Algoritmo para calculo da media de numeros positivos digitados pelo usuario.
Dados de Entrada: numeros nao negativos.
Sada do Programa: media dos valores digitados.
Func~
ao Principal:
FACA:
Leia um valor.
SE valor e positivo ENT~
AO
Atualize a soma dos numeros digitados.
Incremente a quantidade de numeros digitados.
FIM-SE.
ENQUANTO valor digitado for positivo.
SE o usuario digitou algum valor positivo ENT~AO
Calcule a media dos valores digitados.
Imprima essa media.
FIM-SE.
FIM-Func~ao Principal

O Exemplo 2.30 exibe o programa para o Pseudocodigo 2.11.


2.10. COMANDOS DE REPETICAO 55

1 # include < stdio .h >


2 main () {
3 float valor , soma , media ;
4 int quantidade ;
5
6 soma = 0;
7 quantidade = 0;
8 do {
9 printf ( " Informe um valor positivo ou zero para encerrar o programa : " ) ;
10 scanf ( " % f " ,& valor ) ;
11 if ( valor > 0) {
12 soma = soma + valor ;
13 quantidade = quantidade + 1;
14 }
15 } while ( valor > 0) ;
16 if ( quantidade > 0) {
17 media = soma / quantidade ;
18 printf ( " Media dos valores digitados : % f \ n " , media ) ;
19 }
20 }

Exemplo 2.30: Programa que calcula a media dos valores de uma sequencia digitada pelo usuario.

O funcionamento do programa do Exemplo 2.30 e simples. A variavel soma guarda a soma


dos valores digitados pelo usuario e deve ser inicializada com zero. A cada novo valor positivo
digitado, seu valor deve ser atualizado. Essa atualizacao ocorre na linha 12. Observa-se que o
comando de selecao da linha 11 impede que haja atualizacao das variaveis soma e quantidade
quando o usuario quer encerrar o programa e digita zero.
A variavel valor e a condicao de parada do comando de repeticao. Sempre que o usuario
digita zero (ou ate mesmo um numero negativo), o comando de repeticao e encerrado.
Para saber quantos numeros foram digitados, a variavel quantidade e empregada. Ela e
inicializada com zero e, sempre que o usuario digita um numero positivo, ela e incrementada
(linha 13).
Quando o comando de repeticao e encerrado, e preciso verificar se o usuario digitou algum
valor. Essa verificacao se deve ao fato de que o primeiro valor digitado pode ser zero, indicando
que o usuario saiu do programa e nao quis calcular nada. No caso de algum valor valido ter
sido informado (expressao logica da linha 16), entao a media e calculada e uma mensagem com
o resultado e exibida ao usuario (linhas 17 e 18).

2.10.3 Comando de repeticao condensado


O comando de repeticao condensado permite agrupar, em um unico comando, a inicializacao
de uma variavel, o incremento desta variavel e o teste de parada. Seu uso e adequado para
situacoes em que o numero de repeticoes da sequencia de comandos ja e conhecido.
A estrutura do comando e exibida a seguir:
56 CAPITULO 2. CONCEITOS BASICOS

for (<inicializac~
ao>; <express~
ao logica>; <incremento>){
<Sequencia de comandos>
}

Inicializacao e um comando de atribuicao usado para colocar um valor na variavel de controle


utilizada na expressao logica. Assim como os demais comandos de repeticao, a expressao logica
representa a condicao de parada das iteracoes. Incremento define como a variavel de controle
que sera modificada a cada iteracao.
Um exemplo de como utilizar o comando for esta listado no codigo do Exemplo 2.31. Esse
programa serve para imprimir os n primeiros termos de uma PG (progressao geometrica). O
usuario deve informar os valores do primeiro termo, da razao e da quantidade de termos a serem
exibidos.

1 main () {
2 float primeiroTermo , razao , n , i , termo ;
3
4 printf ( " Informe o valor do primeiro termo da PG : " ) ;
5 scanf ( " % f " ,& primeiroTermo ) ;
6 printf ( " Informe o valor da razao da PG : " ) ;
7 scanf ( " % f " ,& razao ) ;
8 printf ( " Informe quantos termos devem ser impressos : " ) ;
9 scanf ( " % f " ,& n ) ;
10 termo = primeiroTermo ;
11 for ( i =0; i < n ; i = i +1) {
12 printf ( " % f " , termo ) ;
13 termo = termo * razao ;
14 }
15 }

Exemplo 2.31: Programa para exibir os n primeiros termos de uma progressao geometrica utilizando o
comando de repeticao for.

No Exemplo 2.31, as variaveis primeiroTermo, razao e n sao inicializadas pelo usuario,


usando o comando de entrada de dados. A linha 10 trata da inicializacao da variavel termo,
que guarda o valor do termo da PG a ser impresso a cada repeticao (iteracao) do comando for.
O comando de repeticao, em primeiro lugar, inicializa a variavel de controle i com o valor
zero. Em seguida, ocorre a verificacao da expressao logica. Caso o usuario tenha digitado
zero, o comando nao executa sua sequencia de instrucoes nenhuma vez, pois tanto i quanto n
armazenam zero e tornam falso o resultado da expressao logica. Nesse caso, nada e impresso na
tela.
Mas se o usuario digitou um valor maior que zero, a sequencia de comandos sera repetida ate
que a quantidade solicitada de termos seja impressa. Para isso, o comando de sada de dados e
chamado logo no incio da sequencia de comandos (linha 12). No caso em que i e igual a zero
(primeira iteracao), o primeiro termo e exibido. A linha 13 conclui a sequencia de comandos
atualizando a variavel termo para guardar o proximo termo da sequencia.
2.10. COMANDOS DE REPETICAO 57

Um passo implcito e que o comando realiza o incremento da variavel i no momento apos


a execucao da ultima instrucao da sequencia de comandos. Feita essa atualizacao, a condicao
de parada e novamente verificada e, caso seja verdadeira, uma nova repeticao e comecada para
imprimir outro termo da PG.
A Tabela 2.9 mostra passo a passo a execucao completa do comando de repeticao para o
programa do Exemplo 2.31. Os valores informados sao para uma PG de razao igual a 2, primeiro
termo igual a 2 e quantidade de termos a serem impressos igual a 4. As colunas mostram as
variaveis principais do comando de repeticao (i, termo e n), o resultado da expressao logica a
cada iteracao e o que foi impresso na tela. Os valores sao referentes ao momento posterior a
execucao da sequencia de instrucoes, antes do incremento da variavel de controle.

i termo n i < n Tela


0 4 4 Verdadeiro 2
1 8 4 Verdadeiro 24
2 16 4 Verdadeiro 248
3 32 4 Verdadeiro 2 4 8 16
4 32 4 Falso 2 4 8 16

Tabela 2.9: Detalhes da execucao do comando for do Exemplo 2.31


.

O programa do Exemplo 2.32 realiza a operacao de exponenciacao de um numero, utilizando


o comando de repeticao for.

1 # include < stdio .h >


2 main () {
3 int base , expoente , resultado , i ;
4
5 printf ( " Informe os valores para base e expoente : " ) ;
6 scanf ( " % d % d " , & base , & expoente ) ;
7
8 resultado = 1;
9 for ( i = 0; i < expoente ; i = i + 1) {
10 resultado = resultado * base ;
11 }
12
13 printf ( " % d elevado a % d = % d \ n " , base , expoente , resultado ) ;
14 }

Exemplo 2.32: Programa para realizar a operacao de exponenciacao utilizando o comando de repeticao
for.

No Exemplo 2.32, os valores da base e do expoente sao fornecidos pelo usuario via comando
de entrada de dados (linha 6). A variavel de controle i e inicializada com o valor zero dentro
do comando for e, a cada iteracao, e incrementada de 1. Quando o valor de i atinge valor igual
ao valor da variavel expoente, as iteracoes sao terminadas e o programa segue para a linha 13,
58 CAPITULO 2. CONCEITOS BASICOS

imprimindo os valores da base, do expoente e do resultado da exponenciacao.


Assim como o comando while, o comando for efetua primeiramente o teste da condicao de
parada antes de executar a sequencia de comandos pela primeira vez.

2.11 Problema dos Lotes Encaixantes


Com o conhecimendo dos conceitos basicos da programacao, uma primeira classe de problemas
pode ser estudada. Trata-se do Problema dos Lotes Encaixantes, que corresponde a situacoes
em que se precisa executar uma varredura em uma massa de dados e extrair dela algumas
informacoes estatsticas.
O termo lotes encaixantes refere-se ao fato de que, na maioria das vezes, problemas como esse
envolvem entidades complexas compostas por outras mais simples. As entidades mais simples
se encaixam para formar outras com um grau de complexidade ainda maior. Assim sendo, e
possvel visualizar varios nveis que variam desde os mais simples e com muitos elementos ate
os mais complexos e com algumas dezenas de integrantes.
A Figura 2.7 ajuda a explicar a ideia. Nela, os quadrados de bordas contnuas e finas repre-
sentam o nvel mais simples. Eles se agrupam para formar os quadrados de bordas pontilhadas,
que representam um nvel intermediario de complexidade. Juntos, os quadrados pontilhados
compoem o nvel mais complexo, representado pelo grande quadrado de borda grossa. Na
pratica, podem existir nao apenas tres, mas quantos nveis forem necessarios para caracterizar
os dados de um programa.

Figura 2.7: Uma representacao grafica para os lotes encaixantes

Considera-se, como exemplo, o problema de um professor que ministra uma disciplina para
diversas turmas. Nesse caso, e interessante saber qual turma obteve melhor rendimento, o aluno
2.11. PROBLEMA DOS LOTES ENCAIXANTES 59

que mais se destacou em cada turma e os que menos renderam nos estudos. Os dados, nesse
caso, podem ser observados em quatro nveis.
O nvel de notas e o mais simples. Ele e indivisvel e e caracterizado pelas notas dos trabalhos
e provas realizados no semestre. O nvel seguinte e o das medias semestrais em que as notas sao
agrupadas por aluno. O terceiro agrupamento e o das turmas. Os alunos sao reunidos de acordo
com as turmas a que pertencem. Por ultimo, o nvel mais complexo, e a propria disciplina. Ela e
a entidade mais abrangente, que e composta diretamente pelas turmas existentes no determinado
semestre.
Problemas de lotes encaixantes, normalmente, sao resolvidos por meio do emprego de coman-
dos de repeticao aninhados. Quanto mais externo for o comando de repeticao, maior o nvel de
complexidade das entidades que estao sendo examinadas. A Figura 2.8 exibe essa ideia, usando
como exemplo o problema das notas escolares.

Figura 2.8: Comandos de repeticao aninhados, utilizados para resolver um problema de lotes
encaixantes.

O Pseudocodigo 2.12 descreve um algoritmo para realizar um levantamento estatstico do


desempenho de alunos e turmas de uma disciplina hipotetica num determinado semestre.
O Exemplo 2.33 ilustra o codigo para um programa que le os dados das notas dos alunos e
exibe a media de cada um. Para cada turma, sera exibida a melhor e a pior media. Ao final,
exibe a turma com maior percentual de aprovacoes da disciplina.
60 CAPITULO 2. CONCEITOS BASICOS

Pseudocodigo 2.12 Problema dos Lotes Encaixantes para o problema das notas escolares
Descricao: programa para analisar as notas de uma disciplina escolar.
Dados de Entrada: codigo da turma, matrculas dos alunos, notas dos alunos.
Sada do Programa: media de cada aluno, matrculas dos alunos de melhor e de pior medias de cada
turma, codigo da turma com melhor rendimento.
Func~
ao Principal:
Leia o codigo de uma turma.
ENQUANTO codigo for diferente de -1 FACA:
Leia o numero de matrcula de um aluno.
ENQUANTO o numero de matrcula for diferente de -1 FACA:
Leia uma nota do aluno.
ENQUANTO a nota digitada n~ ao for negativa FACA:
Leia outra nota do aluno.
Atualize os dados do aluno.
FIM-ENQUANTO
Atualize os dados da turma.
Imprima a media das notas do aluno.
Leia o numero de matrcula de um aluno.
FIM-ENQUANTO
Atualize os dados da disciplina.
Imprima os numeros de matrcula e as medias dos alunos com maior
e menor medias da turma.
Leia o codigo de uma turma.
FIM-ENQUANTO
Impria o codigo da turma com melhor aproveitamento.
FIM-Func~
ao Principal

1 # include < stdio .h >


2 # define MEDIAMINIMA 7.0
3
4 main () {
5 int codTurma , matricula , numNotasAluno , numAlunosTurma , alunoMaiorMedia ,
alunoMenorMedia , melhorTurma ;
6 float nota , mediaAluno , numAprovadosTurma , somaNotasAluno , maiorMediaTurma ,
menorMediaTurma , aproveitamentoTurma , melhorAproveitamento ;
7
8 m e l h o r Aproveitamento = 0;
9
10 printf ( " Informe o codigo da turma : " ) ;
11 scanf ( " % d " , & codTurma ) ;
12 while ( codTurma != -1) {
13 maiorMediaTurma = 0;
14 menorMediaTurma = 10;
2.11. PROBLEMA DOS LOTES ENCAIXANTES 61

15 numAprovadosTurma = 0;
16 numAlunosTurma = 0;
17
18 printf ( " Informe o numero de matricula do aluno : " ) ;
19 scanf ( " % d " , & matricula ) ;
20 while ( matricula != -1) {
21 numNotasAluno = 0;
22 somaNotasAluno = 0;
23 numAlunosTurma = numAlunosTurma + 1;
24
25 printf ( " Informe a nota do aluno : " ) ;
26 scanf ( " % f " , & nota ) ;
27 while ( nota != -1) {
28 somaNotasAluno = somaNotasAluno + nota ;
29 numNotasAluno = numNotasAluno + 1;
30 printf ( " Informe a nota do aluno ou -1 para encerrar : " ) ;
31 scanf ( " % f " , & nota ) ;
32 }
33
34 mediaAluno = somaNotasAluno / numNotasAluno ;
35
36 if ( mediaAluno >= MEDIAMINIMA ) {
37 numAprovadosTurma = numAprovadosTurma + 1;
38 }
39
40 if ( mediaAluno >= maiorMediaTurma ) {
41 maiorMediaTurma = mediaAluno ;
42 alunoMaiorMedia = matricula ;
43 }
44
45 if ( mediaAluno <= menorMediaTurma ) {
46 menorMediaTurma = mediaAluno ;
47 alunoMenorMedia = matricula ;
48 }
49 printf ( " Media do aluno : % f \ n " , mediaAluno ) ;
50 printf ( " Informe o numero de matricula do aluno ou -1 para encerrar : "
);
51 scanf ( " % d " , & matricula ) ;
52 }
53
54 a p roveitamentoTurma = ( numAprovadosTurma / numAlunosTurma ) * 100;
55 if ( melhorAproveitamento < aproveitamentoTurma ) {
56 melhorAproveitamento = aproveitamentoTurma ;
57 melhorTurma = codTurma ;
58 }
59 printf ( " O aluno % d obteve a melhor media da turma (% f ) \ n " , alunoMaiorMedia
, maiorMediaTurma ) ;
60 printf ( " O aluno % d obteve a pior media da turma (% f ) \ n " , alunoMenorMedia ,
menorMediaTurma ) ;
61 printf ( " Informe o codigo da turma ou -1 para encerrar : " ) ;
62 scanf ( " % d " , & codTurma ) ;
62 CAPITULO 2. CONCEITOS BASICOS

63 }
64 printf ( " A turma % d obteve o melhor aproveitamento (% f %%) " , melhorTurma ,
m e l horAproveitamento ) ;
65 }

Exemplo 2.33: Problema dos lotes encaixantes aplicado a notas de uma disciplina

Fazendo uma comparacao com a Figura 2.8 e com o Pseudocodigo 2.12, nao fica complicado
entender o funcionamento do codigo do Exemplo 2.33. Apos a declaracao das variaveis, o
programa inicializa a variavel melhorAproveitamento (linha 8), que armazena o percentual de
aprovacoes da turma de melhor aproveitamento. O valor ajustado e o menor valor possvel (zero)
e sua escolha sera justificada adiante. Em seguida, o programa solicita ao usuario que digite um
codigo para identificar a primeira turma que sera analisada (linhas 10 e 11).
O primeiro comando de repeticao, na linha 12, serve para percorrer todas as turmas, uma
a uma. Sempre que sua expressao logica resultar verdadeiro, e sinal que uma nova turma tera
seus dados digitados.
Aqui vale destacar um artifcio util que e empregado nesse codigo. Todos os comandos de
repeticao fazem a verificacao de suas variaveis de controle comparando-as com o mesmo valor
1. Ocorre que, para evitar que o usuario seja obrigado a saber, a priori, da quantidade de
elementos de um conjunto de dados que sera passado ao programa, utiliza-se um valor absurdo
(qualquer valor que nao pertenca ao conjunto em questao) para servir de sinalizacao para o
programa de que o cunjunto de dados foi inteiramente percorrido. Esse valor e, comumente,
chamado de flag.
No Exemplo 2.33, considera-se que os codigos das turmas e das matrculas sao inteiros
positivos e que as notas sao valores racionais nao negativos. Assim sendo, o valor 1 foi utilizado
para indicar ao programa quando todas as notas de um aluno foram ja digitadas. Ou que todos
os dados dos alunos de uma determinada turma foram digitados. Ou ainda, que todas as turmas
tiveram seus dados informados.
A sequencia de comandos comeca, entao, com a inicializacao das variaveis da turma em
questao (linhas 13 a 16). As variaveis maiorMediaTurma e menorMediaTurma guardam, res-
pectivamente, a melhor e a pior media obtidas na turma. Ja numAprovadosTurma e nu-
mAlunosTurma armazenam, respectivamente, a quantidade de alunos aprovados na turma e a
quantidade total de alunos da turma. Os valores dessas duas variaveis servem para calcular o
aproveitamento da turma.
Nas linhas 18 e 19, o programa solicita ao usuario que informe a matrcula do primeiro aluno
da turma que tera lidas suas notas. Na linha seguinte aparece o segundo comando de repeticao,
o qual interage com os dados dos alunos de uma turma.
Assim como no comando de repeticao mais externo, o comando da linha 21 inicia sua
sequencia de comandos realizando a inicializacao das variaveis pertinentes. As variaveis num-
NotasAluno e somaNotasAluno servem para calcular a media aritmetica simples das notas do
aluno em questao armazenando, respectivamente, a quantidade de notas do aluno e a soma
delas. Na linha 23, a variavel numAlunosTurma e incrementada, indicando a adicao dos dados
de mais um aluno.
2.11. PROBLEMA DOS LOTES ENCAIXANTES 63

As linhas 25 e 26 requerem ao usuario que ele digite o valor da primeira nota do aluno em
questao. Em seguida, o programa executa o comando de repeticao mais interno, que serve para
registrar todas as notas de um determinado aluno.
Esse comando coleta os dados das notas, solicitando ao usuario que digite o valor da nota
(linhas 30 e 31) e atualizando os valores das variaveis que calculam a media do aluno no semestre.
Encerrada a digitacao das notas, o usuario deve digitar 1 para encerrar e, quando o faz, o
programa passa para a rotina de calculo da media (linha 34).
Com o valor da media, algumas verificacoes sao feitas para atualizar as estatsticas da turma
em questao. Em primeiro lugar, e verificado se a media alcancada pelo aluno e suficiente para
aprovacao. Em caso afirmativo, a variavel numAprovadosTurma e incrementada.
E verificado, em seguida (linha 40), se a media do aluno e maior que a maior media encontrada
ate entao. Em caso verdadeiro, a variavel maiorMediaTurma e atualizada, assim como a variavel
alunoMaiorMediaTurma, que guardara a matrcula desse aluno.
O comando de selecao da linha 45 e equivalente ao da linha 40. Desta vez, a intencao e
atualizar os dados do aluno com pior rendimento.
Vale um comentario sobre os valores de inicializacao das variaveis maiorMediaTurma e me-
norMediaTurma. E preciso ter em mente que, para a primeira verificacao de melhor e pior
aluno de cada turma, os respectivos comandos de selecao devem comparar o valor da media
calculada com os valores ja armazenados. O problema e que, para o primeiro aluno de cada
turma, essas variaveis ainda nao contem valores validos. Sendo assim, para evitar erros, a
variavel maiorMediaTurma e inicializada com o menor valor possvel para a media e a variavel
menorMediaTurma, ao contrario, e inicializada com o maior valor possvel. Essa inicilizacao
garante que os dados do primeiro aluno serao corretamente assimilados como o melhor e o pior
resultado encontrado. Essa condicao e correta no incio, pois os dados do primeiro aluno sao os
unicos passados ao sistema ate entao.
O comando da linha 49 imprime os dados do aluno, informando sua matrcula e a media
alcancada. Em seguida o usuario deve informar o codigo da matrcula do proximo aluno ou
encerrar o cadastro dos dados da turma em analise, digitando 1.
Caso tenha encerrado as digitacoes de uma turma, o programa executa o calculo do respectivo
aproveitamento (linha 54).
Feito isso, um comando de selecao verifica se o aproveitamento calculado e melhor que o
melhor ja encontrado. Em caso afirmativo, as variaveis melhorAproveitamento e melhorTurma
sao atualizadas para apontar para a turma recem analisada (linhas 56 e 57, respectivamente).
Terminando a sequencia de instrucoes do segundo comando de repeticao, os dados do melhor
e do pior aluno da turma sao exibidos (linhas 59 e 60). Por ultimo, e perguntado ao usuario
se ele deseja incluir os dados de uma outra turma, bastando, para isso, digitar seu codigo. Se
digitar a flag 1, o comando de repeticao e encerrado e o programa exibira, em sua ultima linha,
os dados da melhor turma (linha 64).
64 CAPITULO 2. CONCEITOS BASICOS

2.12 Exerccios Resolvidos


1. Faca um programa que calcule as razes reais da equacao do segundo grau ax2 + bx + c.

Pseudocodigo 2.13 Calculo das razes da equacao do segundo grau ax2 + bx + c.


Descricao: Algoritmo para calculo das razes de uma equacao do segundo grau ax2 + bx + c.
Dados de Entrada: coeficientes a, b e c.
Sada do Programa: razes da equacao.
Func~
ao Principal:
Leia os coeficientes a, b e c.
Calcule delta igual a b*b-4*a*c.
SE delta for maior ou igual a zero ENT~ AO
SE delta for igual a zero ENTAO~
Calcule a unica raiz igual a -b/(2*a).
Imprima o valor da raiz.
SEN~
AO
Calcule a raiz1 igual a (-b + raiz quadrada de delta)/(2*a).
Calcule a raiz2 igual a (-b - raiz quadrada de delta)/(2*a).
Imprima os valores das razes.
FIM-SE
SEN~
AO
Imprima uma mensagem dizendo que n~ ao ha razes reais.
FIM-SE
FIM-Func~
ao Principal

1 main () {
2
3 float a , b , c , delta , raizDelta , raiz1 , raiz2 ;
4
5 printf ( " Informe os valores das constantes a , b e c ( separados por
espacos ) : " ) ;
6 scanf ( " % f % f % f " , &a , &b , & c ) ;
7
8 delta = b * b - 4* a * c ;
9
10 if ( delta >= 0) {
11 if ( delta == 0) {
12 raiz1 = -b / (2* a ) ;
13 printf ( " As duas raizes sao iguais a : % f . " , raiz1 ) ;
14 } else {
15 raizDelta = sqrt ( delta ) ;
16 raiz1 = ( - b + raizDelta ) / (2 * a ) ;
17 raiz2 = ( - b - raizDelta ) / (2 * a ) ;
18 printf ( " As duas raizes s~ a o : % f e % f " , raiz1 , raiz2 ) ;
2.12. EXERCICIOS RESOLVIDOS 65

19 }
20 } else {
21 printf ( " Nao existem raizes reais desta equacao ! " ) ;
22 }
23 }

Exemplo 2.34: Programa para calcular as razes reais de uma equacao do segundo grau.

2. Escreva um programa que escreva os n primeiros termos da sequencia de Fibonacci. Essa


sequencia tem zero como primeiro termo e 1 como segundo. Do terceiro termo em diante,
a formula para obtencao e a soma dos dois anteriores.

1 main () {
2 int n , termoAtual , penultimoTermo , antePenultimoTermo , i ;
3
4 printf ( " Informe o numero de termos a serem impressos : " ) ;
5 scanf ( " % d " , & n ) ;
6
7 if ( n >= 1) {
8 printf ( " 0 " ) ;
9 }
10 if ( n >= 2) {
11 printf ( " 1 " ) ;
12 }
13
14 antePenultimoTermo = 0;
15 penultimoTermo = 1;
16 termoAtual = 2;
17 for ( i =0; i < n -2; i = i +1) {
18 printf ( " % d " , termoAtual ) ;
19 antePenultimoTermo = penultimoTermo ;
20 penultimoTermo = termoAtual ;
21 termoAtual = penultimoTermo + antePenultimoTermo ;
22 }
23 }

Exemplo 2.35: Programa para calcular as razes reais de uma equacao do segundo grau.

3. Fazer um programa para imprimir a tabuada de 1 a 9.

1 main () {
2 int n1 , n2 , r ;
3
4 printf ( " \ nTABUADA de 1 a 9\ n \ n " ) ;
5 n1 =1;
6 n2 =1;
7 while ( n1 <10) {
8 while ( n2 <10) {
66 CAPITULO 2. CONCEITOS BASICOS

9 r = n1 * n2 ;
10 printf ( " % d * % d = % d \ n " ,n2 , n1 , r ) ;
11 n2 = n2 +1;
12 }
13 printf ( " \ n " ) ;
14 n2 =1;
15 n1 = n1 +1;
16 }
17 }

Exemplo 2.36: Programa para exibir a tabuada de 1 a 9.

4. Calcular o Maximo Divisor Comum (MDC) de dois numeros. O MDC de dois numeros
pode ser obtido escolhendo o maior deles e subtraindo-lhe o valor do menor. Esta operacao
e repetida ate que os dois sejam iguais, cujo valor sera o MDC dos numeros iniciais:

33 15 45 18
18 15 27 18
03 15 09 18
03 12 09 09
03 09 MDC = 09
03 06
03 03
MDC = 03

1 main () {
2 int numero1 , numero2 , auxiliar1 , auxiliar2 ;
3
4 printf ( " Digite dois numeros para calcular seu MDC : " ) ;
5 scanf ( " % d % d " , & numero1 , & numero2 ) ;
6
7 auxiliar1 = numero1 ;
8 auxiliar2 = numero2 ;
9
10 while ( auxiliar1 != auxiliar2 ) {
11 if ( auxiliar1 > auxiliar2 ) {
12 auxiliar1 = auxiliar1 - auxiliar2 ;
13 } else {
14 auxiliar2 = auxiliar2 - auxiliar1 ;
15 }
16 }
17 printf ( " O MDC vale % d \ n " , auxiliar1 ) ;
18 }

Exemplo 2.37: Programa para exibir o MDC de dois numeros.


2.13. RESUMO 67

5. Faca um programa que converta um valor em base binaria para base decimal.

1 main () {
2 int binario , aux1 , aux2 , aux3 , decimal ;
3
4 printf ( " Digite um valor em base binaria : " ) ;
5 scanf ( " % d " , & binario ) ;
6
7 aux1 = binario ;
8 aux2 = 1;
9 decimal = 0;
10 while ( aux1 > 0) {
11 aux3 = aux1 % 10;
12 decimal = decimal + aux3 * aux2 ;
13 aux2 = aux2 *2;
14 aux1 = aux1 /10;
15 }
16 printf ( " O valor % d em base binaria equivale a % d em base decimal \ n " ,
binario , decimal ) ;
17 }

Exemplo 2.38: Programa para converter numeros de binario para decimal.

2.13 Resumo
O computador guarda as informacoes dos programas na memoria. A memoria pode ser
entendida como uma sequencia de celulas, cada uma identificada por um numero conhecido
como endereco de memoria. Nas linguagens de programacao, as variaveis permitem o
acesso as celulas de memoria sem a necessidade de manipular seus enderecos diretamente.
Na linguagem C, para a declaracao de uma variavel, o programador deve informar qual o
tipo de dados que ela ira manipular.
O comando de atribuicao permite atribuir uma determinada informacao (ou valor) a uma
variavel do programa.
Os identificadores sao nomes que identificam uma variavel ou constante. Eles sao formados
por caracteres alfanumericos, sendo que o primeiro deve ser obrigatoriamente alfabetico.
Na programacao, os quatro tipos de dados mais comuns sao: tipo inteiro, tipo ponto
flutuante, tipo booleano e tipo caractere.
Variaveis e constantes podem ser combinadas com operadores para formarem expressoes.
No comando de atribuicao, o valor da expressao (termos a direita do comando) e atribudo
a variavel (a esquerda do comando).
Os principais tipos de expressoes sao aritmetica, relacional e logica.
68 CAPITULO 2. CONCEITOS BASICOS

Na maioria dos problemas de computacao, os comandos de entrada de dados, sada de


dados, selecao e repeticao, sao imprescindveis.

Diversos problemas computacionais encaixam-se na modelagem do Problema dos Lotes


Encaixantes. Essa classe de problemas e facilmente resovida com o aninhamento de co-
mandos de repeticao, os quais destinam-se a examinar um nvel especfico da massa de
dados.

2.14 Exerccios Propostos


1. Escreva um programa, em C, que recebe um valor de angulo em graus e informa os valores
do seno, do cosseno e da tangente desse angulo.

2. Faca um programa, em C, que converta valores de temperatura de Fahrenheit para Celsius.


A proporcao utilizada e:
T emperaturaCelsius (T emperaturaF ahrenheit 32)
=
5 9
3. Faca um programa que calcule a soma
1 3 5 99
S= + + + ... +
1 2 3 50
4. Faca um programa que calcule a soma
37 38 36 37 35 36 12
S= + ... +
1 2 3 37
5. Um comerciante deseja aumentar suas vendas fazendo uma promocao. Os produtos que
ele trabalha e seus respectivos precos e codigos sao: a - anel - R$ 3,00, p - pulseira - R$
5,00, b - brinco - R$ 5,00 e c - cinto - R$ 10,00. As promocoes oferecidas sao:

comprar 1 unidade de cada produto, ganha-se um desconto de 10% no total da com-


pra.
comprar mais de 1 unidade de algum produto, ganha-se 5% de desconto no valor
total da compra. Faca um programa para simular a venda na loja do comerciante,
recebendo como dado de entrada o codigo do produto e o codigo da promocao que o
fregues quiser. Imprimir o valor original da compra e o valor com desconto. Utilize o
comando de selecao multipla (switch).

6. Fazer um programa que calcule e escreva uma tabela de graus centgrados em funcao de
graus farenheit que variam de 50 a 150 de 1 em 1.

7. Faca um programa para ler uma sequencia de n numeros inteiros positivos (um por vez),
e verificar se eles sao pares ou mpares.
2.14. EXERCICIOS PROPOSTOS 69

8. Suponha que a populacao de um pas A seja de 9.000 habitantes com uma taxa anual de
crescimento de 3% e que a populacao de um pas B seja, aproximadamente, de 20.000 de
habitantes com uma taxa anual de crescimento de 1,5%, fazer um programa que calcule
e escreva o numero de anos necessarios para que a populacao de pas A ultrapasse ou se
iguale a populacao do pas B, mantidas estas taxas de crescimento.

9. Uma certa firma fez uma pesquisa de mercado para saber se as pessoas gostaram ou nao de
um novo produto lancado no mercado. Para isso, obteve, para cada pessoa entrevistada,
informacoes a respeito do sexo do entrevistado e sua resposta (S = Sim ou N = Nao).
Sabe-se que foram entrevistados 2000 pessoas, fazer um programa que calcule e escreva:

O numero de pessoas que responderam sim


O numero de pessoas que responderam nao
A porcentagem de pessoas do sexo feminino que responderam sim
A porcentagem de pessoas do sexo masculino que responderam nao

10. A fabrica de chocolates MENINO esta com problemas financeiros e pretende fazer um
corte na folha de pagamento. Para isso, o setor de financas adotou o seguinte criterio para
reduzir a despesa com pessoal:

funcionarios com tempo de servico menor que 2 anos (24 meses) serao demitidos;
funcionarios com tempo de servico superior (ou igual) a 2 anos (24 meses) e menor
que 10 anos (120 meses) terao seus salarios reduzidos em 10%;
funcionarios com tempo de servico superior (ou igual) a 10 anos (120 meses) nao serao
demitidos e nem terao seus salarios reduzidos. Eles poderao optar por um plano de
demissao voluntaria com a seguinte proposta de indenizacao: 2 salarios atuais para
cada ano de servico.

Faca um programa para:

(a) ler mes e ano correntes;


(b) ler os dados dos funcionarios que sao: matrcula do funcionario, salario atual, mes e
ano de ingresso na fabrica. (obs: nao se sabe, a priori, o numero de funcionarios da
fabrica).
(c) aplicar o criterio acima descrito;
(d) imprimir para cada funcionario: a matrcula, o caso em que ele se enquadra no criterio.
Se o funcionario se enquadrar na reducao do salario, imprimir o salario novo. Se ele se
enquadrar no plano de demissao voluntaria, imprimir a indenizacao que o funcionario
recebera.
Captulo 3

Modularizacao
Co-autor:

Gilberto Segundo

Objetivos:

Definir o que e modularizacao;

Apresentar as vantagens de um programa modularizado;

Apresentar metodos de como criar e adaptar programas modularizados;

Introduzir o conceito de recursividade e como usa-la

Os programas sao escritos a fim de resolverem varios tipos de problemas. Alguns desses
problemas exigem mais tempo e maiores esforcos do programador. Por isso, e indispensavel que
o programador utilize de tecnicas que visam uma maior organizacao e consequente entendimento
facilitado do programa. Uma das tecnicas mais utilizadas e a modularizacao.
Este captulo trata sobre o conceito e a pratica de programas modularizados.

3.1 Introducao
Quando se trabalha em qualquer problema complexo, seja em programacao, seja em outra area,
o ser humano sente a necessidade de dividir esse problema maior em problemas menores. Cada
problema menor e entao trabalhado de forma a solucionar as questoes que lhe cabem e, juntando-
se cada uma dessas solucoes, tem-se a solucao do problema maior. Esse processo e chamado de
modularizacao e baseia-se na conhecida tecnica de dividir para conquistar.
Pode-se aplicar a modularizacao em diversas areas na vida cotidiana. Para a fabricacao de
um automovel, por exemplo, sao necessarios diversos servicos: funilaria, montagem, estofamento,
pintura, soldagem, etc. A princpio, poderia existir apenas um robo que fizesse todas as etapas
da fabricacao do carro, mas isso acarretaria em alguns problemas, tais como: complexidade

70
3.2. SUBPROGRAMAS 71

do robo, que deveria fazer todos os servicos de forma completa e eficiente; alto prejuzo em
eventuais servicos de manutencao, que teria que parar todo o processo de montagem; falta de
clareza no entendimento do processo de fabricacao, o que prejudicaria a expansao do processo
de montagem; entre outras.
Uma otima alternativa para esse problema e fazer varios robos, cada um com sua funcao
especfica. O que se estaria fazendo na verdade e uma modularizacao.
Pode-se facilmente mover as ideias contidas no problema apresentado para a elaboracao de
um programa. O programa pode ser dividido em varias partes, sendo que cada parte trata de
uma determinada funcionalidade.
Dessa forma, o programa tem a sua legibilidade melhorada, uma vez que fica mais facil
entender o que o programa faz por completo ou como o programa faz determinada subtarefa. A
modificabilidade do programa tambem e melhorada, uma vez que para se alterar determinada
funcionalidade, e necessario apenas alterar um pequena parte do codigo, nao sendo necessario
modificar varios pontos do codigo.
Como as funcionalidades do programa estando separadas logicamente, o programador tem a
oportunidade de reutilizar uma parte do programa para escrever um outro programa que utilize,
em uma de suas tarefas, o mesmo bloco de instrucoes. A confiabilidade do programa tambem e
aumentada pelo fato dele ser mais facil de ser entendido e corrigido.
Por fim, a produtividade para a elaboracao de um programa tambem e aumentada, uma vez
que cada parte do programa pode ser trabalhada por uma equipe diferente, alem de que cada
equipe so precisa saber o que as partes da outra equipe fazem e nao como fazem.
Nas secoes seguintes serao mostradas tecnicas de como fazer um programa modularizado
utilizando a linguagem C.

3.2 Subprogramas
Um subprograma e um trecho de um programa que realiza qualquer operacao computacional.
Ele efetua parte de uma tarefa que um algoritmo maior deveria executar, ou seja, ele e uma
parte do programa, especializado em alguma funcionalidade. Na linguagem C, o uso de funcoes
e a principal forma de modularizacao.
Uma funcao matematica f (x) e uma relacao que mapeia um dado valor x de um domnio em
um unico valor y de um conjunto imagem. Em programacao, a ideia e semelhante: uma funcao
e um conjunto de instrucoes que recebe alguns valores como dados de entrada e, a partir deles,
produz um valor como sada. A Figura 3.1 ilustra o funcionamento de uma funcao matematica.

Figura 3.1: Funcao simples.


72 CAPITULO 3. MODULARIZACAO

Em programacao, os dados de entrada sao chamados de parametros e o valor da sada e


chamado de retorno. Durante a execucao da funcao, os dados de entrada sao manipulados de
maneira a produzir o resultado esperado. Durante essa manipulacao de dados a funcao pode
chamar outras funcoes, que contem rotinas que auxiliam na elaboracao do resultado final. A
Figura 3.2 ilustra esse processo.

Figura 3.2: Funcao usando outras funcoes.

Na fabricacao de um automovel, algum processo realizado por um determinado robo pode


solicitar servicos de outros robos. Por exemplo, o robo que faz a colocacao das pecas no carro
pode solicitar por diversas vezes o servico do robo de soldagem de pecas. Apos a finalizacao da
soldagem da peca, o robo de montagem podera colocar outra peca no carro.

3.3 Partes de um Subprograma


As partes de um subprograma sao as mesmas de um programa, ou seja: cabecalho, dicionario
de dados, corpo e comentario.
A seguir serao comentadas cada uma dessas partes, enfocando sua importancia nos subpro-
gramas.

3.3.1 Cabecalho
O cabecalho do subprograma e onde estao definidos o nome do subprograma, os tipos dos seus
parametros de entrada e o tipo de dado de retorno.
Os parametros da funcao sao os ingredientes que ela precisa para executar as suas instrucoes
e seus tipos devem ser fielmente respeitados. Ja o tipo de retorno da funcao simboliza o tipo de
produto gerado por ela. Se a funcao diz retornar um numero float, quer dizer que o programador
deve esperar receber apenas esse tipo de dado e tomar as medidas necessarias para manipula-lo
posteriormente.
O nome do subprograma serve para identifica-lo. O programador deve escolher nomes auto-
explicativos sobre a funcionalidade da funcao, ou seja, para que ela serve. Isso torna o codigo
mais legvel e menos susceptvel a erros por parte do programador.
3.3. PARTES DE UM SUBPROGRAMA 73

No Exemplo 3.1, a funcao calculaMedia possui dois parametros de entrada do tipo float
e retorna um valor tambem do tipo float. O primeiro parametro recebe o nome a e o segundo
recebe o nome b. A funcao retorna um dado do tipo float, ou seja, retornara um numero que
contem casas decimais. Mais detalhes sobre passagem de parametros, assim como o retorno de
funcoes serao mostrados posteriormente.

1 float calculaMedia ( float a , float b ) ;

Exemplo 3.1: Cabecalho de um subprograma na linguagem C.

Percebe-se que o nome da funcao ja nos da a ideia do que que ela faz: calcula a media de dois
numeros. Porem, maiores explicacoes sobre essa media, se e aritmetica, geometrica ou outra,
devem ser explicadas nos comentarios da funcao.
Quando a quantidade de parametros de uma funcao nao e respeitada, a funcao nao tem todos
os dados necessarios para a realizacao de suas instrucoes. Na funcao do Exemplo 3.1, quando
o programador chama a funcao calculaMedia passando apenas um numero, ao inves de dois,
o compilador avisa o programador sobre tal erro, nao deixando que o programa seja gerado.
Assim, evita-se erros de execucao.
Tambem podem ocorrer erros quando a ordem ou tipo dos parametros nao sao respeitadas.
No cotidiano, tambem ocorrem esses tipos de erros. Por exemplo, para que um carro funcione,
ele precisa, entre outras coisas, de agua e oleo lubrificante. Caso a agua seja colocada no lugar do
oleo lubrificante, ou o contrario, e previsvel que o carro, em algum momento, apresente falhas
na execucao de suas operacoes, nao se locomovendo. Assim, fica facil perceber que um bom
programador deve sempre verificar se esta respeitando os tipos dos parametros das funcoes.

3.3.2 Dicionario de dados


O dicionario de dados e onde se faz a declaracao de variaveis e constantes usadas no subprograma
e nao declaradas no cabecalho da funcao.
Quando se declara variaveis em subprogramas, estas so podem ser utilizadas naquele sub-
programa. Qualquer outro subprograma, mesmo aquele que chamou a funcao, nao tem acesso
a essas variaveis. Assim, por existirem e serem acessveis apenas naquele subprograma, sao
chamadas de variaveis locais.
Considere novamente o exemplo do funcionamento da fabrica de automoveis. Enquanto
o subprograma Robo de solda estiver fazendo seu trabalho (soldagem de pecas), algumas
informacoes usadas em alguns procedimentos necessitam ser criadas, tais como: quantidade de
solda, qualidade da solda, metodo de soldagem, etc. Quando o Robo de solda termina seu
trabalho, essas informacoes nao sao mais necessarias. Note que os outros robos nao precisam
saber dessas informacoes para poderem executar seus trabalhos.
Para representar em um programa o funcionamento de uma fabrica de automoveis, cada
robo poderia ser representado por um subprograma. As informacoes seriam representadas por
variaveis locais. A Figura 3.3 ilustra essa correspondencia. O Robo de pintura tem duas
74 CAPITULO 3. MODULARIZACAO

variaveis: quantidade de tinta e cor da tinta. Essa variaveis nao sao visveis para os outros
robos; em particular, para o robo de solda.

Figura 3.3: Dicionario de dados.

3.3.3 Corpo
O corpo do subprograma e onde estao contidas as instrucoes a serem executadas pelo subpro-
grama. Nele, podem ser realizadas operacoes, tais como: atribuicoes de valores as variaveis,
chamadas de outras funcoes, calculos de expressoes, leitura de dados e apresentacao de resulta-
dos.
O Pseudocodigo 3.1 descreve uma funcao que calcula a distancia entre dois pontos no plano
cartesiano.

Pseudocodigo 3.1 Passos a serem relizados pela funcao


Processo componente " distancia " :
Usar o teorema de Pitagoras para calcular a dist^
a ncia dos pontos dados

Retornar o valor da dist^ a ncia


FIM - Processo componente " distancia "

O Exemplo 3.2 mostra a implementacao na linguagem C do Pseudocodigo 3.1. O corpo e


composto de atribuicao de valores as variaveis dx, dy e dist atraves de calculos matematicos e
pelo retorno do valor contido em dist. O retorno de dados e estudado na Secao 3.6.
Essa funcao recebe como parametro as coordenadas de dois ponto no plano cartesiano e
retorna a distancia entre esses pontos. Para fazer o calculo dessa distancia, primeiramente
calcula-se a distancia entre as coordenadas x (armazenando esse valor em dx) e a distancia
entre as coordenadas y (armazenando esse valor em dy). Posteriormente, usa-se o teorema de
Pitagoras para calcular a distancia entre os pontos dados.
3.3. PARTES DE UM SUBPROGRAMA 75

1 float distancia ( float x1 , float y1 , float x2 , float y2 ) {


2
3 float dx , dy , dist ;
4
5 dx = x2 - x1 ;
6
7 dy = y2 - y1 ;
8
9 dist = sqrt ( dx * dx + dy * dy ) ;
10
11 return dist ;
12 }

Exemplo 3.2: O corpo do subprograma sao as instrucoes contidas nele.

3.3.4 Comentarios
Para melhor legibilidade e entendimento do codigo do subprograma, e recomendavel fazer co-
mentarios.
Acima de cada subprograma pode-se colocar comentarios com as seguintes finalidades: dizer
para que essa funcao serve, quais os parametros que ela recebe, explicitando como devem ser
esses parametros (unidade de medida, relevancia para a funcao, etc), restricoes para aplicacoes,
entre outras.
No dicionario de dados, os comentarios podem ser usados para explicar o significado de al-
guma variavel cujo nome nao e suficientemente significativo. No corpo da funcao, os comentarios
podem ser usados para explicar o que foi feito em determinado conjunto de instrucoes cujo en-
tendimento nao e facil ou que nao tem utilidade aparente.
Na linguagem C, os comentarios dos subprogramas devem ser feitos da mesma maneira usada
para comentar o programa, entre /* e */ ou apos //, como explicado no Captulo 1.
O Exemplo 3.3 mostra um comentario geral da funcao distancia, explicando os parametros
da funcao e como e feito o calculo.

1 /*
2 Func~
a o para calcular a dist^
a ncia entre dois pontos no plano cartesiano .
3 Dados de entrada :
4 float x1 : a coordenada x do primeiro ponto .
5 float y1 : a coordenada y do primeiro ponto .
6 float x2 : a coordenada x do segundo ponto .
7 float y2 : a coordenada y do segundo ponto .
8
9 Dados de sada :
10 dist : retorna a dist^
a ncia entre os pontos passados
11 */
12
13 float distancia ( float x1 , float y1 , float x2 , float y2 ) {
14
76 CAPITULO 3. MODULARIZACAO

15 float dx , dy , dist ;
16
17 dx = x2 - x1 ;
18
19 dy = y2 - y1 ;
20
21 dist = sqrt ( dx * dx + dy * dy ) ;
22
23 return dist ;
24 }

Exemplo 3.3: Comentarios em um subprograma.

3.4 Chamada de subprogramas


Em programacao, quando um subprograma solicita servicos de um outro subprograma dizemos
que foi feita uma chamada ao subprograma. Durante a execucao de um programa, podem ser
feitas diversas chamadas a um mesmo subprograma.
Considere o exemplo do funcionamento da fabrica de automoveis. O Robo de Solda pode
ser chamado diversas vezes durante o processo de fabricacao de um carro e, e claro, em pon-
tos de montagem diferentes. Toda vez que ele e chamado, executa novamente o seu servico,
considerando as particularidades de cada caso.
Em programacao acontece o mesmo. Um mesmo subprograma pode ser chamado em diversos
pontos do codigo. A funcao distancia, descrita no Exemplo 3.2, pode ser usada para fazer
varios calculos de distancias entre pontos diferentes e em varios lugares diferentes de um mesmo
programa.
A chamada de um subprograma na linguagem C e feita digitando o nome da funcao e, em se-
guida, digitando os dados de entrada necessarios entre parenteses, para que assim o subprograma
execute suas instrucoes.
No Exemplo 3.4, a funcao distancia e chamada duas vezes. Na primeira vez, ela e cha-
mada para calcular a distancia entre duas cidades, sendo fornecidas as coordenadas x e y em
quilometros de distancia das cidades. A segunda chamada da funcao e feita para calcular a
distancia entre dois moveis de um escritorio.
Note que as coordenadas passadas para o subprograma em cada momento podem ser di-
ferentes, fazendo com que o programa calcule distancias diferentes. A interpretacao fsica das
coordenadas passadas tambem pode ser diferente, nao alterando a funcionalidade do subpro-
grama.

1 # include < stdio .h >


2 # include < math .h >
3
4 float distancia ( float x1 , float y1 , float x2 , float y2 ) {
5
6 float dx , dy , dist ;
3.5. PASSAGEM DE PARAMETROS 77

7
8 dx = x2 - x1 ;
9
10 dy = y2 - y1 ;
11
12 dist = sqrt ( dx * dx + dy * dy ) ;
13
14 return dist ;
15 }
16
17
18 main () {
19 float xa , xb , ya , yb , dist ;
20
21 printf ( " Forneca as coordenadas x e y ( em quil^
o metros ) das cidades A e B ,
respectivamente : " ) ;
22
23 scanf ( " % f % f % f % f " , & xa ,& ya ,& xb ,& yb ) ;
24
25 dist = distancia ( xa , ya , xb , yb ) ;
26
27 printf ( " Dist^
a ncia em quil^
o metros entre as cidades A e B : % f \ n " , dist ) ;
28
29 printf ( " Forneca as coordenadas x e y ( em metros ) da cadeira e da mesa do seu
escritorio : " ) ;
30 scanf ( " % f % f % f % f " , & xa ,& ya ,& xb ,& yb ) ;
31
32 dist = distancia ( xa , ya , xb , yb ) ;
33
34 printf ( " Dist^ a ncia em metros entre a cadeira e a mesa do seu escritorio : % f \ n
" , dist ) ;
35 }

Exemplo 3.4: Chamadas de um mesmo subprograma

3.5 Passagem de parametros


Os parametros de uma funcao sao os dados iniciais necessarios que ela possa realizar o seu
trabalho. Por exemplo, a funcao matematica: y(x) = x2 calcula o quadrado do numero x, que
foi passado como parametro para a funcao. Voltando ao exemplo da fabricacao de um carro,
pode-se dizer que a fabrica e o programa principal. Ao final do processo, ela deve fornecer um
carro pronto para o uso. Mas a fabrica, como ja foi dito, nao conta apenas com um robo para
fazer todo o processo e sim com varios robos, cada um com sua funcao especfica.
Cada robo deve receber uma entrada e fornecer uma sada. Para o Robo de Pintura,
as entradas sao: a carroceria do carro, feita por outro robo, e a cor usada para pinta-la. A
partir dessas entradas, o Robo de Pintura executa as instrucoes estabelecidas para pintar a
carroceria. Ao final da pintura, o robo esta pronto para fornecer a sada: a carroceria pintada.
78 CAPITULO 3. MODULARIZACAO

Essa sada pode servir como entrada para outro robo.


Cada parametro de uma funcao possui um tipo, declarado no cabecalho da funcao, como
mostrado no Exemplo 3.1, onde os parametros a e b sao do tipo float. Quando e feita uma cha-
mada ao subprograma, podem ser passados como parametro os valores de variaveis do programa
principal, que obrigatoriamente devem ser do mesmo tipo dos parametros da funcao chamada.
No Exemplo 3.4, sao feitas varias chamadas ao subprograma distancia. Em cada uma dessas
chamadas sao passadas entradas distintas, nesse caso, pontos diferentes no plano cartesiano.
Considere que na primeira chamada os pontos passados sao: xa = 10, ya = 30, xb = 20, yb =
60. Ja na segunda chamada foram passados os pontos: xa = 15, ya = 18, xb = 5, yb = 53. A
Figura 3.4 ilustra esse processo.

Figura 3.4: Passagem de parametro.

Quando o programa principal chama o subprograma distancia, os valores que estao contidos
nas variaveis xa, ya, xb e yb sao passados para as variaveis x1, y1, x2 e y2 do subprograma.
E importante perceber que, quando e feita uma passagem de parametros, apenas os valores
das variaveis sao passados para o subprograma chamado e nao as variaveis em si. Esse metodo
de passagem de parametro e chamado de passagem por copia, ou entao, passagem por valor.
A primeira implicacao dessa regra e a a nao necessidade de se usar variaveis como entrada
para outra funcao, podendo-se usar diretamente um valor. O Exemplo 3.5 ilustra essa ideia. No
programa principal e feita a chamada ao subprograma distancia passando-se os valores 10 ,
15 , 26 e 23 como parametros. Na funcao distancia, as variaveis x1, y1, x2 e y2 recebem
esses valores, para so entao a funcao iniciar suas instrucoes.
3.5. PASSAGEM DE PARAMETROS 79

1 # include < stdio .h >


2
3 float distancia ( float x1 , float y1 , float x2 , float y2 ) {
4
5 float dx , dy , dist ;
6
7 dx = x2 - x1 ;
8
9 dy = y2 - y1 ;
10
11 dist = sqrt ( dx * dx + dy * dy ) ;
12
13 return dist ;
14 }
15
16 main () {
17 int dist ;
18
19 dist = distancia (10 , -15 , 26 , -23) ;
20
21
22 }

Exemplo 3.5: Passagem de parametros.

Outra implicacao do fato de que apenas valores sao passados como parametro para uma
funcao e a impossibilidade de mudar o conteudo das variaveis que sao usadas como entrada
para a funcao. No Exemplo 3.6, a variavel a, da funcao dobraValor , recebe apenas o valor
da variavel x da funcao principal. Com isso, a variavel x permanece com o seu valor anterior a
chamada da funcao dobraValor , apesar da variavel a dessa funcao ter o seu valor alterado.

1 # include < stdio .h >


2
3 int dobraValor ( int a ) {
4 printf ( " Numero original : % d \ n " , a ) ;
5
6 a = 2* a ;
7
8 printf ( " O seu dobro e : % d \ n " , a ) ;
9
10 return a ;
11 }
12
13 main () {
14 int x = 10;
15
16 dobraValor ( x ) ;
17
18 printf ( " O valor de x e : % d \ n " , x ) ;
80 CAPITULO 3. MODULARIZACAO

19 }

Exemplo 3.6: Passagem de valor na chamada de subprogramas.

A sada da execucao do programa do Exemplo 3.6 e mostrada no Exemplo 3.7. Note que,
como esperado, a variavel x permaneceu com o seu valor original.

1 Numero original : 10
2 O seu dobro e : 20
3 O valor de x e : 10

Exemplo 3.7: Sada da execucao do programa do Exemplo 3.6.

Uma outra implicacao do metodo de passagem de valores e a nao visibilidade das variaveis
do programa principal no subprograma chamado, ou seja, o subprograma nao pode utilizar as
variaveis do programa principal ou de algum outro subprograma. No Exemplo 3.8, as variaveis
x e y nao podem ser acessadas dentro da funcao alteraValor . Com isso, o compilador emite
um erro quando o programa e compilado, dizendo que as variaveis x e y nao foram declaradas
anteriormente e a compilacao do programa e interrompida.

1 void alteraValor ( void ) {


2 x = 20;
3 y = x *5;
4 }
5
6 main () {
7 int x = 10 , y = 15;
8
9 alteraValor () ;
10 }

Exemplo 3.8: Visibilidade das variaveis.

O que ocorre no Exemplo 3.8 tambem pode ser comparado com os robos da fabrica de
automoveis. O Robo de Solda nao pode usar a variavel local cor do automovel do Robo de
Pintura. O Robo de Solda nao sabe o que e essa variavel, nem que ela existe, assim, nao
pode usa-la.

3.6 Retorno de dados


Esta secao trata da forma como os dados sao retornados em cada subprograma, isto e, como se
produz uma sada da funcao.
Um subprograma produz, no decorrer das suas instrucoes, um valor final (uma sada), que
devera ser passado para o programa ou mesmo subprograma que o chamou. O valor retornado
pelo subprograma sera atribudo a alguma variavel do programa que chamou essa funcao, ou
entao usado em alguma expressao.
3.6. RETORNO DE DADOS 81

Na linguagem C, deve-se declarar o tipo de dado retornado pela funcao em seu cabecalho
e usa-se o comando return para especificar a variavel ou valor que deve ser retornado. Apos a
sua execucao, a execucao da funcao corrente termina, mesmo que existam mais instrucoes apos
o return. Na implementacao da funcao distancia, feita no Exemplo 3.2, o valor contido na
variavel dist e retornado.
Como a main tambem e uma funcao, tambem temos que especificar o tipo de dado retornado
por ela. Por padrao, na linguagem C, a funcao main retorna um valor tipo int. Quando o
programador nao coloca nenhum valor para ser retornado pela main (como no Exemplo 3.9),
alguns compiladores fazem com que o valor 0 seja retornado por padrao. O valor 0 tambem e
usado para simbolizar que nada aconteceu de errado na execucao do programa.
Neste livro, optou-se por nao explicitar o valor retornado pela funcao main, ja que este valor
nao sera usado no programa. Contudo, vale lembrar que alguns compiladores exigem que o
retorno da main esteja escrito no codigo fonte, ficando a cargo do programador a percepcao da
obrigatoriedade de escreve-lo.
Considere um programa que calcula a media de um aluno e diz se ele foi aprovado ou nao.
O Pseudocodigo 3.2 mostra os passos a serem realizados pelo programa.

Pseudocodigo 3.2 Programa para calcular a media de um aluno.


Func~a o Principal :
Usar o processo componente " calculaMedia " passando - se as 2 notas de provas
do aluno como par^ a metros .
Verificar se a media retornada e maior ou igual a sete .
Caso afirmativo :
O aluno esta aprovado .
Caso negativo :
Usar o processo componente " calculaMedia " passando - se a media
anterior e a nota da prova final do aluno como par^ a metros .
Verificar se a nova media retornada e maior ou igual a cinco .
Caso Afirmativo ;
O aluno esta aprovado .
Caso negativo :
O aluno esta reprovado .
FIM - Func~a o Principal

Processo componente " calculaMedia " :


Somar os dois numeros passados como par^
a metros e dividir o resultado por
dois .
Retornar o resultado acima .
FIM - Processo componente " calculaMedia "

Uma possvel implementacao do Pseudocodigo 3.2 esta transcrita no Exemplo 3.9. Primei-
ramente, o programa pede que o usuario digite os valores das notas das duas provas realizadas
pelo aluno. Essas notas sao armazenadas nas variaveis nota1 e nota2 respectivamente e, logo
em seguida, sao passadas como parametros para a funcao calculaMedia. A funcao calcula-
Media recebe dois valores do tipo float e faz a media aritmetica deles, retornando, ao final da
82 CAPITULO 3. MODULARIZACAO

sua execucao, o valor dessa media. O valor retornado e armazenado na variavel resultado da
funcao principal, que por sua vez serve de condicao para a aprovacao do aluno. Caso o valor
armazenado em resultado seja maior ou igual a sete, o aluno esta aprovado; caso contrario, o
programa pede a nota da prova final do aluno, que e armazenada na variavel notaFinal. As
variaveis notaFinal e resultado sao entao usadas como parametros da funcao calculaMedia.
Caso o resultado retornado seja maior ou igual a cinco, o aluno esta aprovado.

1 # include < stdio .h >


2
3 float calculaMedia ( float a , float b ) {
4 float media ;
5
6 media = ( a + b ) /2;
7
8 return media ;
9 }
10
11 main () {
12 float nota1 , nota2 , notaFinal , resultado ;
13
14 printf ( " Forneca as notas das duas provas do aluno : " ) ;
15 scanf ( " % f % f " , & nota1 , & nota2 ) ;
16
17 resultado = calculaMedia ( nota1 , nota2 ) ;
18
19 printf ( " A media do aluno foi : % f \ n " , resultado ) ;
20
21 if ( resultado >= 7) {
22 printf ( " O aluno passou de semestre .\ n " ) ;
23 }
24 else {
25 printf ( " O aluno teve que fazer prova final . Forneca a nota da prova final
do aluno : " ) ;
26 scanf ( " % f " , & notaFinal ) ;
27
28 if ( calculaMedia ( resultado , notaFinal ) >= 5) {
29 printf ( " O aluno passou de semestre .\ n " ) ;
30 }
31 else {
32 printf ( " O aluno n~
a o obteve o rendimento mnimo para passar de semestre
. Esta reprovado .\ n " ) ;
33 }
34 }
35 }

Exemplo 3.9: Retorno de dados.

Note que, na primeira chamada da funcao calculaMedia, o valor retornado foi armazenado
em uma variavel, pois precisa ser usado posteriormente. Ja na segunda chamada da funcao, o
3.6. RETORNO DE DADOS 83

valor retornado e usado diretamente na condicional de aprovacao ou nao do aluno, nao sendo
necessario seu armazenamento em alguma variavel. Mas atencao: caso fosse necessario usar esse
valor posteriormente, deveramos guarda-lo em alguma variavel, ao inves de chamar outra vez
a funcao calculaMedia usando os mesmos parametros. Essa chamada sendo feita novamente
acarretaria em perda de desempenho do programa, ja que a funcao teria que recalcular valores,
o que levaria um certo tempo para ser feito.
Em muitos casos e interessante, ou ate necessario, que o subprograma altere as variaveis
criadas no programa que o chamou. No exemplo da fabrica de automoveis e isso que deve
acontecer. A Figura 3.5 ilustra esse processo.

Figura 3.5: Passagem de parametro por referencia.

Nesse exemplo, o carro que e passado para o robo de solda esta com varias pecas soltas.
Entao, o robo solda essas pecas e passa o carro para o robo de pintura. Nesse caso, percebe-
se que deve-se passar o mesmo carro para o proximo robo e nao apenas um copia das suas
caractersticas, que simbolizam os valores das variaveis em programas. Entao, o carro recebe a
pintura e e passado como parametro para uma proxima funcao, ou seja, para um outro robo.
Como discutido na Secao 3.5, nao e possvel para um subprograma que recebe o valor de
uma variavel de um outro subprograma, alterar diretamente o valor da variavel do subprograma
que o chamou. Nesse caso, pode-se retornar o valor calculado e entao armazena-lo na variavel
desejada. A expressao geral e x = f (x).
Por exemplo, pode-se fazer uma variacao do uso da funcao dobraValor usada no Exemplo
3.6 para alterar o valor da variavel do programa que a chamou. Essa alteracao esta descrita no
Exemplo 3.10. Nesse exemplo, o valor retornado pela funcao dobraValor agora e armazenado
na variavel x.

1 # include < stdio .h >


2
3 int dobraValor ( int a ) {
4 printf ( " Numero original : % d \ n " , a ) ;
84 CAPITULO 3. MODULARIZACAO

5
6 a = 2* a ;
7
8 printf ( " O seu dobro e : % d \ n " , a ) ;
9
10 return a ;
11 }
12
13 main () {
14 int x = 10;
15
16 x = dobraValor ( x ) ;
17
18 printf ( " O valor de x e : % d \ n " , x ) ;
19 }

Exemplo 3.10: Atualizacao da variavel de outra funcao.

A sada da execucao do programa do Exemplo 3.10 e mostrada no Exemplo 3.11. Note que
a variavel x passou a ter o valor 20.

1 Numero original : 10
2 O seu dobro e : 20
3 O valor de x e : 20

Exemplo 3.11: Sada da execucao do programa do Exemplo 3.6.

3.6.1 Encerramento antecipado de execucao


Observando a definicao de calculaMedia, implementada no Exemplo 3.9, nota-se que o valor
so e retornado ao final da funcao, quando todas as outras instrucoes foram executadas. No
entanto, e possvel ocorrer de um subprograma ter varios pontos de retorno distintos.
No Pseudocodigo 3.3 sao mostrados os passos realizados pela funcao ehDivisor , que verifica
se um numero e divisor do outro. Para evitar um erro matematico, tem-se que verificar se o
divisor e diferente de zero. Se for igual a zero, entao a execucao da funcao deve ser interrompida
e o erro devera ser indicado de alguma forma. Nesse caso, escolheu-se retornar o valor 0.
No Exemplo 3.12 e apresentada a implementacao, na linguagem C, do Pseudocodigo 3.3.
Note que a funcao apresenta tres pontos distintos de retorno. No primeiro, e verificado se o
divisor e zero e caso isso seja verdade e retornado o valor 0, caso contrario, a funcao continua
executando suas instrucoes. No segundo, e verificado se o resto da divisao inteira de x por y e
zero, ou seja, e verificado se y e divisor de x. Caso isso seja verdade e retornado o valor 1, caso
contrario, e retornado o valor 1, simbolizando que y nao e divisor de x. Aqui, o valor 1 foi
usado para simbolizar sucesso na verificacao.
Note ainda que esse recurso torna a funcao mais eficiente e legvel.
3.6. RETORNO DE DADOS 85

Pseudocodigo 3.3 Funcao que determina se um numero e divisor do outro


Processo componente " ehDivisor " :
Verificar se o divisor e igual a zero
Caso afirmativo :
Retornar o valor zero

Verificar se o resto da divis~ a o do dividendo pelo divisor e igual a zero


Caso afirmativo :
Retornar o valor 1
Caso contrario :
Retornar o valor -1
FIM - Processo componente " ehDivisor "

1 int ehDivisor ( int x , int y ) {


2 if ( y == 0) {
3 return 0;
4 }
5
6 if ( x % y == 0) {
7 return 1;
8 } else {
9 return -1;
10 }
11 }

Exemplo 3.12: Encerramento antecipado para evitar erros

Ainda no Exemplo 3.12, o encerramento antecipado de execucao foi feito para evitar um
erro na execucao do programa. Porem, o encerramento antecipado de execucao pode ser feito
naturalmente, sem o objetivo de evitar erros de execucao.
Considere o caso do Exemplo 3.9. O programa principal poderia ser na verdade um sub-
programa que retorna 1 caso o aluno esteja aprovado e 1 caso contrario. Se o aluno obtiver
media 7 apenas com as duas primeiras notas, a execucao do programa e interrompida e o valor
1 e retornado. Essa alteracao esta descrita no Exemplo 3.13. Note que nao e necessario o uso
do else na linha 20, pois caso a o resultado da media seja maior ou igual a sete, a funcao ira
interromper sua execucao, nao acarretando o calculo invalido da media considerando a nota da
prova final do aluno, que nao foi feita.

1 # include < stdio .h >


2
3 float calculaMedia ( float a , float b ) {
4 float media ;
5
6 media = ( a + b ) /2;
7
8 return media ;
86 CAPITULO 3. MODULARIZACAO

9 }
10
11 int aprovado ( float nota1 , float nota2 , float notaPF ) {
12 float resultado ;
13
14 resultado = calculaMedia ( nota1 , nota2 ) ;
15
16 if ( resultado >= 7) {
17 return 1;
18 }
19
20 if ( calculaMedia ( resultado , notaPF ) >= 5) {
21 return 1;
22 }
23 else {
24 return 0;
25 }
26 }
27
28 main () {
29 float nota1 , nota2 , notaPF ;
30 int passou ;
31
32 printf ( " Forneca as notas das duas provas do aluno e a nota da prova final .
Caso o aluno n~ a o tenha feito prova final , digite zero : " ) ;
33 scanf ( " % f % f % f " , & nota1 , & nota2 , & notaPF ) ;
34
35 passou = aprovado ( nota1 , nota2 , notaPF ) ;
36
37 if ( passou == 1) {
38 printf ( " O aluno passou de semestre .\ n " ) ;
39 }
40 else {
41 printf ( " O aluno n~a o obteve o rendimento mnimo para passar de semestre .
Esta reprovado .\ n " ) ;
42 }
43 }

Exemplo 3.13: Encerramento antecipado natural.

3.7 Funcoes sem lista de parametros


Ate agora as funcoes mostradas continham dados de entrada, ou seja, uma lista de parametros.
Mas ha casos em que isso nao e necessario, pois a funcao executa suas instrucoes sem precisar
de dados de entrada. Alguns leitores podem achar que isso faz com que o subprograma retorne
sempre o mesmo dado, afinal, a ausencia de dados de entrada tornaria a funcao uma funcao
constante.
Esse pensamento estaria correto se o subprograma nao pudesse coletar dados externos,
3.8. FUNCOES SEM RETORNO DE DADOS 87

usando por exemplo a funcao scanf.


No Exemplo 3.14 a funcao lerNumeros nao tem nenhum parametro de entrada. Isso foi feito
pois ela sempre executara a mesma rotina: lera 5 numeros digitados pelo usuario e fara a soma
deles. E facil perceber que o resultado retornado pode variar a cada chamada da funcao. Isso
acontece pois essa funcao usa os dados retornados por scanf para completar suas instrucoes.
Note que em C usa-se void na lista de parametros para simbolizar que a funcao nao possui
parametros de entrada.

1 int lerNumeros ( void ) {


2 int x =0 , temp , i ;
3
4 for ( i =0 ; i <5 ; i ++) {
5 printf ( " digite um numero : " ) ;
6 scanf ( " % d " , & temp ) ;
7 x += temp ;
8 }
9
10 return x ;
11 }

Exemplo 3.14: Lista de parametros vazia

3.8 Funcoes sem retorno de dados


Assim como ha funcoes que nao tem nenhum parametro, tambem ha funcoes que nao retornam
nenhum tipo de dado. Na linguagem C, para simbolizar essa situacao usa-se void como tipo de
dado retornado.
O Exemplo 3.15 exibe uma funcao que nao retorna dados para a funcao que a chamou. A
funcao multiplica recebe tres numeros e exibe o resultado da multiplicacao destes. Nota-se que
nenhum dado e retornado, nem mesmo o valor da multiplicacao. O programador pode decidir
por este tratamento caso o resultado da multiplicacao nao seja usado posteriormente.

1 void multiplica ( float a , float b , float c ) {


2
3 printf ( " Resultado = % f " , a * b * c ) ;
4
5 }

Exemplo 3.15: Funcao sem retorno.

Funcoes sem retorno de dados podem guardar os valores gerados em algum arquivo. Caso isso
seja feito, os dados podem ser recuperados lendo-se esses arquivos. A manipulacao de arquivos,
tanto para leitura, tanto para escrita, e discutida na captulo 8.
Ha tambem a opcao da funcao nao ter nenhum parametro de entrada e nenhum dado retor-
88 CAPITULO 3. MODULARIZACAO

nado. O Exemplo 3.16 mostra uma funcao para exibir uma saudacao ao usuario. Note que a
funcao nao possui parametros de entradas nem valor de retorno.

1 void saudacao ( void ) {


2 printf ( " Bem - vindo ao programa Calculo Eletr^ o nico , onde seus calculos
s~
a o resolvidos rapidamente \ nPara mais informac~ o es e atualizac~
o es
acesse o site : www . programaemc . com . br " ) ;
3
4 }

Exemplo 3.16: Funcao sem parametro e sem retorno.

3.9 Recursividade
A recursividade ocorre quando algo e definido a partir de si mesmo. Uma recursao sempre e
definida a partir de uma ou mais relacoes recursivas (quando o proximo elemento e definido a
partir do anterior), e uma ou mais bases de recursao (pontos de parada).
Em programacao, o metodo recursivo pode ser usado para definir uma funcao. Por exemplo,
pode-se definir uma lista como sendo a uniao de um elemento com uma lista. Essa lista poderia
ser mais um elemento com mais uma lista ou apenas uma lista vazia. Pode-se simplificar essa
definicao por:

lista = lista vazia base da recursao


lista = elemento + lista relacao recursiva

Por exemplo, o calculo do fatorial de um numero inteiro nao negativo pode ser implementado
atraves de uma funcao recursiva. Pode-se definir o fatorial de um numero n como sendo o proprio
numero n multiplicado pelo fatorial do numero n-1. A definicao de fatorial pode ser representada
por:

fatorial (0) = 1 base da recursao


fatorial (n) = n * fatorial (n-1) relacao recursiva

Nas definicoes recursivas anteriores sao usados criterios de parada a fim de que o processo
recursivo tivesse um limite. Imagine se uma lista fosse definida somente como um elemento
concatenado com uma lista. Por essa definicao nunca se teria uma lista finita, pois sempre novos
elementos teriam que ser adicionados. Para evitar isso usa-se um criterio de parada, chamado
de base da recursao. No exemplo da definicao da lista, a base da recursao e uma lista vazia. No
exemplo do fatorial de um numero, a base da recursao pode ser o numero 0, ou seja, e como se
dissesse: quando chegar ao numero 0 pare.
O Pseudocodigo 3.4 mostra os passos a serem seguidos para o calculo do fatorial de um
numero de forma recursiva.
3.9. RECURSIVIDADE 89

Pseudocodigo 3.4 Fatorial de um numero de forma recursiva.


Processo componente " fatorial " :
Verificar se o numero passado como par^a metro e igual a zero
Caso Afirmativo :
Retornar o valor 1.
Caso Negativo :
Retornar o numero passado como par^a metro multiplicado pelo valor do
fatorial do seu antecessor .
FIM - Processo componente " fatorial "

A implementacao recursiva do fatorial na linguagem C e mostrada no Exemplo 3.17. O


programa chama ele mesmo quantas vezes forem necessarias ate que o parametro n seja 0.
Quando isso acontece, a funcao retorna 1 e a execucao prossegue a partir da ultima chamada da
funcao, multiplicando o valor retornado (1) pelo valor 1 da chamada anterior da funcao e assim
sucessivamente, ate se obter o fatorial do numero n inicial.

1 int fatorial ( int n ) {


2 if ( n == 0) {
3 return 1;
4 } else {
5 return n * fatorial (n -1) ;
6 }
7 }

Exemplo 3.17: Implementacao recursiva do fatorial.

E importante o compreender que a cada chamada do programa fatorial e reservado um


novo espaco de memoria, ou seja, os dados do programa fatorial anterior sao preservados e
caso a funcao recursiva tivesse variaveis locais, novas instancias dessas variaveis seriam criadas
a cada chamada, sem qualquer relacao com as variaveis da outra chamada do subprograma. Por
isso, funcoes recursivas tendem a necessitar de mais memoria do computador.

Chamando a funcao fatorial passando-se como argumento o numero 5, tem-se o esquema


ilustrado na Figura 3.6. As setas direcionadas para baixo significam a chamada do programa
apontado, exibindo o valor passado para como parametro. As setas direcionadas para cima
significam o retorno do dado do programa chamado, mostrando o valor retornado pela funcao.
90 CAPITULO 3. MODULARIZACAO

Figura 3.6: Fatorial utilizando recursao.

3.9.1 Implementacao nao recursiva equivalente

Em geral, toda funcao recursiva pode ser implementada sem recursao, atraves do uso de coman-
dos de repeticao. A vantagem da versao nao recursiva e a eficiencia. Ja a vantagem da recursiva
e a elegancia, legibilidade e redigibilidade.
O Exemplo 3.18 mostra uma forma nao recursiva da implementacao da funcao fatorial. O
comando for e utilizado para multiplicar o f por i a cada valor de i, ate que i assuma o valor de
n, passado como parametro.

1 int fatorial2 ( int n ) {


2 int i , f ;
3
4 f = 1;
5
6 for ( i =2 , i <= n ; i ++) {
7 f= f*i;
8 }
9 return f ;
10 }

Exemplo 3.18: Implementacao nao recursiva do fatorial.


3.10. EXERCICIOS RESOLVIDOS 91

3.10 Exerccios Resolvidos

Exerccio Resolvido 3.1 - Soma de inteiros

Faca uma funcao que receba um numero inteiro positivo n, calcule a soma dos n primeiros
numeros naturais e retorne esse valor. Considere os numeros naturais comecando do 1. Nao use
a formula de P.A. (Progessao aritmetica).

Solucao Possvel:
Para produzir a soma dos n primeiros inteiros deve-se gerar a serie dos n primeiros valores
inteiros e acumula-los em uma variavel soma.
Na Figura 3.7 e mostrado um exemplo de solucao para n = 4. Note que a medida que os
valores de i sao gerados eles sao acumulados em soma. Fica claro que e necessaria a realizacao
de uma repeticao para produzir os valores de i e atualizar soma.

i soma
1 1
2 3
3 6
4 10

Figura 3.7: Solucao para n = 4.

O Pseudocodigo 3.5 mostra o algoritmo que descreve os passos a serem realizados pela funcao.

Pseudocodigo 3.5 Passos a serem realizados pela funcao soma.


Processo componente " soma " :
Inicializar soma com o valor zero
Para um loop de 1 a n :
Acumular em soma o valor corrente de i
Retornar o valor soma
FIM - Processo componente " soma "

O Exemplo 3.19 mostra a implementacao desse algoritmo. Note que o pseudocodigo foi
seguido fielmente, acrescentado-se apenas instrucoes necessarias e especficas da linguagem C.
92 CAPITULO 3. MODULARIZACAO

1 int soma ( int n ) {


2 int i , soma ;
3
4 soma = 0;
5
6 for ( i = 1; i <= n ; i = i + 1) {
7 soma = soma + i ;
8 }
9
10 return soma ;
11 }

Exemplo 3.19: Exerccio Resolvido - Soma de inteiros.

Exerccio Resolvido 3.2 - Multiplos

Faca um programa que leia um numero natural n e dois numeros naturais i e j diferentes de
0 e imprima em ordem crescente os n primeiros naturais que sao multiplos de i ou de j. Resolva
o problema utilizando uma funcao que verifique se um numero e multiplo do outro. Exemplo:
Para n = 6 , i = 2 e j = 3 a sada devera ser : 0,2,3,4,6,8.

Solucao Possvel:

Para exibir os n primeiros naturais multiplos de i e j deve-se testar os numeros naturais um


a um, comecando de zero e verificando se sao multiplos de i ou de j. Caso o numero natural
corrente, contido na variavel natural, seja multiplo da variavel i ou j, uma variavel contadora
cont devera ser incrementada e a verificacao continuara com o proximo numero natural. A
verificacao devera acabar quando n numeros naturais multiplos de i ou de j forem encontrados.
Essa condicao sera alcancada quando a variavel cont for igual a variavel n, que contem o numero
de multiplos a serem impressos.

A Figura 3.8 mostra os passos realizados pelo programa para n = 6, i = 2 e j = 3.

Para saber se um numero x e multiplo de um numero y basta verificar se o resto da divisao


inteira de x por y e zero.

O Pseudocodigo 3.6 mostra um algoritmo que descreve os passos a serem realizados pela
funcao.
3.10. EXERCICIOS RESOLVIDOS 93

No Natural Multiplo de j ? Multiplo de i ? No de multiplos de i ou j


0 sim sim 1
1 nao nao 1
2 nao sim 2
3 sim nao 3
4 nao sim 4
5 nao nao 4
6 sim sim 5
7 nao nao 5
8 nao sim 6

Figura 3.8: Solucao para n = 6, i = 2 e j = 3.

Pseudocodigo 3.6 Passos a serem relizados pela funcao.


Func~a o Principal :
Ler os valores de n , i e j
Chamar o processo componente multiplos passando - se como par^
a metros n , i e j
.
FIM - Func~a o Principal

Processo componente " multiplos " :


Inicializar a variavel do numero atual e a variavel indicando o npumero de
multiplos com o valor zero .
Enquanto o numero de multiplos for menor que n :
Verificar se o numero atual e multiplo de i ou de j atraves do processo
componente ehMultiplo
Caso afirmativo :
Incrementar a variavel contadora de multiplos e exibir o valor do
numero atual .
Incrementar a variavel do numero atual
FIM - Processo componente " multiplos "

Processo componente " ehMultiplo " :


Verificar se o resto da divis~ a o de x por y e zero .
Caso afirmativo :
Retornar o valor 1 , indicando sucesso .
Caso negativo :
Retornar o valor 0.
FIM - Processo componente " ehMultiplo "

O Exemplo 3.20 mostra a implementacao do algoritmo do Pseudocodigo 3.6. Na funcao


ehMultiplo usou-se como convencao o valor 1 para indicar sucesso na verificacao de x ser
multiplo de y e 0 caso contrario. Note tambem que no comando de repeticao for da funcao
multiplos foi omitido o terceiro parametro. Isso foi necessario pois a variavel cont so devera
94 CAPITULO 3. MODULARIZACAO

ser incrementada se natural for multiplo de i ou j, o que nem sempre e verdade.

1 # include < stdio .h >


2
3 int ehMultiplo ( int x , int y ) {
4
5 if ( x % y == 0) {
6 return 1;
7 }
8 else {
9 return 0;
10 }
11 }
12
13 void multiplos ( int i , int j , int n ) {
14 int natural , cont ;
15
16 natural = 0;
17
18 printf ( " Os % d primeiros multiplos de % d ou de % d s~
ao : " , n , i , j ) ;
19
20 cont = 0;
21 while ( cont < n ) {
22
23 if ( ehMultiplo ( natural , i ) == 1 || ehMultiplo ( natural , j ) == 1) {
24 printf ( " % d " , natural ) ;
25 cont ++;
26 }
27
28 natural ++;
29 }
30 printf ( " \ n " ) ;
31
32 }
33
34 int main () {
35 int n , i , j ;
36
37 printf ( " Digite 3 numeros naturais , o primeiro sendo a quantidade n de
multiplos que ser~ a o impressos e os dois ultimos sendo os numeros i e j
que servir~ a o como base para a gerac~
a o dos multiplos : " ) ;
38
39 scanf ( " % d % d % d " , &n , &i , & j ) ;
40
41 multiplos (i , j , n ) ;
42
43 return 0;
44 }

Exemplo 3.20: Exerccio Resolvido - Multiplos.


3.10. EXERCICIOS RESOLVIDOS 95

Exerccio Resolvido 3.3 - Triangulo Retangulo

Faca uma funcao que dados tres numeros naturais, verifique se eles formam os lados de um
triangulo retangulo. Os numeros dados simbolizam o comprimento de cada lado do triangulo.

Solucao Possvel:
Na geometria, o Triangulo Retangulo e um triangulo que possui um angulo reto e outros
dois angulos agudos. Sabe-se ainda que o quadrado da hipotenusa (o maior lado do retangulo)
e igual a soma dos quadrados dos catetos.
Sendo assim, para resolver esse exerccio, precisamos primeiro saber qual dos tres lados
dados, a, b ou c, corresponde a hipotenusa. Para tanto, compara-se todos os lados, dois a dois,
colocando-se o maior lado na variavel a.
Como o maior lado estara guardado em a e certo dizer que os lados dados correspondem a
um triangulo retangulo se, e somente se, a2 = b2 + c2 .
O Pseudocodigo 3.7 mostra um algoritmo que descreve os passos a serem realizados pela
funcao.

Pseudocodigo 3.7 Passos a serem relizados pela funcao.


Processo componente " trianguloRetangulo " :
Comparar se o lado b e maior do que o lado a
Caso afirmativo , trocar os valores de a e b.
Comparar se o lado c e maior do que o lado a
Caso afirmativo , trocar os valores de a e c.
Verificar se a * a = b * b + c * c
Caso afirmativo :
Retornar o valor 1
Caso negativo :
Retornar o valor 0
FIM - Processo componente " trianguloRetangulo "

O Exemplo 3.21 mostra a implementacao desse algoritmo. Note que para trocar os valores
das variaveis a e b teve-se que ter uma variavel auxiliar, chamada aux, para guardar o valor de
a para so depois atribuir o valor da variavel b para a variavel a e posteriormente atribuir o valor
antigo da variavel a (que esta guardado na variavel aux) para a variavel b.

1 int tr ia nguloRetangulo ( int a , int b , int c ) {


2 int aux ;
3
4 if ( b > a ) {
5 aux = a ;
6 a = b;
7 b = aux ;
8 }
9
10 if ( c > a ) {
96 CAPITULO 3. MODULARIZACAO

11 aux = a ;
12 a = c;
13 c = aux ;
14 }
15
16 if ( a * a == b * b + c * c ) {
17 return 1;
18 }
19 else {
20 return 0;
21 }
22 }

Exemplo 3.21: Exerccio Resolvido - Triangulo Retangulo.

Exerccio Resolvido 3.4 - Tabuada

Faca um programa que dado dois numeros inteiros, m e n, gere uma tabuada conforme a
Figura 3.9, sendo que para esse exemplo os numeros fornecidos foram: 22 e 37.

0x1=0 1x1=1 ... 37 x 1 = 37


0x2=0 1x2=2 ... 37 x 2 = 74
. . ... .
. . ... .
0 x 22 = 0 1 x 22 = 22 ... 37 x 22 = 814

Figura 3.9: Solucao para m = 22 e n = 37.

Solucao Possvel:
Para gerar a tabuada proposta, e preciso perceber onde sao usados os numeros dados. Note
que a tabuada representa o resultado da multiplicacao de um numero x por um numero y. O
numero x assume valores desde 0 ate o segundo numero dado como argumento, enquanto o
numero y assume valores desde 1 ate o primeiro numero dado como argumento.
E facil perceber que e necessario o uso de comandos de repeticao para solucionar o problema.
Pode-se usar 2 comandos de repeticao, um contido no outro, diretamente ou indiretamente, via
uma funcao externa.
O Pseudocodigo 3.8 mostra um algoritmo que descreve os passos a serem realizados pela
funcao.
3.10. EXERCICIOS RESOLVIDOS 97

Pseudocodigo 3.8 Passos a serem relizados pela funcao.


Processo componente " mult " :
Para um loop variando de 1 a n :
Guardar o resultado de cont vezes x em r e exibir o resultado
FIM - Processo componente " mult "

Func~a o Principal :
Coletar os valores das duas variaveis ( n e m )
Para um loop com x variando de 0 a m :
Chamar processo componente " mult " , passando como par^
a metro n e x
FIM - Func~
a o Principal

O Exemplo 3.22 mostra a implementacao desse algoritmo. Note que a variavel a da funcao
mult contem o valor da variavel n da funcao main, enquanto a variavel b da funcao mult
contem o valor da variavel x da funcao main.

1 # include < stdio .h >


2
3 void mult ( int a , int b ) {
4 int r , cont ;
5
6 for ( cont =1; cont <= a ; cont ++) {
7 r = b * cont ;
8
9 printf ( " % dx % d =% d \ n " , b , cont , r ) ;
10 }
11
12 printf ( " \ n " ) ;
13 }
14
15 int main ( void ) {
16 int m , n , x ;
17
18 printf ( " Entre com dois numeros inteiros : " ) ;
19 scanf ( " % d % d " , &n ,& m ) ;
20
21 for ( x =0; x <= m ; x ++) {
22 mult (n , x ) ;
23 }
24
25 return 0;
26 }

Exemplo 3.22: Exerccio Resolvido - Tabuada.

Exerccio Resolvido 3.5 - N primeiros numeros primos

Seja n inteiro positivo diferente de zero. Faca um programa para exibir os n primeiros
98 CAPITULO 3. MODULARIZACAO

numeros primos.

Solucao Possvel:

Para exibir os n primeiros numeros primos e necessario fazer primeiro uma funcao que verifi-
que se um dado numero x e primo ou nao. Por definicao: Numero primo e um numero natural
que pode ser dividido (o resto da sua divisao e zero) apenas por dois numeros naturais, o 1 (um)
e ele mesmo, com excessao do numero 1, que nao e tido como primo.

Sabe-se tambem que o unico numero que e primo e par ao mesmo tempo e o numero 2. Logo,
qualquer numero que seja par (resto da divisao por 2 igual a zero) que nao seja o dois, nao e
primo.

Entao, o que se tem a fazer para saber se determinado numero e primo e verificar se ele e o
numero dois ou se ele tem apenas 2 divisores. Mas, caso o numero em questao seja o numero
1, multiplo de 2 (com excessao do zero e do dois), ou tiver mais de 2 divisores, entao ele nao e
primo.

Todos os numeros inteiros positivos diferentes de zero tem ao menos 2 divisores: o 1 e ele
mesmo, com excessao do numero 1. Logo, se for encontrado mais algum divisor, o numero
estudado nao e primo. Entao, apos feitas as verificacoes anteriores, para encontrar algum outro
divisor do numero, basta ir dividindo-o por todos os numeros mpares maiores ou iguais a 3 e
menores ou iguais a raiz quadrada do numero em questao. Caso nao encontremos divisores nesse
intervalo, entao nao existem outros divisores alem do 1 e o proprio numero.

O Pseudocodigo 3.9 mostra um algoritmo que descreve os passos a serem realizados pela
funcao.

O Exemplo 3.23 mostra a implementacao desse algoritmo. Note que o valor retornado pela
funcao ehPrimo foi convencionado. Caso o numero passado seja primo, a funcao retornara o
valor um, caso contrario retornara o valor zero. O programador tem a liberdade de convencionar
o valor retornado, mas devera indicar isso no comentario do programa.
3.10. EXERCICIOS RESOLVIDOS 99

Pseudocodigo 3.9 Passos a serem relizados pela funcao.


Processo componente " ehPrimo " :
Verificar se x e igual a um
Caso afirmativo :
Retornar o valor zero
Verificar se x e igual a 2
Caso afirmativo :
Retornar o valor um
Verificar se x e divisvel por 2
Caso afirmativo :
Retornar o valor zero
Para um loop com i variando de 3 a raiz quadrada de x :
Verificar se x e divisvel por i
Caso afirmativo :
Retornar o valor zero
Incrementar o valor de i em 2 unidades
Retornar o valor um
FIM - Processo componente " ehPrimo "

Func~a o Principal :
Coletar o valor da variavel quant que representa o numero de primos a serem
impressos
Iniciar numero com valor 1.
Para um loop com cont variando de zero a quant :
Usar a func~a o ehPrimo para verificar se cont e primo
Caso afirmativo ;
Imprimir cont
Incrementar cont
FIM - Func~
a o Principal

1 # include < stdio .h >


2 # include < math .h >
3
4 /*
5 Verifica se o numero passado como par^ a metro e primo .
6 Entrada : um numero inteiro positivo diferente de zero
7 Sada : retorna 1 para o caso do numero informado for primo e zero caso contrario
8 */
9 int ehPrimo ( int x ) {
10
11 int i ;
12
13 if ( x == 1) {
14 return 0;
15 }
16
17 if ( x == 2) {
18 return 1;
100 CAPITULO 3. MODULARIZACAO

19 }
20
21 if ( x % 2 == 0) {
22 return 0;
23 }
24
25 for ( i = 3; i <= sqrt ( x ) ; i = i +2) {
26 if ( x % i == 0) {
27 return 0;
28 }
29 }
30
31 return 1;
32 }
33
34 int main () {
35
36 int quant , numero , cont ;
37
38 printf ( " Digite a quantidade de numeros primos a serem impressos : " ) ;
39
40 scanf ( " % d " , & quant ) ;
41
42 printf ( " Os numeros primos s~
ao : " ) ;
43
44 for ( numero = 1 , cont = 0; cont < quant ; numero ++) {
45 if ( ehPrimo ( numero ) == 1) {
46 printf ( " % d " , numero ) ;
47
48 cont ++;
49 }
50 }
51
52 printf ( " \ n " ) ;
53
54 return 0;
55 }

Exemplo 3.23: Exerccio resolvido - N primeiros numeros primos.

Exerccio Resolvido 3.6 - Palndromo

Um palndromo e uma palavra, frase ou qualquer outra sequencia de unidades (como uma
cadeia de ADN) que tenha a propriedade de poder ser lida tanto da direita para a esquerda
como da esquerda para a direita.
Sabendo-se disso, faca uma funcao que verifique se um numero inteiro e palndromo.
Exemplo:
101 e palndromo
1012 nao e palndromo
3.10. EXERCICIOS RESOLVIDOS 101

Solucao Possvel:

Para fazer essa funcao e necessario ler o numero de tras para frente. Para ler o ultimo
algarismo de um numero guardado numa variavel x basta guardamos numa variavel m o resto
da divisao inteira desse numero por 10. Apos isso, guarda-se em x a parte inteira da divisao de
x por 10, excluindo-se assim o ultimo algarismo do numero. Nesse momento a variavel m possui
o ultimo algarismo original de x, enquanto esta possui agora apenas os primeiros algarismos
originais, excluindo-se apenas o ultimo.

Entao, multiplicando-se m por 10 e somando-se a esse resultado o resto da divisao inteira do


novo valor de x por 10, teremos em m os ultimos dois algarismo de x na ordem inversa.

E facil perceber que basta fazer os passos acima recursivamente para obter em m o numero
x original, mas na ordem inversa. Tendo-se esse numero, basta compara-lo com o valor original,
guardado anteriormente em uma outra variavel.

A Figura 3.10 ilustra o comportamento das variaveis x e m com o decorrer das iteracoes
para um x inicial igual a 52325. A coluna x /10 mostra a parte inteira da divisao de x por 10,
enquanto a coluna x % 10 mostra o resto dessa divisao. Percebe-se que o processo iterativo deve
acabar quando o x final for igual a zero. Note que no final desse exemplo a variavel m contem
o numero inicial x do processo, mostrando que 52325 e palndromo.

Iteracao x inicial x / 10 x % 10 m final x final


1 52325 5232 5 5 5232
2 5232 523 2 52 523
3 523 52 3 523 52
4 52 5 2 5232 5
5 5 0 5 52325 0

Figura 3.10: Solucao para x = 52325.

O Pseudocodigo 3.10 mostra o algoritmo que descreve os passos a serem realizados pela
funcao.
102 CAPITULO 3. MODULARIZACAO

Pseudocodigo 3.10 Passos a serem relizados pela funcao.


Processo componente " palindromo " :
Guardar em c o valor de x
Iniciar m com zero
Enquanto x for diferente de zero :
Fazer m igual a multiplicac~a o de m por 10 com a soma da divis~
a o inteira
de x por 10.
Fazer x igual a divis~a o inteira de x por 10
Verificar se m e igual a c
Caso positivo :
Retornar o valor um
Caso negativo :
Retornar o valor zero .
FIM - Processo componente " palindromo "

O Exemplo 3.24 mostra a implementacao desse algoritmo. Note que a variavel c guarda o
valor inicial de x e que esta variavel e comparada com o valor de m ao final do processo. Aqui
tambem usou-se a convencao de usar o valor de retorno 1 para indicar sucesso na verificacao e
zero caso contrario.

1 int palindromo ( int x ) {


2 int c , m ;
3
4 c = x;
5
6 m = 0;
7
8 while ( x != 0) {
9 m = m *10 + ( x % 10) ;
10 x = x / 10;
11 }
12
13 if ( m == c ) {
14 return 1;
15 } else {
16 return 0;
17 }
18 }

Exemplo 3.24: Exerccio resolvido - Palndromo.

Exerccio Resolvido 3.7 - Maximo Divisor Comum

Faca uma funcao que calcule o maximo divisor comum entre dois numeros naturais passados
como parametros da funcao. Faca a funcao na forma recursiva e na forma iterativa.

Solucao Possvel:
3.10. EXERCICIOS RESOLVIDOS 103

A apresentacao mais simples consiste em comparar-se os numeros e colocar a diferenca entre


os dois (o menor subtrado do maior). Agora compara-se o resultado dessa subtracao com o
menor numero anterior, repetindo-se o processo ate que se obtenha igualdade entre os numeros
nas duas colunas, que e o resultado procurado.
Para melhor compreender o funcionamento do metodo, basta recorrer a um raciocnio muito
simples. Com efeito, se observarmos com atencao a tabuada da multiplicacao de um numero
qualquer, podemos ver que a diferenca entre dois produtos e sempre um produto que figura
na mesma tabuada. Portanto, se dois numeros forem multiplos de um terceiro, entao a sua
diferenca tambem e, o que nos permite substituir o maior deles por essa diferenca, para efeitos
de calculo do MDC; e tudo isso tantas vezes quantas forem necessarias, ate que se chegue a um
ponto em que os dois numeros se identificam no mesmo, o que nos remete para a questao mais
simples: qual e o MDC de dois numeros iguais? O que e precisamente a situacao a que se chegou
no nosso metodo.
O Pseudocodigo 3.11 mostra o algoritmo que descreve os passos a serem realizados pela
funcao iterativa.

Pseudocodigo 3.11 Passos a serem relizados pela funcao.


Processo componente " mdc_Recursivo " :
Verificar se a e igual a b
Caso positivo :
Retornar o valor de a
Verificar se a e maior que b
Caso positivo :
Chamar a func~
a o mdc_Recursivo passando - se como primeiro par^ a metro o
valor da subtrac~
a o de a - b e como segundo par^
a metro o b
Caso negativo :
Chamar a func~
a o mdc_Recursivo passando - se como primeiro par^ a metro o
valor da subtrac~
a o de b - a e como segundo par^
a metro o a
FIM - Processo componente " mdc_Recursivo "

O Exemplo 3.25 mostra a implementacao desse algoritmo.

1 int mdc_Recursivo ( int a , int b ) {


2 if ( a == b ) {
3 return a ;
4 }
5
6 if ( a > b ) {
7 return mdc (a -b , a ) ;
8 } else {
9 return mdc (a , b - a ) ;
10 }
11 }

Exemplo 3.25: Exerccio resolvido - M.D.C. (modo recursivo).


104 CAPITULO 3. MODULARIZACAO

O Pseudocodigo 3.12 mostra o algoritmo que descreve os passos a serem realizados pela
funcao iterativa.

Pseudocodigo 3.12 Passos a serem relizados pela funcao.


Processo componente " mdc_Iterativo " :
Enquanto a for diferente de b faca :
Verificar se a e maior que b :
Caso positivo :
Colocar em a o resultado da subtrac~
ao a - b
Caso contrario :
Colocar em b o resultado da subtrac~
ao b - a
Retornar o valor de a
FIM - Processo componente " mdc_Iterativo "

O Exemplo 3.26 mostra a implementacao desse algoritmo. Note que nesse caso a imple-
mentacao na forma iterativa e obtida facilmente a partir da forma recursiva.

1 int mdc_Iterativo ( int a , int b ) {


2
3 while ( a != b ) {
4 if ( a > b ) {
5 a = a-b;
6 } else {
7 b = b-a;
8 }
9 }
10
11 return a ;
12 }

Exemplo 3.26: Exerccio resolvido - M.D.C. (modo iterativo).

3.11 Resumo
A modularizacao baseia-se na conhecida tecnica de dividir para conquistare pode ser
definida como a divisao de um problema em varias partes. Cada uma dessas partes pode
ser desenvolvida independentemente das outras.
As partes principais de um subprograma sao: cabecalho, dicionario de dados, corpo e
comentarios.
Os parametros de um subprograma sao os dados iniciais necessarios para a funcao poder
realizar o seu trabalho. O subprograma pode tambem nao receber nenhum parametro para
realizar suas funcoes. Na linguagem C, quando a funcao nao tem parametros usa-se void
na declaracao da funcao, em sua lista de parametros.
3.12. EXERCICIOS PROPOSTOS 105

Um subprograma podera produzir no decorrer das suas instrucoes um valor que sera retor-
nado, chamado de valor de retorno. Na linguagem C, quando a funcao nao produz nenhum
valor de retorno usa-se void para simbolizar o tipo de dado retornado.

Algumas vezes e interessante antecipar o encerramento da execucao de programas.

A programacao recursiva e uma tecnica muito utilizada na implementacao de funcoes.

Geralmente e possvel criar versoes recursivas e nao recursivas de uma mesma funcao.

As vantagens encontradas em um programa modularizado sao:


Legibilidade: facilidade de leitura e entendimento do codigo fonte;

Manutenibilidade: facilidade de modificar o codigo fonte;

Reusabilidade: facilidade de reutilizar total ou parcialmente o codigo fonte;

Confiabilidade: como o programa e facil de ser entendido e corrigido, torna-se mais


confiavel;

Produtividade: o programa pode ser dividido em modulos e cada modulo pode ser traba-
lhado por uma equipe diferente. Alem disso, uma equipe so precisa saber o que determi-
nado modulo de outra equipe faz e nao como faz;

3.12 Exerccios Propostos


1. (a) Construa uma funcao encaixa que, dados dois inteiros positivos a e b, verifica se b
corresponde aos ultimos dgitos de a.
Ex.:

a b
567890 890 =>encaixa
1243 1243 =>encaixa
2457 245 =>nao encaixa
457 2457 =>nao encaixa

(b) Usando a funcao do item anterior, faca um programa que le dois inteiros positivos a e
b e verifica se o menor deles e segmento do outro.
Exemplo:

a b
567890 678 => b e segmento de a
1243 2212435 => a e segmento de b
235 236 => um nao e segmento do outro
106 CAPITULO 3. MODULARIZACAO

2. Faca uma funcao que receba como argumento os lados de um triangulo e calcule o seu
permetro e em seguida o valor da area.

3. Considere o valor de = 3.141592. Construa uma funcao para calcular a area de um


crculo tendo como dado de entrada, o valor do raio. Em seguida, fazer outra funcao para
calcular o raio do crculo que possui como area, a metade da area calculada anteriormente.

4. Faca uma funcao que receba como parametro uma quantia em reais (valor inteiro). Calcule
o numero de cedulas de cada tipo (1, 2, 5, 10, 20, 50, 100), necessario para pagar a quantia.
Exiba apenas o numero nao nulo de cedulas de cada tipo.

5. Faca uma funcao que receba como entrada dois valores inteiros, a e b, e exiba a sequencia
de numeros pares do intervalo (a, b), com a < b.

6. Uma loja de material de construcao precisa de um programa para saber a quantidade de


metros quadrados (m2) que devem ser usados para colocar piso nas casas dos clientes. E
fornecido o numero de comodos, os lados dos quadrilateros de cada comodo e o preco do
piso que o cliente escolheu. Faca uma funcao que receba essas informacoes como dados de
entrada e imprima a quantidade de piso (em m2 ) e o valor da compra. Obs: Todos os
comodos da casa tem a forma de quadrilateros.

7. Faca uma funcao que calcule e retorne o n-esimo termo de uma PA (progressao aritmetica)
sendo fornecidos como entrada o numero de termos, o primeiro termo e a razao.

8. Elabore uma funcao em C que calcule o valor aproximado de com precisao de cinco
decimos atraves da serie:

1 1 1
= 13
33
+ 53
. . .

Considere que a precisao de cinco decimos requer que a soma dos elementos da serie so
deve ser interrompida quando o valor do termo e inferior a 0,00001. Para implementar a
funcao, use uma subfuncao que, dados x e y, calcule xy .
3.13. TRABALHOS SUGERIDOS 107

3.13 Trabalhos Sugeridos


1. Aplicando Conhecimentos de Calculo
Em um belo dia, um preguicoso monitor de Calculo teve uma simples ideia para facilitar
seu trabalho. Percebendo que varios alunos tinham duvidas sobre derivadas e integrais
de polinomios, ele resolveu pedir a um programador (nesse caso, voce) um algoritmo que
fizesse esse trabalho.
Por experiencia, o monitor restringiu o programa a polinomios de ate grau nove e passou
as seguintes especificacoes ao programador:
Exemplos de Dados de Entrada
Uma funcao seria digitada da seguinte maneira: (Opcao 1)

+2x4+0x3-25x2+1x1-5x0=

ou seja, a leitura devera ser pelo teclado e sempre na mesma ordem: <operador> <coeficiente>
<variavel> <potencia>. Observacao: a leitura do teclado deve ser feita termo a termo,
por exemplo, +2x4 [enter] +0x3 [enter]. Apenas o caractere = podera ser lido sepa-
radamente.
Uma funcao f (x) = 2x4 25x2 + x 5 seria digitada da seguinte maneira: (Opcao 2)

+2x4-25x2+1x-5=

Ou seja, a leitura devera ser pelo teclado, porem com algumas diferencas em relacao a
opcao anterior. Serao omitidos a potencia 1, a variavel com potencia 0 e os termos
com coeficiente 0. Observacao: nesse caso a leitura tambem deve ser feita termo a termo,
porem, deve-se lembrar que alguns termos podem ser menores que outros (+2x4 e 1x,
por exemplo).
Em ambos os casos, a flag de final de leitura sera o caracter = e os coeficientes serao
numero reais (float). A ideia e que, se o programador optar pela segunda opcao, o algoritmo
devera ser generico ao ponto de receber as duas formas de entrada.
Armazenada a funcao de entrada, o programa devera imprimir, no mesmo formato de
entrada, a derivada e a integral da funcao. Como se nao bastasse a folga do monitor, ele
ainda deseja que o programa imprima tambem as razes das funcoes quando estas forem
do primeiro ou do segundo grau.
Exemplos de Sada
Usando a funcao de entrada: f (x) = x2 5x + 6

Derivada 2.00x1-5.00x0= (Opcao 1) 2.00x-5.00= (Opcao 2)


108 CAPITULO 3. MODULARIZACAO

Integral 0.33x3-2.50x2+6.00x1+C= (Opcao 1) 0.33x3-2.50x2+6.00x+C= (Opcao 2)


Razes (apenas para funcoes de primeiro e segundo grau) x1=2.00 x2=3.00 Os coefi-
cientes e as razes deverao ser impressos com 2 casas decimais.
Captulo 4

Tipos Abstratos de Dados


Co-Autores:

Bruna Vello Colnago


Rodrigo Lopes Batista

Objetivos:

Introduzir o conceito de Tipos Compostos Heterogeneos;

Explicar as vantagens da utilizacao de Tipos Abstratos de dados;

Tornar o codigo do programa mais compreensvel;

Simplificar a confeccao de aplicacoes mais complexas.

Este captulo busca esclarecer como a criacao e manipulacao de novos tipos de dados permite
facilitar o entendimento, a confeccao e o reaproveitamento de codigo.

4.1 Tecnicas de Programacao Top-down e Bottom-up


Para escrever um programa, os programadores podem adotar duas tecnicas: a Top-down e a
Bottom-up. A tecnica Bottom-up consiste em considerar o programa como um conjunto de
modulos correlacionados, implementar cada um desses modulos e depois junta-los por meio de
uma estrutura global. Ja a tecnica Top-down consiste em primeiramente definir a estrutura
global do programa e posteriormente definir cada uma das partes que detalham as suas tarefas.
Analogamente, um projetista de automoveis pode inicialmente definir como sera o carro,
velocidade, rapidez, rotacoes maximas, etc , e posteriormente confeccionar seus componentes
(motor, rodas, freios, suspensao...), conforme a necessidade - Top-down - ou confeccionar cada
um dos componentes separadamente, garantir seu funcionamento e depois junta-los em um carro
cujas metas sejam compatveis com as possibilidades dos componentes - Bottom-up.

109
110 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Na abordagem Bottom-up, cada modulo e implementado segundo suas funcionalidades e


objetivos, independentemente do restante do programa, assim torna-se mais simples redigir o
codigo do programa, facilitando o trabalho do programador. Alem do mais, a separacao do
programa em modulos permite a realizacao de testes especficos, facilitando a depuracao de
erros. Outra importancia dessa abordagem e a facilidade de alterar partes do programa, sendo
necessario alterar apenas alguns modulos sem se importar com os demais.
Por fim, o programa torna-se muito mais facil de ser entendido, ao passo que o leitor nao
precisa ler todo o codigo do programa, bastando saber as funcionalidades de cada modulo. A
implementacao dos mesmos importa somente ao programador. Assim, a abordagem Bottom-up
e mais vantajosa em relacao a Top-down, ao passo que e muito mais intuitiva ao programador.

4.2 Tipos Compostos Heterogeneos (Estruturas)

As formas de representacao de dados fornecidas pelo computador sao insuficientes para que o
programador possa representar em sua totalidade as possveis colecoes de dados existentes. Pro-
gramadores ha muito tempo reconhecem o valor de se organizar itens de dados correlacionados
em uma unica entidade computacional. As Estruturas sao entidades que representam tipos de
dados e que permitem o agrupamento de varias variaveis de diversos tipos.
Suponha um programa que faca o cadastro de estudantes de uma universidade, cada um
possuindo um conjunto de atributos correlacionados, como por exemplo: matrcula, idade, co-
eficiente de rendimento e perodo. Utilizando as entidades de programacao ja conhecidas, no
cadastro de um unico aluno seriam necessarias quatro variaveis para representar todos os seus
atributos; para dois estudantes, seriam necessarias oito variaveis; ja para diversos estudantes
seria necessario um conjunto de variaveis consideravelmente grande.
A utilizacao de muitas variaveis aumenta o trabalho do programador e dificulta que outra
pessoa entenda a aplicacao. Para solucionar esse problema, muitas linguagens de programacao,
entre elas a linguagem C, possibilitam ao programador criar tipos compostos heterogeneos. Na
linguagem C, os tipos compostos heterogeneos sao chamados struct (estruturas). As estruturas
sao conjuntos de dados correlacionados, que podem ser representados em sua totalidade por uma
unica variavel. No exemplo mencionado, cada estudante poderia ser representado por uma unica
variavel, dessa forma, seria necessario adicionar apenas uma variavel para cada novo estudante
a ser cadastrado.
Dada a impossibilidade de se antecipar todos os tipos de dados utilizados por um programa,
uma linguagem de programacao apenas implementa tipos de dados simples. Desse modo, fica a
cargo do programador usar desses dados para a definicao de novos tipos de dados.
Alem de facilitar e tornar mais clara a implementacao, os tipos compostos heterogeneos tem
como finalidades principais a possibilidade de retorno de mais de um valor por funcao e a criacao
de tipos abstratos de dados. Essas caractersticas sao discutidas mais adiante neste captulo.
4.2. TIPOS COMPOSTOS HETEROGENEOS (ESTRUTURAS) 111

4.2.1 Definicao
Uma estrutura e uma colecao de variaveis, que podem ou nao ser de tipos diferentes, colocadas
sob um unico nome para manipula-las. As estruturas ajudam na organizacao do codigo e fa-
cilitam a vida do programador, pois juntam variaveis relacionadas e permitem que elas sejam
tratadas como uma unidade maior.
Na pratica, uma estrutura e uma caixa onde podem ser agrupados diversos dados correla-
cionados. Essa caixa, na verdade, e o conjunto de alguns bytes de memoria correspondente ao
somatorio dos tamanhos dos dados que se pretende agrupar.
A Figura 4.1 ilustra uma estrutura que representa um estudante, tEstudante. Note que a
estrutura tEstudante ocupa um espaco equivalente ao espaco necessario para guardar todos os
seus atributos.

Figura 4.1: Estrutura tEstudante

Sintaxe
Para criar uma estrutura com n atributos, deve-se obdecer a sintaxe exposta abaixo.

struct <nome da estrutura>{


<tipo do atributo 1> <nome do atributo 1>;
<tipo do atributo 2> <nome do atributo 2>;
...
<tipo do atributo n> <nome do atributo n>;
};

A palavra struct indica que a entidade criada e uma estrutura e as chaves delimitam o trecho
onde os atributos da estrutura sao definidos. Cada atributo e definido por seu tipo seguido de
um identificador.
O Exemplo 4.1 apresenta a sintaxe de uma estrutura para o caso do estudante.

1 struct tEstudante {
2 int idade ;
3 int matricula ;
112 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

4 float coeficiente ;
5 int periodo ;
6 };

Exemplo 4.1: Sintaxe da estrutura tEstudante

4.2.2 Uso
Como as estruturas sao conjuntos de dados, existem duas formas basicas de manipula-las, se-
lecionando um unico elemento de dado de forma seletiva, ou manipulando toda a estrutura de
forma integral.

Seletivo
Para manipular cada um dos atributos da estrutura se utiliza o mecanismo de selecao conforme
a sintaxe apresentada a seguir.

<nome da variavel>.<nome do atributo>

O Exemplo 4.2 ilustra o uso seletivo da estrutura estudante. Nesse Exemplo, a leitura de
dados e feita atribuindo-se cada valor lido a um atributo da estrutura.

1 # include < stdio .h >


2 # include < stdlib .h >
3
4 struct tEstudante {
5 int idade ;
6 int matricula ;
7 float coeficiente ;
8 int periodo ;
9 };
10
11 main () {
12 struct tEstudante aluno ;
13 printf ( " Digite os dados do estudante " ) ;
14 scanf ( " % d % d % f % d " ,& aluno . idade ,& aluno . matricula ,& aluno . coeficiente ,& aluno .
periodo ) ;
15 }

Exemplo 4.2: Acesso seletivo a atributos da estrutura.

Integral
Quando se deseja manipular a estrutura como um todo, se utiliza simplesmente o nome da
variavel onde a mesma esta armazenada. O uso integral em atribuicoes e descrito pela sintaxe
a seguir.
4.2. TIPOS COMPOSTOS HETEROGENEOS (ESTRUTURAS) 113

<nome da variavel 1> = <nome da variavel 2>

O Exemplo 4.3 ilustra o uso integral da estrutura estudante.

1 # include < stdio .h >


2 # include < stdlib .h >
3
4 struct tEstudante {
5 int idade , matricula , periodo ;
6 float coeficiente ;
7 };
8
9 main () {
10 struct tEstudante aluno , outro ;
11 printf ( " Digite os dados do estudante " ) ;
12 scanf ( " % d % d % f % d " ,& aluno . idade ,& aluno . matricula ,& aluno . coeficiente ,& aluno .
periodo ) ;
13 outro = aluno ;
14 }

Exemplo 4.3: Acesso integral a estrutura

No Exemplo 4.3, ao final da execucao do programa os valores dos atributos de outro sao
iguais aos valores dos atributos de aluno.
O comando typedef pode ser utilizado para renomear tipos simples ou compostos. Para
utilizar o comando typedef, deve-se obdecer a sintaxe exposta abaixo.

typedef <nome do tipo simples ou composto> <novo nome>;

Na pratica, a renomeacao de tipos facilita o entendimento e a escrita de codigo.


O Exemplo 4.4 apresenta a utilizacao do typedef.

1 typedef struct tEstudante tEstudante ;


2
3 typedef float distancia ;

Exemplo 4.4: Uso do comando typedef

O Exemplo 4.5 apresenta a adaptacao do Exemplo 4.3 para a utilizacao do comando typedef,
permitindo que o tipo composto heterogeneo struct tEstudante seja utilizado atraves da palavra
struct tEstudante.

1 # include < stdio .h >


2 # include < stdlib .h >
3
4 struct tEstudante {
5 int idade ;
114 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

6 int matricula ;
7 float coeficiente ;
8 int periodo ;
9 };
10
11 typedef struct tEstudante tEstudante ;
12
13 main () {
14 tEstudante aluno , outro ;
15 printf ( " Digite os dados do estudante " ) ;
16 scanf ( " % d % d % f % d " ,& aluno . idade ,& aluno . matricula ,& aluno . coeficiente ,& aluno .
periodo ) ;
17 outro = aluno ;
18 }

Exemplo 4.5: Acesso integral a estrutura utilizando o comando typedef

Simplificacao na Passagem de Parametros e Retorno de Funcao


Conforme comentou-se anteriormente, as estruturas sao conjuntos de dados inter-relacionados,
dessa forma a passagem de parametros de funcao e simplificada, pois ao inves de cada um dos
atributos ser um parametro da funcao, apenas a estrutura e passada. Note a diferenca entre a
lista de parametros das duas funcoes representadas no Exemplo 4.6.

1 void funcao1 ( int idade , int matricula , float coeficiente , int periodo ) { ... }
2
3 void funcao2 ( tEstudante aluno ) { ... }

Exemplo 4.6: Passagem de estrutura por parametro.

A mesma funcao pode ser escrita dos dois modos apresentados no Exemplo 4.6 na funcao1
quatro variaveis relacionadas ao estudante sao passadas como parametro; na funcao2 , o proprio
estudante e passado como parametro. A funcao2 e mais compreensvel que a funcao1 , pois a
pessoa que ler o codigo da funcao1 tera que inferir que aquelas quatro variaveis referem-se a
um mesmo estudante.
Da mesma forma, o retorno de funcoes pode ser simplificado pelo uso de estruturas. Suponha
que se deseje fazer a leitura de dados de um estudante. O Exemplo 4.7 mostra duas maneiras de
fazer isso: utilizando funcoes que retornam estruturas ou funcoes que retornam tipos simples.

1 # include < stdio .h >


2 # include < stdlib .h >
3
4 struct tEstudante {
5 int idade ;
6 int matricula ;
7 float coeficiente ;
4.2. TIPOS COMPOSTOS HETEROGENEOS (ESTRUTURAS) 115

8 int periodo ;
9 };
10
11 typedef struct tEstudante tEstudante ;
12
13 int lePeriodo () {
14 int periodo ;
15 scanf ( " % d " ,& periodo ) ;
16 return periodo ;
17 }
18
19 int leMatricula () {
20 int matricula ;
21 scanf ( " % d " ,& matricula ) ;
22 return matricula ;
23 }
24
25 float leCoeficiente () {
26 float coeficiente ;
27 scanf ( " % f " ,& coeficiente ) ;
28 return coeficiente ;
29 }
30
31 int leIdade () {
32 int idade ;
33 scanf ( " % d " ,& idade ) ;
34 return idade ;
35 }
36
37 tEstudante leEstudante () {
38 tEstudante aluno ;
39 scanf ( " % d % d % f % d " ,& aluno . idade ,& aluno . matricula ,& aluno . coeficiente ,& aluno .
periodo ) ;
40 return aluno ;
41 }
42
43 main () {
44 tEstudante aluno1 , aluno2 ;
45 aluno1 . periodo = lePeriodo () ;
46 aluno1 . matricula = leMatricula () ;
47 aluno1 . coeficiente = leCoeficiente () ;
48 aluno1 . idade = leIdade () ;
49 aluno2 = leEstudante () ;
50 }

Exemplo 4.7: Estrutura como retorno de funcao.

No Exemplo 4.7 e realizada a leitura de dados de aluno1 sem utilizar o retorno de estruturas,
e de aluno2, utilizando-o. Para realizar a leitura de aluno1 foram necessarias a declaracao de
quatro funcoes diferentes, uma para cada atributo, e quatro chamadas de funcao no programa
principal. Para realizar a leitura de aluno2 foi necessario apenas a declaracao de uma funcao
116 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

e uma chamada de funcao no programa principal. Assim, pode-se perceber que o retorno de
estruturas torna o codigo mais compacto, legivel e eficiente, por ter menos chamadas de funcao.

4.3 Tipos Abstratos de Dados


Tipos abstratos de dados (TADs) sao novos tipos de dados implementados pelo programador,
nos quais ele define tanto as estruturas de dados quanto as operacoes a elas aplicaveis, conforme
suas necessidades para a resolucao de um determinado problema.
Os TADs podem ser considerados generalizacoes de tipos primitivos de dados, assim como
funcoes sao generalizacoes de operacoes primitivas, tais como adicao, subtracao e multiplicacao.
Da mesma forma que funcoes podem ser usadas para encapsular partes de algoritmos, o TAD
pode ser usado para encapsular tipos de dados.
O TAD pode ser discutido pela perspectiva do implementador e do usuario do tipo. O im-
plementador cria as estruturas de dados, implementa as funcoes para manipula-las. Ja o usuario
utiliza esse TAD como se fosse um tipo de dados fornecido pela linguagem de programacao.
Deste modo, o usuario so deve manipular os atributos do TAD atraves das funcoes definidas
pelo implementador do tipo.

4.3.1 Definicao
Um tipo abstrato de dados e um tipo de dado definido em termos do seu comportamento e
nao em termos de sua representacao. A ideia de tipo abstrato de dados e desvincular o tipo de
dado (valores e operacoes) de sua implementacao, ou seja, quando definimos um tipo abstrato
de dados estamos preocupados com o que ele faz e nao como ele faz.
Os programas que usam um determinado tipo abstrato de dados sao chamados clientes; e
o programa que define sua estrutura e comportamento e conhecido como implementacao. Um
TAD pode ser definido como a composicao de uma estrutura de dados e das operacoes definidas
sobre estes dados.

Exemplo da Definicao de um Tipo Composto com Operacoes Pre-definidas


Suponha que se deseja definir um tipo abstrato de dados tData, considerando os atributos dia,
mes e ano. Primeiramente, deve-se decidir as operacoes a serem definidos para o TAD, que
podem ser as seguintes:

inicializaData: funcao que inicializa uma data a partir de valores passados como parametro.

leData: funcao que inicializa uma data a partir de valores lidos do teclado.

alteraData: funcao que altera uma data a partir de valores passados como parametro.

eBissexto: funcao que indica se um ano e bissexto ou nao.

diasNoMes: funcao que indica a quantidade de dias do mes em questao.


4.3. TIPOS ABSTRATOS DE DADOS 117

diaSeguinte: funcao que altera a data para o dia seguinte.


Pode-se entao implementar o TAD na linguagem desejada. No Exemplo 4.8, observa-se a
implementacao do TAD tData na linguagem C.

1 # include < stdio .h >


2
3 typedef struct data {
4 int dia ;
5 int mes ;
6 int ano ;
7 } tData ;
8
9 tData in icializarValores ( int d , int m , int a ) {
10 tData dt ;
11
12 dt . dia = d ;
13 dt . mes = m ;
14 dt . ano = a ;
15 return dt ;
16 }
17
18 tData leData () {
19 tData d ;
20
21 printf ( " Entre com a data " ) ;
22 scanf ( " % d % d % d " ,& d . dia ,& d . mes ,& d . ano ) ;
23 return d ;
24 }
25
26 tData alteraData ( int d , int m , int a ) {
27 tData dt ;
28
29 dt . dia = d ;
30 dt . mes = m ;
31 dt . ano = a ;
32 return dt ;
33 }
34
35 int eBissexto ( tData d ) {
36 if ( d . ano %400==0) {
37 return 1;
38 } else if ( d . ano %100==0) {
39 return 0;
40 } else if ( d . ano %4==0) {
41 return 1;
42 } else {
43 return 0;
44 }
45 }
46
118 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

47 int diasNoMes ( tData d ) {


48 if ( d . mes ==4|| d . mes ==6|| d . mes ==9|| d . mes ==11) {
49 return 30;
50 } else {
51 if ( d . mes =2) {
52 if ( eBissexto ( d ) ) {
53 return 29;
54 } else {
55 return 28;
56 }
57 } else {
58 return 31;
59 }
60 }
61 }
62
63 tData diaSeguinte ( tData d ) {
64 if ( d . dia < diasNoMes ( d ) ) {
65 d . dia ++;
66 } else {
67 d . dia =1;
68 if ( d . mes <12) {
69 d . mes ++;
70 } else {
71 d . mes =1;
72 d . ano ++;
73 }
74 }
75 return d ;
76 }
77
78 main () {
79 }

Exemplo 4.8: Definicao do TAD tData.

O codigo do Exemplo 4.8 ilustra a definicao de um Tipo Composto com Operacoes Pre-
definidas. O tipo tData contem os atributos de uma data. Uma variavel desse tipo e inicializada
atraves da funcao inicializarValores, essa funcao permite inicializacao dos atributos sem que
estes sejam acessados. A funcao de leitura leData obtem uma data do teclado e retorna uma
variavel do tipo tData contendo os valores lidos. A funcao alteraData apresenta o mesmo
codigo que a funcao de inicializacao, contudo sua utilizacao destina-se a datas ja inicializadas.
A funcao eBissexto verifica se uma dada data esta em um ano bissexto, o algoritmo considera a
seguinte regra: Sao bissextos todos os anos multiplos de 400, nao sao bissextos todos os multiplos
de 100 e nao de 400, sao bissextos todos os multiplos de 4 e nao multiplos de 100, por fim, nao
sao bissextos todos os demais anos. A funcao diasNoMes determina o numero de dias de um
do mes de uma determinada data. Os meses de abril, junho, setembro e novembro possuem 30
dias. Caso a data seja do mes de fevereiro, verifica-se se o ano e bissexto, se for bissexto o mes
4.3. TIPOS ABSTRATOS DE DADOS 119

tem 29 dias, se nao for bissexto o mes tem 28 dias. Caso a data nao esteja em nenhum dos
meses citados, o mes apresenta 31 dias.

Importancia de Definir Tipos com Operacoes Proprias


A criacao de um conjunto de operacoes proprias de um TAD tem como finalidade torna-lo,
aos olhos do usuario, um tipo de dados da propria linguagem, ou seja, o usuario nao deve se
preocupar em como sao implementados os TADs. Alem do mais, utilizar as operacoes do TAD
torna o codigo mais confiavel. Suponha uma aplicacao que utilize o TAD tData conforme o
Exemplo 4.9.

1 # include < stdio .h >


2
3 // A definic~
a o do TAD , bem como suas operac~
o es , esta no Exemplo 4.6
4
5 main () {
6 tData d ;
7
8 d = leData () ;
9 printf ( " Passou um dia !\ n " ) ;
10 d . dia ++;
11 printf ( " Hoje e % d /% d /% d .\ n " ,d . dia , d . mes , d . ano ) ;
12 }

Exemplo 4.9: Problema de Confiabilidade no uso do TAD tData.

Observe que, no Exemplo 4.9, a intencao do programador foi adicionar um dia a data lida,
entretanto, este nao utilizou a funcao do tipo diaSeguinte. Para grande parte dos valores de
data, o programa funcionaria corretamente, entretanto, para datas que sao o ultimo dia do mes,
esse programa gera inconsistencia, pois a data obtida nao sera uma data valida. No decorrer da
aplicacao, um valor invalido de data sera repassado para outras partes do programa. Assim essa
inconsistencia pode ter um efeito colateral que acarrete em problemas no codigo. Sendo assim,
e altamente recomendavel que se utilizem apenas as operacoes ja definidas para manipular as
estruturas do TAD e que nao ocorra a manipulacao de atributos.

Vantagens de Usar TADs


As vantagens de utilizar um TAD sao:
Facilidade de Manutencao: Pode-se alterar o tipo usado sem alterar a aplicacao. Por
exemplo, pode-se incluir novos atributos ou operacoes sem que o codigo que utilize o tipo
seja alterado.
Reutilizacao: Um TAD bem generalizado pode ser utilizado em diversas aplicacoes.
Abstracao: A abstracao de informacoes atraves do TAD permite a melhor compreensao
dos algoritmos e maior facilidade de programacao.
120 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Ocultamento: Separa o codigo de implementacao do codigo de uso do TAD, que funciona


como um tipo de dados fornecido pela linguagem.

Integridade: A manipulacao dos atribuitos por operacoes definidas sobre o tipo impedem
a ocorrencia de inconsistencias.

Na pratica, essas vantagens tornam o codigo mais facil de se escrever, mais compreensvel
para quem le e mais facil de se modificar.

Problema do uso de TADs em C


A ideia do TAD e esconder o codigo da implementacao do usuario, que nao deve ter acesso
indiscriminado aos atributos do TAD. A linguagem C, entretanto, nao oferece a funcionalidade
de protecao ao acesso aos atributos e operacoes. Na verdade, C nao implementa TAD, o que se
faz e apenas uma simulacao, visto que apesar de se definir as operacoes sobre o tipo, o usuario
tem a liberdade para acessar diretamente os atributos sem nenhuma protecao, tornando o codigo
nao confiavel.

4.3.2 Definicao de Atributos de um TAD


Os atributos de um TAD sao os dados que se relacionam a ele. Por exemplo, no caso do
estudante, seus atributos sao: idade, matrcula, coeficiente de rendimento e perodo. Conforme
definido na Secao 4.2.1.

4.3.3 Definicao de Operacoes de um TAD


As operacoes de um TAD sao funcoes que utilizadas para acesso dos dados. Estas operacoes
visam impedir o acesso direto aos dados, assim, o usuario so deve acessar os dados atraves dessas
operacoes.

Tipos de Operacoes de um TAD


Um TAD nao e definido por seus atributos, mas sim por suas operacoes. As operacoes sao
a interface do programador usuario com a propria representacao interna. Existem cinco tipos
diferentes de operacoes que podem ser realizadas sobre um TAD:

Construtoras
Operacoes construtoras sao aquelas que inicializam variaveis, logo devem ser utilizadas antes de
qualquer outra para garantir que o TAD foi inicializado corretamente. Observe no Exemplo 4.10
diferentes implementacoes de funcoes construtoras para o TAD estudante.

1 tEstudante inicializar () {
2 tEstudante novo ;
4.3. TIPOS ABSTRATOS DE DADOS 121

3
4 novo . idade =0;
5 novo . matricula =0;
6 novo . coeficiente =0;
7 novo . periodo =0;
8 return novo ;
9 }
10
11 tEstudante inicializarValores ( int idade , int matricula , float coeficiente , int
periodo ) {
12 tEstudante novo ;
13
14 novo . idade = idade ;
15 novo . matricula = matricula ;
16 novo . coeficiente = coeficiente ;
17 novo . periodo = periodo ;
18 return novo ;
19 }

Exemplo 4.10: Diferentes formas de Implementacao da funcao construtora para o TAD


tEstudante.

A funcao inicializar inicia com zero todos os atributos de uma variavel do tipo tEstudante,
esta variavel e retornada pela funcao de inicializacao. A funcao inicializarValores exerce o
mesmo papel de inicializar que a funcao inicializar . Contudo, esta recebe os valores a serem
incializados como parametros da funcao.

Analisadoras

As operacoes analisadoras ou consultoras analisam o conteudo de um TAD e retornam proprie-


dades, ou seja, essas operacoes obtem informacoes do TAD. O Exemplo 4.11 mostra uma funcao
anasiladora.

1 int bomAluno ( tEstudante aluno ) {


2 if ( aluno . coeficiente >7.0) {
3 return 1;
4 } else {
5 return 0;
6 }
7 }

Exemplo 4.11: Funcao analisadora.

Observa-se, no Exemplo 4.11, que a funcao bomAluno e um exemplo de funcao analisadora


para o TAD tEstudante, pois retorna uma propriedade do mesmo (se e um bom aluno).
122 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Modificadoras
As operacoes modificadoras, ou atualizadoras, permitem alteracoes de atributos do TAD. O
Exemplo 4.12 mostra uma funcao modificadora para o TAD tEstudante.

1 tEstudante alteraIdade ( tEstudante aluno , int novaIdade ) {


2 aluno . idade = novaIdade ;
3 return aluno ;
4 }

Exemplo 4.12: Funcao modificadora.

Produtoras
As operacoes produtoras sao aquelas que, a partir dos dados de um TAD, produzem uma nova
informacao. A funcao maiorIdade, do Exemplo 4.13, produz um resultado (a maior idade) a
partir de dois estudantes dados.

1 int maiorIdade ( tEstudante est1 , tEstudante est2 ) {


2 if ( est1 . idade > est2 . idade ) {
3 return est1 . idade ;
4 }
5 return est2 . idade ;
6 }

Exemplo 4.13: Exemplo de funcao produtora.

Destrutoras
As operacoes destrutoras sao utilizadas para liberar recursos de memoria quando o TAD nao e
mais necessario. Exemplos e explicacoes sobre essa funcao sao dados no Captulo 7.

4.3.4 Uso do TAD


Considere o Exemplo 4.14 que faz uso do TAD tData. Note que o TAD e acessado apenas pelas
operacoes pre-definidas.

1 # include < stdio .h >


2
3 // A definic~
a o do TAD , bem como suas operac~
o es , esta no Exemplo 4.6
4
5 main () {
6 tData data ;
7 int anoBissexto ;
8 int nDias ;
4.4. EXERCICIOS RESOLVIDOS 123

9
10 data = leData () ;
11 anoBissexto = eBissexto ( data ) ;
12
13 if ( anoBissexto == 1) {
14 printf ( " Ano Bissexto " ) ;
15 } else {
16 printf ( " Ano n~
a o Bissexto " ) ;
17 }
18
19 nDias = diasNoMes ( data ) ;
20 printf ( " Numero de dias no m^
e s : " , nDias ) ;
21 }

Exemplo 4.14: Uso do TAD tData.

O Exemplo 4.14 determina se uma data digitada pelo teclado e de um ano bissexto e o
numero de dias no mes da data digitada. Pode-se afirmar que o programa usuario manipula o
TAD somente atraves das operacoes pre-definidas pelo implementador, assim o codigo usuario
fica mais legvel. Pode-se observar que as operacoes separam o codigo usuario do codigo de
implementacao do TAD. A redigibilidade tambem aumenta, visto que o acesso aos dados e
realizado apenas por simples operacoes. O codigo tambem fica mais confiavel, pois o usuario
nao altera livremente os dados, isso so pode ser feito atraves das operacoes do TAD. E, por fim,
caso a implementacao do TAD precise ser alterada, o codigo usuario nao sofrera mudancas, a
menos que os cabecalhos das funcoes sejam alterados.

4.3.5 Tipos de TADs


Os TADs podem se dividir em TADs de Domnio e TADs Implementacionais, conforme sua
relacao com o problema ou com sua solucao .

TADs de Domnio
Sao aqueles que definem um tipo de dados que esta no domnio do problema. Como por exemplo,
os TADs tEstudante e tData.

TADs Implementacionais
Sao de objetos de programacao que nao tem relacao direta com o problema, mas sim com sua
implementacao. Podemos tomar como exemplos as listas, as arvores, os grafos e as filas. Alguns
desses conceitos sao apresentados nos proximos captulos.

4.4 Exerccios Resolvidos


Exerccio Resolvido4.1 - Leitura da Estrutura tData
124 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Faca um programa que imprima de uma estrutura do tipo data, a data de nascimento
30/01/1984.

Solucao Possvel:
Nao existem dados de entrada, a data de nascimento a ser informada e armazenada direta-
mente em uma estrutura data. A impressao da data e realizada no formato abreviado utilizando
o comando printf . A sada do programa e a impressao da data 30/01/1984.

1 # include < stdio .h >


2
3 struct data { // estrutura do tipo data
4 int dia ;
5 int mes ;
6 int ano ;
7 };
8
9 int main () {
10 struct data dataNascimento ;
11
12 // inicializa a estrutura data de nascimento
13 dataNascimento . dia = 30;
14 dataNascimento . mes = 01;
15 dataNascimento . ano = 1984;
16 printf ( " Data : % d /% d /% d \ n " , dataNascimento . dia , dataNascimento . mes ,
dataNascimento . ano ) ;
17 return 0;
18 }

Exerccio Resolvido 4.2 - Operacoes sobre a Estrutura Data

Defina, agora, a estrutura do exemplo anterior como um tipo e realize operacoes (sobre este
tipo) de edicao e consulta.

Solucao Possvel:
Este programa utiliza operacoes de alteracao e consulta sobre o TAD para datas. A estrutura
para datas e declara como um tipo utilizando o comando typedef. A operacao alteraData
recebe como parametros o dia, mes e ano de uma determinada data e passa, como retorno de
funcao, a data informada. A operacao consultaData realiza a impressao de uma data. Assim,
pode-se observar as vantangens da utilizacao de TADs, atraves destas duas operacoes e necessario
escrever menos, visto que as operacoes podem ser aproveitadas. A data e inicializada no corpo
do programa em 30/01/1984, em seguida e alterada. A sada do programa e a impressao da
data 16/05/2007.

1 # include < stdio .h >


2
4.4. EXERCICIOS RESOLVIDOS 125

3 typedef struct data {


4 int dia ;
5 int mes ;
6 int ano ;
7 } tData ;
8
9 tData alteraData ( int d , int m , int a ) {
10 tData dt ;
11
12 dt . dia = d ;
13 dt . mes = m ;
14 dt . ano = a ;
15 return dt ;
16 }
17
18 void consultaData ( tData data ) {
19 printf ( " Data : % d /% d /% d \ n " , data . dia , data . mes , data . ano ) ;
20 }
21
22 int main () {
23 tData dataNascimento ;
24
25 dataNascimento = alteraData (30 ,01 ,1984) ;
26 dataNascimento = alteraData (16 ,05 ,2007) ;
27
28 consultaData ( dataNascimento ) ;
29 return 0;
30 }

Exerccio Resolvido 4.3 - Menor Data

Escreva um programa em C que leia duas datas e retorne a menor data cronologicamente.
Observacao: As datas devem ser armazenadas em estruturas.

Solucao Possvel:
O programa que determina qual e a menor data entre duas. Agora, a inicializacao das
datas e realizada por leitura do teclado. O retorno da operacao menorData e a menor entre
duas datas, a primeira verificacao e realizada quanto ao ano, caso o ano das datas sejam iguais
verifica-se o mes, se os meses sao iguais verifica-se os dias.

1 # include < stdio .h >


2
3 struct data { // estrutura do tipo data
4 int dia ;
5 int mes ;
6 int ano ;
7 };
8
126 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

9 struct data menorData ( struct data data0 , struct data data1 ) {


10 if ( data0 . ano < data1 . ano ) {
11 return data0 ;
12 } else if ( data1 . ano < data0 . ano ) {
13 return data1 ;
14 } else if ( data0 . mes < data1 . mes ) {
15 return data0 ;
16 } else if ( data1 . mes < data0 . mes ) {
17 return data1 ;
18 } else if ( data0 . dia < data1 . dia ) {
19 return data0 ;
20 } else {
21 return data1 ;
22 }
23 }
24
25 struct data leData () {
26 struct data d ;
27
28 printf ( " Entre com a data " ) ;
29 scanf ( " % d % d % d " ,& d . dia ,& d . mes ,& d . ano ) ;
30 return d ;
31 }
32
33 main () {
34 // declarac~
a o de variaveis
35 struct data data0 , data1 , menor ;
36
37 // entrada das datas
38 printf ( " Primeira data :\ n " ) ;
39 data0 = leData () ;
40 printf ( " \ nSegunda data :\ n " ) ;
41 data1 = leData () ;
42
43 // determina a menor data
44 menor = menorData ( data0 , data1 ) ;
45
46 printf ( " \ nA menor data eh : % d / % d / % d .\ n " , menor . dia , menor . mes , menor . ano ) ;
47 }

Exerccio Resolvido 4.4 - TAD Tponto

Defina o tipo Tponto para representar os pontos do plano cartesiano de duas dimensoes.

Solucao Possvel:
Para representar um ponto, e criada uma estrutura com dois atributos, as coordenadas x e
y.
4.4. EXERCICIOS RESOLVIDOS 127

1 typedef struct {
2 int x ;
3 int y ;
4 } Tponto ;

Exerccio Resolvido 4.5 - Operacao Tponto simetricoOrigem (Tponto ponto)

Agora, implemente a operacao Tponto simetricoOrigem (Tponto ponto), para deter-


minar o ponto simetrico de um ponto em relacao a origem.

Solucao Possvel:
A operacao simetricoOrigem tem como retorno uma variavel do tipo Tponto. Para obter
um ponto simetrico a outro em relacao a origem, basta inverter as coordenadas do ponto dado.

1 Tponto simetricoOrigem ( Tponto ponto ) {


2 ponto . x = - ponto . x ;
3 ponto . y = - ponto . y ;
4
5 return ponto ;
6 }

Exerccio Resolvido 4.6 - Operacao int qualQuadrante (Tponto ponto)

Agora, implemente a operacao int qualQuadrante (Tponto ponto) que determina em


qual quadrante pertence um ponto.

Solucao Possvel:
Dado um ponto, a operacao qualQuadrante retorna a qual quadrante esse ponto pertence.
A funcao retorna 0 quando o ponto for parte do limite de quadrantes, 1 quando o ponto pertencer
ao 1o quadrante, 2 quando pertencer ao 2o quadrante, 3 se pertencer ao 3o quadrante e 4 caso seja
do 4o quadrante. Para determinar a que quadrante o ponto pertence, e utilizado um conjunto
de if e else aninhados que verificam os sinais das coordenadas.

1 int qualQuadrante ( Tponto ponto ) {


2 if ( ponto . x == 0 || ponto . y == 0) {
3 return 0;
4 } else {
5 if ( ponto . x > 0) {
6 if ( ponto . y > 0) {
7 return 1;
8 } else {
9 return 4;
10 }
11 } else {
128 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

12 if ( ponto . y > 0) {
13 return 2;
14 } else {
15 return 3;
16 }
17 }
18 }

Exerccio Resolvido 4.8 - Operacao float distanciaPontos (Tponto a, Tponto b)

Defina um programa que calcule a distancia entre dois pontos, implemente e use operacoes
construtoras e destrutoras, implemente tambem a operacao float distanciaPontos (Tponto
a, Tponto b), de calculo de distancia entre dois pontos.

Solucao Possvel:
Inicialmente, sao definidas a estrutura Tponto e as operacoes aplicaveis a ela. Alem das
funcoes relacionadas ao problema, deve-se sempre definir as funcoes inicializadora e destrutora.
A funcao ler() inicializa o Tponto a partir de valores fornecidos pelo usuario, a funcao zera()
zera as coodernadas do ponto e a funcao distanciaPontos calcula a distancia entre pontos, dada
pela raiz quadrada da soma do quadrado da diferenca entre as abscissas e entre as ordenadas.
A funcao principal utiliza os metodos declarados de forma a corresponder a funcionalidade
desejada.

1 # include < stdio .h >


2
3 struct ponto {
4 int x , y ;
5 }
6
7 typedef struct ponto Tponto ;
8
9 // Operac~
a o construtora :
10 Tponto ler () {
11 Tponto ponto ;
12
13 printf ( " Entre com as coordenadas \ n " ) ;
14 printf ( " X : " ) ;
15 scanf ( " % d " , & ponto . x ) ;
16 printf ( " Y : " ) ;
17 scanf ( " % d " , & ponto . y ) ;
18
19 return ponto ;
20 }
21
22 // Operac~
a o destrutora :
23 Tponto zera () {
24 Tponto origem = {0 , 0};
4.5. RESUMO 129

25
26 return origem ;
27 }
28
29 // Operac~
a o Produtora :
30 float distanciaPontos ( Tponto a , Tponto b ) {
31 float distancia , deltaX , deltaY ;
32
33 deltaX = a . x - b . x ;
34 deltaY = a . y - b . y ;
35
36 distancia = sqrt ( deltaX ^2 + deltaY ^2) ;
37 return distancia ;
38 }
39
40 main () {
41 Tponto a , b ;
42 float distancia ;
43
44 a = ler () ;
45 b = ler () ;
46
47 distancia = distanciaPontos (a , b ) ;
48 printf ( " Distancia entre pontos : % f \ n " , distancia ) ;
49
50 a = zera () ;
51 b = zera () ;
52 }

4.5 Resumo
Inicialmente foram abordadas as seguintes abordagens na programacao:

bottom-up: que e uma tecnica que propoe considerar o programa como um conjunto
de modulos correlacionados, implementar cada um desses modulos e depois junta-los
por meio de uma estrutura global.
top-down: que e uma tecnica que proporciona que um programa seja visto como
uma descricao de um processo. O processo e dividido em subprogramas, os quais sao
responsaveis por cumprir partes da funcionalidade geral do processo. Por sua vez,
cada subprograma ainda pode ser dividido em novos subprogramas.

Utilizou-se a tecnica dividir para conquistarnas duas abordagens, onde cada abordagem
deve ser utilizada de acordo com as informacoes conhecidas do sistema. Aqui enfatizou-se
a abordagem bottom-up porque ela permite que o programador usuario nao se preocupe
com a implementacao de cada modulo, bastando saber a funcionalidade de cada modulo
para que os mesmos possam ser integrados.
130 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Uma estrutura agrupa varias variaveis numa so. Funciona como uma ficha pessoal que
tenha nome, telefone e endereco. A ficha seria uma estrutura. A estrutura, entao, serve
para agrupar um conjunto de dados nao similares, formando um novo tipo de dados.
Estas proveem uma grande facilidade para o armazenamento de tipos nao definidos pela
linguagem. O uso de estruturas e considerado uma otima pratica de programacao, visto
que, alem de permitirem uma organizacao melhor, possibilitam realizar atribuicoes ou
passagem de parametros em uma unica vez. Uma estrutura pode ser manipulada de forma
seletiva (acesso a um unico atributo) ou de forma integral (acesso a estrutura como um
todo).

Os TADS sao generalizacoes das estruturas, pois associamos varias operacoes sobre a
estrutura, estas podem ser dos seguintes tipos: construtoras, analisadoras, modificadoras,
produtoras e destrutoras. Assim, os TADS sao definidos como uma estrutura de dados e as
operacoes sobre os dados. A importancia de um TAD esta na transparencia que ele oferece,
o usuario do tipo nao precisa se preocupar em como o TAD e implementado. O uso de
TADs permite inumeras vantagens como abstracao, facilidade de alteracao, reutilizacao,
ocultamento e integridade. Os Tipos Abstratos de Dados sao classificados em TADs de
Domnio e TADs Implementacionais. TADs de domnio sao aqueles que descrevem um tipo
do problema, enquanto TADs Implementacionais descrevem uma estrutura que ira ajudar
a resolver o problema, mas nao se refere ao problema. Exemplos de TADs de domnio sao
os registros de alunos, funcionarios, datas, etc. Exemplos de TADs implementacionais sao
as pilhas, listas, etc.

Contudo, e importante dizer que a ideia do TAD e que o codigo da implementacao do tipo
de dado, fique invisvel para o programador que for usar o tipo. Assim, na verdade, C nao
implementa TAD, o que se faz e uma simulacao de TDAs. No entanto, a simulacao de
TADs em C se torna uma boa pratica de programacao, pois torna o codigo do programa
mais compreensvel e simplifica a definicao das estruturas de dados. Em linguagens que
permitam Programacao Orientada a Objetos (POO), os TADs podem ser implementados
com a confiabilidade que C nao permite.

4.6 Lista de Exerccios

Exerccio 4.1 - TAD tCilindro

Implemente um TAD para o armazenamento dos dados de um cilindro. A entrada dos dados
referentes ao cilindro sera realizada via console(teclado). As possveis operacoes sobre o tipo
sao:

leitura dos dados;


4.6. LISTA DE EXERCICIOS 131

Inicializa Cilindro;

Altera Altura;

Altera Raio;

Calcula Volume;

Imprime Dimensoes;

Imprime Volume;

Exerccio 4.2 - Determinando a Idade

Modifique o TAD tData incluindo uma operacoes que determine a idade de uma pessoa, e
quantos dias faltam para seu aniverario tendo como entradas a data de nascimento e a data
atual. Deve-se considerar anos bissextos e meses com o numero de dias conforme o calendario
ocidental.

Exerccio 4.3 - TAD tPonto

Implemente um TAD Ponto para representar um ponto no plano com as seguintes operacoes:

leitura: leitura via console;

desloca x: realiza um deslocamento horizontal;

desloca y: realiza um deslocamento vertical;

atribui x y: atribui novos valores as coordenadas de um ponto;

distancia: calcula a distancia entre dois pontos;

imprime: imprime as coordenadas do ponto.

Exerccio 4.4 - TAD tCirculo

Implemente um TAD Circulo para representar um crculo (Observe que o centro e um ponto)
com as seguintes operacoes:

leitura: leitura via console;


132 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

atribui raio: atribui um valor r ao raio do circulo;

atribui centro: atribui novo valor ao centro do circulo;

area: calcula a area do crculo;

interior: verifica se um dado ponto esta dentro do crculo.

Exerccio 4.5 - TAD tTrapezio

Implemente um TAD Trapezio para representar um trapezio com as seguintes operacoes:

leitura: funcao leitura para cada lado do trapezio;

atribuicao: atribui individualmente cada lado;

area: calcula a area do trapezio;

interior: verifica se um dado ponto esta dentro do trapezio.

Exerccio 4.6 - Imobiliaria

Voce foi contratado para fazer algumas pesquisas no cadastro de uma imobiliaria. Considere
que cada imovel possui um codigo de identificacao, uma area total e o preco por metro quadrado
do imovel. Implemente um programa que leia os dados dos imoveis, armazene e imprima o
imovel mais caro e o de maior area em um TAD imovel.

4.7 Trabalhos Sugeridos

Trabalho 4.1 - Estacionamento

Considere o sistema responsavel pelo controle de entrada e sada de veculos de um estaci-


onamento que funciona 24 h por dia. Um motorista ao entrar no estacionamento recebe um
ticket indicando o horario e a data de entrada, na sada do estacionamento o atendente realiza
a cobranca pelo uso do servico.
O preco do uso do estacionamento por hora e de R$ 1,00 nos dias uteis e R$ 2,00 em sabados,
domingos e feriados.
4.7. TRABALHOS SUGERIDOS 133

O sistema deve ler do ticket do usuario o horario e a data de entrada, deve identificar o
horario atual e a respectiva data, calcular o tempo de uso do estacionamento e o valor a ser
pago.
Caso o motorista permaneca no estacionamento por menos de 15 minutos ele nao devera
pagar pelo uso do estacionamento. Caso o motorista permaneca por menos de uma hora ele
devera pagar pelo preco de uma hora completa.
Implemente um TAD horario que armazene os horarios e as datas de entrada e sada para
cada veculo e que tenha operacoes como:

ler horario de entrada;

ler data de entrada;

obter horario do sistema;

obter data do sistema;

calculo de tempo no estacionamento;

calculo de custo;

determinacao do horario e data de sada para um determinado custo de uso do estaciona-


mento considerando um determinado horario de entrada.

Trabalho 4.2 - Banco

O gerente de uma agencia bancaria te contratou para fazer um programa em C para extrair
algumas informacoes das contas dos correntistas de sua agencia. Para tanto leia sucessivamente
os dados do numero da conta (um valor inteiro positivo) e valor da operacao (um valor ponto
flutuante positivo para as operacoes de credito e negativo para as operacoes de debito) realizadas
no ultimo mes. Seu programa deve usar os dados da listagem para informar:

1. As operacoes suspeitas, isto e, aquelas que movimentaram acima de 20.000 reais

2. Os dois correntistas que produziram o maior saldo nas suas operacoes.

3. O saldo total das operacoes na agencia .

Nos tens 1, 2, e 3 tambem devem ser apresentados os numeros das contas dos correntistas.
Considere que os dados de cada conta sao fornecidos contiguamente, isto e, sao fornecidos
todos os dados das operacoes de uma conta, depois todos os dados de outra conta e assim
sucessivamente. O processamento deve ser encerrado quando for lido um numero de conta igual
a zero.
134 CAPITULO 4. TIPOS ABSTRATOS DE DADOS

Trabalho 4.3 - NPD UFES

O diretor do NPD da UFES te contratou para fazer um programa em C para extrair algumas
informacoes a respeito dos cursos e dos alunos da UFES. Para tanto leia sucessivamente os dados
codigo do curso (um valor inteiro positivo), a matrcula do aluno (um valor inteiro positivo), a
carga horaria da disciplina (um valor inteiro positivo) e a media final obtida em uma disciplina
cursada pelo aluno (um valor ponto flutuante). Seu programa deve esses dados para informar:

1. O coeficiente de rendimento de cada aluno .

2. O melhor aluno de cada curso, isto e, o aluno de melhor coeficiente de rendimento no


curso.

3. O curso mais difcil, isto e, aquele que tem o menor coeficiente de rendimento medio.

4. Os tres alunos com maior numero de reprovacoes na UFES.

Nos tens 1 e 2 devem ser apresentados o curso, a matrcula e o coeficiente do aluno. No


item 3 devem ser apresentados o curso e o coeficiente de rendimento medio. No item 4, devem
ser apresentados o curso, a matrcula e o numero total de reprovacoes. Considere que os dados
de cada aluno e curso sao fornecidos contiguamente, isto e, sao fornecidos todos os dados das
disciplinas cursadas por um aluno em um curso, depois todos os dados de outro aluno deste
curso e assim sucessivamente ate terminar todos os dados de alunos do curso. Esse processo se
repete para todos os cursos da UFES. O processamento deve ser encerrado quando for lido um
codigo de curso igual a zero.
Captulo 5

Vetores
Autores:

Andrezinho
Thiago Paris Salviato

Objetivos:

Introduzir a estrutura de dados vetor e mostrar sua importancia;

Apresentar suas operacoes basicas e discutir os cuidados a serem tomados ao utiliza-las;

Definir um TAD Implementacional tVetorInt;

Apresentar aplicacoes para o TAD.

5.1 Vetores e sua importancia


Imagine que se queira fazer um programa para manusear dados de um mesmo tipo - as idades
das pessoas de uma famlia, ou os salarios dos funcionarios de uma empresa, por exemplo.
Uma alternativa seria a utilizacao de varias variaveis ou entao a leitura de valores sempre que
necessarios. Mas sera que essas seriam as melhores solucoes? E se o programa for grande e
haja necessidade de que ele seja modularizado? Nao ficaria ruim na hora de passar todas essas
variaveis como parametros? Ao se refletir sobre essas perguntas, outra ideia pode vir a tona:
a utilizacao de um TAD, ja que, dessa forma, as variaveis podem ser encapsuladas numa so
estrutura e apenas ela poderia ser passada como parametro! Boa ideia! Mas e se fossem 100
valores diferentes? Ou se houvesse a necessidade de mante-los ordenados, por exemplo? Como
ja se pode perceber, nem sempre os tipos basicos de dados (int, float, char) sao suficientes para
exprimir estruturas de dados em algoritmos.
Para ilustrar melhor o problema considere que um professor tenha uma turma de 50 alunos
e deseja saber quantos deles tiveram nota acima da media da turma. Com os instrumentos
apresentados ate o captulo anterior, seria necessaria a leitura das notas dos alunos por duas

135
136 CAPITULO 5. VETORES

vezes (ou a utilizacao de 50 variaveis - nao tente isso em casa!): uma para calcular a media e
outra para verificar quais alunos tiveram notas superiores a media. O Exemplo 5.1 mostra uma
maneira de resolver o problema do professor utilizando a Linguagem C, fazendo a dupla leitura
citada acima.

1 # include < stdio .h >


2
3 main () {
4 int i ;
5 int quant ;
6 float nota ;
7 float soma ;
8 float media ;
9
10 nota = 0.0;
11 soma = 0.0;
12 media = 0.0;
13
14 for ( i = 0; i < 50 ; i ++) {
15 printf ( " Digite uma nota " ) ;
16 scanf ( " % f " ,& nota ) ;
17 soma = soma + nota ;
18 }
19
20 media = soma /50;
21
22 quant = 0;
23
24 for ( i = 0; i < 50 ; i ++) {
25 printf ( " Digite uma nota \ n " ) ;
26 scanf ( " % f " ,& nota ) ;
27 if ( nota > media ) quant ++;
28 }
29
30 printf ( " \ n % d alunos obtiveram nota acima da media \ n " , quant ) ;
31 }

Exemplo 5.1: Resolucao do exemplo do professor sem a utilizacao de vetores

Todos os problemas citados ate agora no captulo poderiam ser resolvidos com a utilizacao
de vetores.
Um vetor e uma estrutura de dados composta homogenea, isto e, ele possui, em geral, mais
de um elemento - por isso, composta - sendo que estes elementos sao sempre de um mesmo tipo
- por isso, homogenea.
Resumindo, a utilizacao de um vetor se faz necessaria sempre que for preciso armazenar
uma quantidade finita de dados de um mesmo tipo, para ser manuseado durante a execucao do
programa. No Exemplo 5.1, por exemplo, as notas de todos os alunos da turma poderiam ter
sido armazenadas num unico vetor com apenas uma leitura e a partir de entao eles estariam
5.2. REPRESENTACAO 137

disponveis para serem utilizados durante todos os calculos.

5.2 Representacao
A Figura 5.1 mostra a maneira mais comum de se representar um vetor graficamente.

Figura 5.1: Representacao de um vetor

A Figura 5.1 representa um vetor de inteiros chamado vet com os nove primeiros numeros
primos, onde o terceiro elemento (elemento de ndice igual a 2) e o numero 5 e o de ndice 4 e o
numero 11, por exemplo. E importante lembrar que todo o primeiro elemento de um vetor na
linguagem C possui o ndice zero.
Analizando a Figura 5.1 fica evidente outras importantes caractersticas dessa estrutura: a
seqencializacao e a indexacao de seus elementos e a sua dimensao.
Quando se declara um vetor num programa, significa que durante a sua execucao sera re-
servado um espaco de memoria sequencial com o tamanho necessario para que seja possvel
armazenar todos os elementos do vetor. Portanto os elementos serao armazenados sequencial-
mente na memoria.
Essa propriedade facilita bastante o acesso do programa ao elemento desejado dentro do
vetor, pois, para que o dado seja recuperado, fica sendo necessario apenas o nome do vetor, que
localiza o primeiro elemento, e um ndice, que indicara ao programa a que distancia do incio
esta o dado desejado.
Mas para que seja feita a reserva do espaco de memoria, deve ser informado no codigo qual
o tamanho do vetor, isto e, o numero maximo de elementos que ele pode armazenar. A Figura
5.2 mostra uma ilustracao de como um vetor e armazenado na memoria do computador.
Dados os conceitos teoricos do vetor, chega a hora de discutir como essa estrutura pode ser
utilizada na pratica, abordando assuntos como a sua definicao, as operacoes definidas sobre ela
e outros. Isso sera feito a partir de agora.

5.3 Definicao
O primeiro passo para uma utilizacao clara e efetiva dos vetores e uma declaracao que deixe claro
para que a estrutura esta sendo usada. Isso ajuda a dar legibilidade ao codigo, logo, facilida a
implementacao.
Na linguagem C, o vetor e declarado da seguinte maneira:

<tipo_dados> <nome_vetor>[<tam_vetor>];
138 CAPITULO 5. VETORES

Figura 5.2: Vetores na memoria do computador

onde tipo dados representa o tipo dos dados dos elementos que serao armazenados no vetor,
nome vetor o nome pelo qual o vetor sera referenciado e tam vetor o numero de elementos que
cabem la dentro. O Exemplo 5.2 mostra alguns exemplos de declaracao de vetores.

1 int vet [9];


2
3 char nome [9];
4
5 float notas [6];
6
7 float notasAlunos [50];

Exemplo 5.2: Algumas declaracoes de vetores

No Exemplo 5.2, o primeiro vetor declarado pode ser representado graficamente pela Figura
5.1 mostrada na secao anterior, ou seja, seu nome e vet e ele pode armazenar ate nove elementos
5.3. DEFINICAO 139

do tipo int. O segundo e o terceiro foram os representados pela Figura 5.2. Aquele, chamado
nome, armazena no maximo 6 elementos do tipo char, e este, chamado notas, ate seis do tipo
float. Ja o ultimo poderia ser utilizado no problema do professor citado anteriormente, por
exemplo. Neste vetor, chamado notasAlunos, podem ser armazenados cinquenta valores do tipo
float, que poderiam representar as notas dos cinquenta alunos.

5.3.1 Definicao do Tamanho do Vetor

Na linguagem C, o tamanho do vetor pode ser definido em dois momentos diferentes: em


tempo de compilacao e em tempo de execucao; lembrando sempre que, em ambos os casos,
uma vez que ele for definido e que a memoria for alocada, o tamanho nao podera ser alterado.
Alguns fatores devem ser considerados na hora de escolher entre essas duas abordagens, como o
consumo de memoria e o tipo de aplicacao.

Definicao em tempo de compilacao

A definicao em tempo de compilacao e caracterizada pelo numero inteiro entre colchetes na


declaracao do vetor. Todas as declaracoes mostradas no Exemplo 5.2 sao exemplos de vetores
definidos em tempo de compilacao, antes da execucao do programa.
Entao, para armazenar dados nesse tipo de vetor, e necessario que se tenha conhecimento
previo da quantidade maxima de elementos que nele sera armazenado.
Suponha que se queira fazer um programa para cadastrar os alunos de uma escola de ensino
medio com cinco turmas por ano e fazer um acompanhamento de suas notas. Considere tambem
que cada aluno pode cursar, no maximo, oito materias por ano, duas delas optativas. O Exemplo
5.3 mostra uma estrutura que poderia ser utilizada para representar um aluno na linguagem C.

1 typedef struct aluno {


2 char nome [50];
3 int rg ;
4 int ano ;
5 char turma ;
6 float notas [8];
7 } tAluno ;

Exemplo 5.3: Definicao da Estrutura Aluno

A estrutura proposta no Exemplo 5.3 possui um vetor chamado nome, do tipo char e de
tamanho igual a 50 para armazenar o nome completo de um aluno; duas variaveis do tipo int
chamadas rg e ano, para armazenar, respectivamente, o numero do RG do aluno e seu ano
(serie); uma variavel do tipo char chamada turma, para armazenar sua turma; e um vetor do
tipo float chamado notas e de tamanho igual a 8, para armazenar as notas desse aluno nas
diferentes disciplinas que ele pode cursar.
140 CAPITULO 5. VETORES

Repare que as notas e o nome do aluno podem ocupar um espaco menor que o tamanho
maximo do vetor. Essa caracterstica dos vetores definidos em tempo de compilacao pode acar-
retar em desperdcio de memoria, pois os espacos armazenados para eles podem nao ser utilizados
totalmente.

Definicao em tempo de execucao

A definicao do tamanho em tempo de execucao, pode ser implementada substituindo o


numero inteiro que e colocado entre colchetes na declaracao do vetor por uma variavel que so
ganhara um valor durante a execucao do programa. O Exemplo 5.4 mostra como isso pode ser
feito na linguagem C.

1 # include < stdio .h >


2
3 main () {
4
5 int num ;
6
7 ...
8
9 printf ( " Quantas notas deseja armazenar no vetor ? " ) ;
10 scanf ( " % d " ,& num ) ;
11
12 float notas [ num ];
13
14 ...
15
16 }

Exemplo 5.4: Definicao de um vetor em tempo de execucao

Repare que declarando o vetor dessa forma, o desperdcio de memoria pode ser eliminado,
ja que o vetor e utilizado por completo, desde que se tenha conhecimento previo do numero de
elementos que serao armazenados e que se possa informa-lo a aplicacao.
E importante ressalvar que essa forma dinamica de definicao de tamanho nao funciona no
caso de vetores dentro de estruturas, como o vetor nota da estrutura tAluno do Exemplo 5.3,
por exemplo.

5.4 Operacoes
Na linguagem C nao existem operacoes pre-definidas para a manipulacao de um vetor como um
todo. Por exemplo, nao e permitida a leitura de um vetor por inteiro com o comando scanf. As
operacoes so podem ser feitas para cada elemento do vetor, individualmente.
5.4. OPERACOES 141

Como ja mencionado na Secao 5.2, para acessar um elemento de um vetor, sao necessarios
apenas seu nome e o ndice que informa sua localizacao. Na linguagem C a sintaxe de acesso a
um elemento de um vetor e dada por:

<nome_vetor> [<ndice>]

onde o ndice pode ser tanto um numero inteiro maior ou igual a zero quanto uma expressao
inteira composta por variaveis e numeros.
Sabendo acessar o elemento ele pode ser manipulado como uma variavel qualquer e ser
utilizado em operacoes das mais diversas como atribuicao, operacoes aritmeticas, etc. O Exemplo
5.5 mostra algumas dessas operacoes em Linguagem C.

1 # include < stdio .h >


2
3 main () {
4 int i ;
5 int vet1 [10];
6 int vet2 [10];
7 float notas [8];
8 float soma ;
9 float media ;
10
11 vet1 [0] = 43;
12
13 for ( i =0; i <5; i ++) {
14 vet1 [2* i +1] = 10;
15 }
16
17 for ( i =0; i <10; i ++) {
18 vet2 [ i ] = 0;
19 }
20
21 for ( i =0; i <8; i ++) {
22 scanf ( " % f " ,& notas [ i ]) ;
23 }
24
25 for ( i =0; i <10; i ++) {
26 vet2 [ i ] = vet1 [ i ];
27 }
28
29 for ( i =0; i <8; i ++) {
30 soma += notas [ i ];
31 }
32 media = soma /8;
33 }

Exemplo 5.5: Operacoes sobre elementos de um vetor


142 CAPITULO 5. VETORES

No Exemplo 5.5, primeiramente sao declarados a variavel i, do tipo int; os vetores vet1 e
vet2, ambos do tipo int e de tamanho igual a 10; o vetor notas do tipo float e de tamanho
igual a 8; e as variaveis soma e media. Em seguida, o primeiro elemento do vetor vet1, ou seja,
o elemento de ndice 0 (zero), recebe o valor 43 (linha 11); todos os elementos de vet1 de ndice
mpar recebem o valor 10 (linhas 13 a 15); todos os elementos de vet2 recebem o valor zero
(linhas 17 a 19); cada uma das 8 posicoes do vetor notas e preenchido por um valor lido do
teclado (linhas 21 a 23); os elementos do vetor vet1 sao copiados para vet2 (linhas 25 a 27); e,
por ultimo, e calculada a media dos valores contidos no vetor notas e armazenada na variavel
media (linhas 29 a 32).
E importante ressaltar a grande importancia da utilizacao de expressoes como ndice, pois
ela e essencial para o acesso rapido a todos os elementos do vetor.
Um outro exemplo interessante para ilustrar as operacoes sobre os vetores e a resolucao do
problema do professor citado na secao 5.1. Ja foi proposta uma solucao fazendo uma dupla
leitura das notas dos alunos (Exemplo 5.1). O Exemplo 5.6 mostra uma solucao utilizando
vetores.

1 # include < stdio .h >


2
3 main () {
4 int i ;
5 int quant ;
6 float notas [50];
7 float soma ;
8 float media ;
9
10 nota = 0.0;
11 soma = 0.0;
12 media = 0.0;
13
14 for ( i = 0; i < 50 ; i ++) {
15 printf ( " Digite uma nota " ) ;
16 scanf ( " % f " ,& notas [ i ]) ;
17 soma = soma + nota [ i ];
18 }
19
20 media = soma /50;
21
22 quant = 0;
23
24 for ( i = 0; i < 50 ; i ++) {
25 if ( notas [ i ] > media ) quant ++;
26 }
27
28 printf ( " \ n % d alunos obtiveram nota acima da media \ n " , quant ) ;
29 }

Exemplo 5.6: Resolucao do exemplo do professor com a utilizacao de vetores


5.4. OPERACOES 143

Repare que, dessa vez, os valores referentes as notas sao lidos do teclado apenas uma vez e
sao, entao, armazenados no vetor notas e imediatamente somados para o calculo da media da
turma, valor este armazenado na variavel media. Para a contagem de alunos com nota acima da
media, nao e necessaria uma nova leitura do teclado, apenas uma consulta ao vetor notas que
ja possui todos os valores armazenados.
Os vetores tambem podem ser passados como parametro de funcoes, mas nunca como retorno.
O Exemplo 5.7 mostra um codigo em linguagem C que resolve o problema anterior, mas com a
utilizacao de funcoes que recebem um vetor como parametro.

1 # include < stdio .h >


2
3 float calculaMedia ( float vetor [50]) {
4 int i ;
5 float soma ;
6 float media ;
7
8 soma = 0.0;
9
10 for ( i = 0 ; i < 50 ; i ++)
11 soma = soma + vetor [ i ];
12 }
13
14 media = soma /50;
15
16 return ( media ) ;
17 }
18
19 int alunosAcimaMedia ( float vetor [50] , float media ) {
20 int i ;
21 int quant ;
22
23 quant = 0;
24 for ( i = 0 ; i < 50 ; i ++)
25 if ( vetor [ i ] > media ) quant ++;
26 }
27
28 return ( quant ) ;
29 }
30
31 main () {
32 int i ;
33 int quant ;
34 float notas [50];
35 float soma ;
36 float media ;
37
38 nota = 0.0;
39 soma = 0.0;
40 media = 0.0;
144 CAPITULO 5. VETORES

41
42 for ( i = 0 ; i < 50 ; i ++) {
43 printf ( " Digite uma nota " ) ;
44 scanf ( " % f " ,& notas [ i ]) ;
45 }
46
47 media = calculaMedia { notas };
48
49 quant = alunosAcimaMedia ( notas , media ) ;
50
51 printf ( " \ n % d alunos obtiveram nota acima da media \ n " , quant ) ;
52 }

Exemplo 5.7: Passagem de vetores para funcao

Algumas caractersticas da linguagem C tornam a utilizacao de vetores passados como


parametro perigosa quando nao tomados os devidos cuidados. Por isso essa tecnica nao sera
usada agora.

5.4.1 Acesso indevido


A Figura 5.2 da secao 5.2 tambem pode ser utilizada para ilustrar um exemplo de um problema
que ocorre com certa frequencia em programas na linguagem C, ja que ela nao faz nenhuma
restricao ao acesso de elementos fora dos limites do vetor. Portanto, se, por acidente, um
programador tentasse acessar em seu programa o ndice 14 do vetor nome representado na
figura, por exemplo, nenhum erro seria acusado em tempo de compilacao, ou de execucao. O
erro so poderia ser percebido em tempo de execucao, quando o computador retornasse um valor
inesperado no programa.
Mas isso e o mnimo que pode acontecer. Se o programador utilizasse um ndice diferente,
ele poderia acessar e apagar alguma faixa de memoria de outro programa e acabar causando
danos irreversveis. Logo, a solucao mais adequada e sempre avaliar os limites de um vetor antes
de manipula-lo.

5.5 Strings
O vetor de elementos do tipo char do Exemplo 5.2, chamado nome, assim como todos os vetores
do tipo char, podem ser considerados de um novo tipo, o tipo string. Strings correspondem a
uma sequencia de caracteres. Geralmente, sao usadas para realizar a entrada e sada de dados
em um programa e para armazenar dados nao numericos.
E comum representar uma string como um vetor de caracteres (tipo char), dispondo das
mesmas operacoes comuns a essa estrutura de dados. A Figura 5.3 exemplifica a representacao
de uma string como um vetor de caracteres.
Toda string armazenada em um vetor e terminada por um caracter especial, conhecido como
caracter nulo \0 (barra-zero). A principal funcao desse caracter e alertar o fim de uma string,
5.5. STRINGS 145

Figura 5.3: Vetor de caracteres.

evitando um acesso indesejado a uma posicao do vetor que se encontra vazia. O programa
do Exemplo 5.8 demonstra uma forma nao usual de imprimir strings, mas aplica o conceito de
caracter nulo.

1 void imprime_string ( char palavra []) {


2 int i ;
3
4 for ( i =0; palavra [ i ] != \0 ; i ++) {
5 printf ("% c " , palavra [ i ]) ;
6 }
7 printf ("\ n ") ;
8 }

Exemplo 5.8: Subprograma para impressao de strings, utilizando diretamente o barra-zero.

No Exemplo 5.8, a condicao de parada do laco for e encontrar o caracter \0, e, durante cada
iteracao, cada caracter do vetor e impresso separadamente. A forma mais comum (e simples)
de imprimir uma string e a apresentada no Exemplo 5.9.

1 void imprime_string ( char palavra []) {


2
3 printf ( " % s \ n " , palavra ) ;
4 }

Exemplo 5.9: Impressao de uma string sem a utilizacao direta do caracter barra-zero.

O programa do Exemplo 5.9 produz o mesmo resultado do Exemplo 5.8, porem, naquele o
caracter \0 e verificado implicitamente pela funcao printf.
A biblioteca padrao de C ainda oferece algumas funcoes muito uteis para a manipulacao de
strings, como a strcpy (copia), a strlen (tamanho) e a strcmp (comparacao). O Exemplo 5.10
demonstra o uso dessas funcoes.

1 # include < stdio .h >


2 # include < string .h >
3
4 main () {
5 char vet1 [20] = " ola enfermeira " ;
6 char vet2 [20];
7 char vet3 [20];
8
9 printf ( " Tamanho da string : % d \ n \ n " , strlen ( vet1 ) ) ;
10
146 CAPITULO 5. VETORES

11 strcpy ( vet2 , vet1 ) ;


12 printf ( " % s \ n % s \ n \ n " , vet1 , vet2 ) ;
13
14 strcpy ( vet3 , " sim senhor " ) ;
15 printf ( " % s \ n \ n " , vet3 ) ;
16
17 if ( strcmp ( vet1 , vet2 ) == 0) {
18 printf ( " As strings sao iguais " ) ;
19 } else {
20 printf ( " As strings sao diferentes " ) ;
21 }
22
23 if ( strcmp ( vet1 , vet3 ) == 0) {
24 printf ( " As strings sao iguais " ) ;
25 } else {
26 printf ( " As strings sao diferentes " ) ;
27 }
28 }

Exemplo 5.10: Programa demonstrativo de algumas funcoes da biblioteca padrao de C para


manipulacao de strings.

A primeira linha dentro da funcao main mostra uma maneira de inicializar um vetor, no
caso do exemplo o vetor chamado vet1, e ja preenche-lo com valores, no mesmo caso com o texto
ola enfermeira (linha 5). Em seguida sao declarados mais dois vetores de caracteres: vet1 e
vet2, ambos de tamanho igual a 20. Depois disso as funcoes da biblioteca padrao string.h sao
utilizadas para, respectivamente: imprimir o tamanho da string em vet1, com a funcao strlen
(linha 9); copiar o conteudo de vet1 em vet2, com a funcao strcpy e em seguida imprim-los na
tela com a funcao printf da biblioteca stdio.h (linhas 11 e 12); copiar a string sim senhor para
dentro do vetor vet3 e em seguida imprimir seu conteudo na tela (linhas 14 e 15); e, finalmente,
comparar o conteudo das strings contidas em vet1 e vet2 (linhas 17 a 21), e vet1 e vet3 (linhas
23 a 27) com a funcao strcmp.
Para o uso da funcao strcpy, a ordem dos parametros e de suma importancia; o primeiro
parametro e o vetor de destino da string que se deseja copiar, enquanto o segundo e o vetor de
origem da string. Em relacao a funcao strcmp, ela retorna o valor zero se as strings armazenadas
em ambos os vetores forem iguais (letras em caixa alta sao consideradas diferentes das em caixa
baixa).

5.6 TAD Implementacional


Como dito na secao 5.4, na linguagem C nao existem operacoes basicas para a manipulacao
de vetores como um todo. Porem a manipulacao individual de todos os seus elementos pode
ser muito trabalhosa e muitas vezes operacoes comuns devem ser repetidas ao longo do codigo
do programa comprometendo sua legibilidade, confiabilidade e modificabilidade. Por isso, as
operacoes basicas sobre um vetor devem ser modularizadas pela utilizacao de subprogramas e,
5.6. TAD IMPLEMENTACIONAL 147

para isso, a utilizacao de um TAD se mostra oportuna.


Nessa secao sera apresentado na linguagem C um TAD de natureza implementacional, cha-
mado tVetorInt, que podera ser usado em aplicacoes que necessitam manipular vetores de intei-
ros. Para isso sera definida uma estrutura contendo um vetor e o numero de elementos correntes
do vetor.

5.6.1 Atributos

O Exemplo 5.11 mostra os atributos da estrutura utilizada no TAD Implementacional tVetorInt.

1 # define TAM 100


2
3 typedef struct vetor {
4 int vet [ TAM ];
5 int n ;
6 } tVetorInt ;

Exemplo 5.11: Definicao da Estrutura tVetorInt

O Exemplo 5.11 mostra a definicao da estrutura tVetorInt. Ela possui um vetor de inteiros
chamado vet, de tamanho definido por TAM e um atributo n do tipo int que representa o
numero corrente de elementos em vet, ou seja, o numero de elementos armazenados no vetor
num determinado momento da execucao do programa. Esse valor deve ser zero quando o vetor
estiver vazio e deve ser incrementado ou decrementado sempre que se adicionar ou excluir um
elemento do vetor, respectiviamente.
A primeira linha do codigo do Exemplo 5.11 faz com que toda a palavra TAM existente
no codigo do programa seja substituda pelo valor 100 na hora da compilacao. Esse artifcio e
bastante utilizado quando se trabalha com vetores com tamanho maximo, ja que permite que
uma alteracao no valor de TAM no incio do codigo modifique os tamanhos de todos os vetores
do codigo que possuem TAM como definicao de tamanho.
A Figura 5.4 mostra uma maneira simples de representar graficamente a estrutura proposta
no Exemplo 5.11.
A figura mostra duas estruturas do tipo. Como pode ser observado, apesar de vet possuir
tamanho igual a 100, ele possui apenas 9 elementos na primeira estrutura e 5 na segunda, valores
representados pelo atributo n.

5.6.2 Operacoes

Agora serao ilustradas as funcoes que devem ser utilizadas como operacoes para manipular a
estrutura tVetorInt. Tal como visto no captulo anterior, essas operacoes se dividem em quatro
categorias diferentes: contrutoras, analizadoras, produtoras e modificadoras.
148 CAPITULO 5. VETORES

Figura 5.4: Representacao grafica da estrutura tVetorInt.

Construtoras
Para o caso do TAD tVetorInt, podem ser criadas duas funcoes dessa natureza, uma que inicializa
a estrutura com um vetor vazio e outra que a inicializa ja lendo para dentro do vetor um
determinado numero de elementos ignorando o que havia antes. Para efeito de comparacao,
essas duas funcoes sao semelhantes a operacao = utilizada sobre as variaveis dos tipos
primitivos (int, float, char, etc). Uma dessas duas funcoes deve ser utilizada antes de qualquer
outra operacao sobre a estrutura.
A funcao do Exemplo 5.12 e uma maneira de se implementar uma funcao que inicializa a
estrutura tVetorInt sem nunhum elemento dentro do vetor.

1 tVetorInt iniciaVazioVetorInt ( tVetorInt v ) {


2 v . n = 0;
3 return ( v ) ;
4 }

Exemplo 5.12: Inicializacao de um tVetorInt vazio

Repare que a funcao tem como parametro de entrada uma estrutura do tipo tVetorInt, ou
seja, antes de ser utilizada, uma variavel do tipo deve ser declarada no corpo principal do
programa, a funcao main da linguagem C.
O Exemplo 5.13 mostra o codigo da outra funcao construtora citada.

1 tVetorInt leituraVetorInt ( tVetorInt v , int num ) {


2 int i ;
3 for ( i = 0 ; i < num ; i ++) scanf ( " % d " ,& v . vet [ i ]) ;
4 v . n = num ;
5 return ( v ) ;
6 }

Exemplo 5.13: Inicializacao de um tVetorInt com leitura de elementos


5.6. TAD IMPLEMENTACIONAL 149

A funcao do Exemplo 5.13 recebe como parametro de entrada a estrutura do tipo tVetorInt
que se quer inicializar e o numero de elementos que se quer ler para dentro do vetor. Ela, entao,
le do teclado os elementos desejados e armazena-os dentro do vetor da estrutura.

Analisadoras
As operacoes analizadoras sao utilizadas para que se possa obter informacoes relacionadas com
os valores do TAD. Serao mostradas aqui tres funcoes desse tipo.
A primeira funcao, que pode ser visualizada no Exemplo 5.14 e para que possam ser visua-
lizados em tela os valores de todos os elementos armazenados no vetor da estrutura.

1 void escritaVetorInt ( tVetorInt v ) {


2 int i ;
3
4 printf ( " \ n \ n " ) ;
5 printf ( " Os elementos que estao atualmente no vetor sao : " ) ;
6 printf ( " \ n " ) ;
7 for ( i = 0 ; i < v . n ; i ++) printf ( " % d " ,v . vet [ i ]) ;
8 printf ( " \ n \ n " ) ;
9 }

Exemplo 5.14: Escrita em tela

A funcao simplesmente acessa todos os elementos do vetor e imprime seus valores na tela
(linha 7).
As outras duas funcoes citadas como analizadoras sao ambas utilizadas para determinar a
existencia de um determinado elemento dentro do vetor. Essas funcoes, alem de funcionarem
como uma simples consulta dentro de um vetor, podem ser utilizadas dentro de outras funcoes
como, por exemplo, para achar um determinado elemento que se queira apagar.
A funcao do Exemplo 5.15 varre o vetor sequencialmente a procura de um elemento e retorna
seu ndice se encontra-lo. Caso contrario, ela retorna -1.

1 int p e s q u isaSe quen cial Int ( tVetorInt v , int elem ) {


2 int i ;
3
4 for ( i = 0 ; i < v . n ; i ++) {
5 if ( v . vet [ i ]== elem ) return ( i ) ;
6 }
7 return ( -1) ;
8 }

Exemplo 5.15: Pesquisa Sequencial

A funcao do Exemplo 5.15 recebe como parametro de entrada a estrutura do tipo tVetorInt
que possui o vetor que se deseja pesquisar e o elemento a ser encontrado. Ela, entao, compara,
a partir do primeiro, o valor de todos os elementos com o valor procurado. Se achar um igual,
150 CAPITULO 5. VETORES

retorna o valor de seu ncide. Se chegar ao final do vetor e nao tiver encontrado nenhum valor
igual ao que se procura, a funcao retorna o valor -1.
Repare que se existir no vetor dois elementos iguais, a funcao obtem apenas o ndice do
primeiro. Isso acontece, porque ela retorna quando o primeiro valor e encontrado. Outra carac-
terstica dessa funcao que deve ser evidenciada e sua ineficiencia quando o valor procurado esta
nas ultimas posicoes ou quando ele nao existe no vetor.
Para resolver o problema da ineficiencia citada, outras funcoes foram desenvolvidas para
otimizar o processo de localizacao de elementos. O Exemplo 5.16 mostra uma delas: a Pesquisa
Binaria. Essa funcao deve ser utilizada apenas em vetores ordenados de forma crescente. Ela
percorre o vetor partindo-o ao meio a procura do elemento e retorna seu ndice se encontra-lo e
-1 caso contrario.

1 int pe sq uisaBinariaInt ( tVetorInt v , int elem ) {


2 int inicio ;
3 int fim ;
4 int meio ;
5
6 inicio = 0;
7 fim = v . n - 1;
8
9 while ( inicio <= fim ) {
10 meio = ( inicio + fim ) /2;
11 if ( elem < v . vet [ meio ]) fim = meio - 1;
12 else if ( elem > v . vet [ meio ]) inicio = meio + 1;
13 else return ( meio ) ;
14 }
15 return ( -1) ;
16 }

Exemplo 5.16: Pesquisa Binaria

A funcao do Exemplo 5.16 recebe como parametro de entrada a estrutura do tipo tVetorInt
que possui o vetor que se deseja pesquisar e o elemento a ser encontrado. Sao declaradas,
entao, tres variaveis: inicio, fim e meio. A variavel inicio recebe, o valor do ndice do menor
elemento do vetor, ou seja, zero, e fim recebe o valor do ndice do maior elemento, ou seja,
v.n 1. Enquanto o valor de inicio for menor ou igual ao valor de fim, meio recebe o ndice do
elemento central da regiao por eles delimitada. E exatamente esse elemento central que sempre
sera comparado ao elemento que se esta procurando. Se aquele for maior que este, fim recebe o
valor do ndice anterior a meio. Caso contrario, inicio recebe o valor do ndice posterior a meio.
Se nenhuma das duas alternativa acontecerem, quer dizer que o elemento foi encontrado e seu
ndice e igual a meio. Se em algum momento inicio passar a ser maior que fim, o elemento nao
existe na lista e a funcao retorna -1.
As Figuras 5.5 e 5.6 mostram o funcionamento do algoritmo do Exemplo 5.16 para duas
pesquisas diferentes.
A Figura 5.5 ilustra as etapas da busca do elemento 4 dentro do vetor vet da estrutura
5.6. TAD IMPLEMENTACIONAL 151

Figura 5.5: Etapas da pesquisa binaria com o elemento procurado no incio do vetor.

do tipo tVetorInt mostrada na figura. Primeiramente inicio e fim adquirem os valores 0 e 10,
respectivamente. Como inicio e menor que fim, meio recebe o ndice 5, e o elemento apontado
por ele, o valor 10, e comparado ao valor procurado. Como 10 e maior que 4, fim recebe o ndice
4. Como inicio continua menor que fim, meio recebe o ndice 2. Dessa vez, o elemento apontado
por meio e igual ao elemento procurado. A funcao retorna, entao, o valor de seu ndice: 2.
Ja a Figura 5.6 mostra a busca do elemento 23 dentro do mesmo vetor apresentado na Figura
5.5. Primeiramente inicio e fim adquirem os valores 0 e 10, respectivamente. Como inicio e
menor que fim, meio recebe o ndice 5, e o elemento apontado por ele, o valor 10, e comparado
ao valor procurado. Como 10 e menor que 23, inicio recebe o ndice 6. Como inicio continua
menor que fim, meio recebe o ndice 8, e o elemento apontado por ele, o valor 19, e comparado
ao valor procurado. Novamente 19 e menor que 23 e inicio recebe o ndice 9. Como inicio
continua menor que fim, meio recebe o ndice 9 tambem. Dessa vez, o elemento apontado por
meio e igual ao elemento procurado. A funcao retorna, entao, o valor de seu ndice: 9.
E importante observar que, para esta ultima busca, o algoritmo de pesquisa binaria faz
apenas tres comparacoes enquanto que o algoritmo de pesquisa sequencial teria de fazer dez.

Produtoras
As funcoes dessa natureza geram algum tipo de produto como resultado de operacoes feitas
sobre dois ou mais vetores. Dentre elas podem ser citadas a Adicao e o Produto Escalar.
A Adicao, Exemplo 5.17, soma cada elemento de mesmo ndice de um vetor com outro
com o mesmo numero de elementos correntes e armazena o resultado em um terceiro vetor. Se
os vetores a serem somados nao possuirem o mesmo numero de elementos correntes, a funcao
retorna uma mensagem de erro.
152 CAPITULO 5. VETORES

Figura 5.6: Etapas da pesquisa binaria com o elemento procurado no final do vetor.

1 tVetorInt somaVetorInt ( tVetorInt v1 , tVetorInt v2 , tVetorInt soma ) {


2 int i ;
3
4 if ( v1 . n != v2 . n ) {
5 printf ( " \ n \ n " ) ;
6 printf ( " Vetores com tamanhos diferentes " ) ;
7 printf ( " \ n \ n " ) ;
8 soma . n = 0;
9 } else {
10 soma . n = v1 . n ;
11 for ( i = 0 ; i < soma . n ; i ++) {
12 soma . vet [ i ] = v1 . vet [ i ] + v2 . vet [ i ];
13 }
14 }
15 return ( soma ) ;
16 }

Exemplo 5.17: Adicao de dois vetores

A funcao somaVetorInt recebe como parametros de entrada tres estruturas do tipo tVetorInt.
Duas delas, v1 e v2, devem possuir os vetores que se deseja somar, e a outra, soma, o vetor onde
a soma sera armazenada. Primeiramente verifica-se se os vetores de v1 e v2 possuem o mesmo
numero de elementos correntes. Se nao possurem, uma mensagem de erro e exibida na tela.
5.6. TAD IMPLEMENTACIONAL 153

Caso contrario, cada elemento de mesmo ndice de v1 e v2 sao somados e armazenados com o
mesmo ndice no vetor de soma e este e retornado pela funcao.
Ja a funcao Produto Escalar, cujo codigo pode ser encontrado no Exemplo 5.18 calcula, como
o proprio nome ja diz, o produto escalar entre dois vetores. Para isso, os vetores devem possuir o
mesmo numero de elementos correntes. Caso contrario a funcao tambem retorna uma mensagem
de erro.

1 int escalaVetorInt ( tVetorInt v1 , tVetorInt v2 ) {


2 int i ;
3 int prod ;
4
5 prod = 0;
6 if ( v1 . n != v2 . n ) {
7 printf ( " \ n \ n " ) ;
8 printf ( " Vetores com tamanhos diferentes " ) ;
9 printf ( " \ n \ n " ) ;
10 } else {
11 for ( i = 0 ; i < v1 . n ; i ++) {
12 prod = prod + v1 . vet [ i ] * v2 . vet [ i ];
13 }
14 }
15 return ( prod ) ;
16 }

Exemplo 5.18: Produto Escalar

A funcao Produto Escalar recebe como parametros de entrada duas estruturas tVetorInt:
v1 e v2. Primeiro o numero de elementos correntes de seus vetores sao verificados. Caso seja
diferente, uma mensagem de erro e exibida na tela. Se forem iguais, o produto escalar de seus
vetores, que vem a ser a soma dos produtos dos elementos de mesmo ndice, sao calculados.

Modificadoras
As operacoes a seguir sao responsaveis por realizar alteracoes no vetor do TAD de forma a
garantir a coerencia da estrutura. Para excluir um elemento no meio de um vetor, por exemplo,
a funcao de exclusao deve, alem de apaga-lo, cuidar para que o numero de elementos correntes
do vetor seja atualizado e que todos os elementos com ndice maior que o dele sejam transferidos
uma posicao para a esquerda. Alem da operacao de exclusao, serao apresentadas a seguir funcoes
de insercao e ordenacao.
A funcao do Exemplo 5.19 mostra um algoritmo para insercao de um elemento numa posicao
pre-determinada. Ela insere o elemento na posicao desejada considerando a posicao zero como
a primeira do vetor.

1 tVetorInt insereVetorInt ( tVetorInt v , int elem , int pos ) {


2 int i ;
3
154 CAPITULO 5. VETORES

4 if (( pos < 0) || ( pos > v . n ) ) {


5 printf ( " \ n \ n " ) ;
6 printf ( " Entrada de Dados Incorreta " ) ;
7 printf ( " \ n \ n " ) ;
8 } else {
9 v . n ++;
10 for ( i = v . n - 2 ; i > pos - 1 ; i - -) {
11 v . vet [ i +1] = v . vet [ i ];
12 }
13 v . vet [ i ] = elem ;
14 }
15 return ( v ) ;
16 }

Exemplo 5.19: Insercao em Posicao Pre-determinada

A funcao insereVetorInt recebe como parametros de entrada uma estrutura tVetorInt, o


elemento a ser inserido e a posicao em que ele deve ser inserido. Se a posicao for invalida, ou
seja, menor que 0 ou maior que v.n, uma mensagem de erro e impressa na tela e o elemento nao
e inserido. Caso contrario os elementos de dentro do vetor vao sendo movidos para a direita de
modo a deixar a posicao desejada para a insercao livre. Finalmente, o vetor recebe o valor do
elemento na posicao desejada e a estrutura e retornada.
A funcao do Exemplo 5.20 mostra um algoritmo de exclusao de um elemento. Ela percorre
o vetor da estrutura sequencialmente procurando o elemento a ser excludo. Se encontra-lo,
retira-o e continua procurando outros iguais para excluir ate nao achar mais.

1 tVetorInt excluiVetorInt ( tVetorInt v , int elem ) {


2 int i ;
3 int pos ;
4 int quant ;
5
6 pos = 0;
7 quant = 0;
8 while ( pos < v . n ) {
9 if ( v . vet [ pos ] == elem ) {
10 for ( i = pos ; i < v . n - 1 ; i ++) {
11 v . vet [ i ] = v . vet [ i +1];
12 }
13 v .n - -;
14 }
15 pos ++;
16 }
17 return ( v ) ;
18 }

Exemplo 5.20: Exclusao de Elemento

A funcao excluiVetorInt recebe como parametros de entrada uma estrutura tVetorInt e o


elemento que se quer excluir. Ela, entao, varre o vetor da estrutura da primeira a ultima posicao
5.6. TAD IMPLEMENTACIONAL 155

comparando os elementos nele contidos com o valor do elemento a ser excludo. Se for igual, ele
move todos os elementos seguintes para a esquerda apagando, assim, o elemento alvo. Depois
disso ele continua a procura por outros elementos a que tambem devem ser excludos. Quando
chega ao final do vetor, ela retorna a estrutura modificada.
As duas proximas funcoes a serem apresentadas sao de ordenacao. Ambas colocam em ordem
crescente os elementos de um vetor, mas utilizando metodos diferentes. A funcao do Exemplo
5.21 mostra a ordenacao pelo metodo do menor.

1 tVetorInt ordenaMenorInt ( tVetorInt v ) {


2 int pos ;
3 int i ;
4 int indice_menor ;
5 int menor ;
6
7 for ( pos = 0 ; pos < v . n - 1 ; pos ++) {
8 menor = v . vet [ pos ];
9 for ( i = pos + 1 ; i < v . n ; i ++) {
10 if ( v . vet [ i ] < menor ) {
11 menor = v . vet [ i ];
12 indice_menor = i ;
13 }
14 }
15 v . vet [ indice_menor ] = v . vet [ pos ];
16 v . vet [ pos ] = menor ;
17 }
18 return ( v ) ;
19 }

Exemplo 5.21: Ordenacao pelo metodo do menor

A funcao ordenaMenosInt recebe a estrutura tVetorInt cujo vetor se deseja ordenar. Pri-
meiramente ela varre o vetor procurando o elemento de menor valor e o troca de posicao com
o primeiro elemente do vetor. Feito isso, ela procura o menor elemento no restante do vetor e
o troca de posicao com o segundo elemento e assim sucessivamente com o terceiro, quarto, . . .,
v.n 1 elementos. A Figura 5.7 ilustra o funcionamento desse algoritmo.
Ja a funcao ordenaBolhaInt, Exemplo 5.22, utiliza o metodo da bolha.

1 tVetorInt ordenaBolhaInt ( tVetorInt v ) {


2 int a ;
3 int b ;
4 int temp ;
5
6 for ( a = 1 ; a < v . n ; a ++) {
7 for ( b = v . n - 1 ; b >= a ; --b ) {
8 if ( v . vet [b -1] > v . vet [ b ]) {
9 temp = v . vet [b -1];
10 v . vet [b -1] = v . vet [ b ];
156 CAPITULO 5. VETORES

11 v . vet [ b ] = temp ;
12 }
13 }
14 }
15 return ( v ) ;
16 }

Exemplo 5.22: Ordenacao pelo metodo da bolha

A funcao ordenaBolhaInt tambem recebe como parametro de entrada apenas a estrutura


tVetorInt. Entao ela percorre o vetor inteiro, do maior para o menor ndice, fazendo comparacoes
entre seus elementos e trocando-os quando o elemento de maior ndice possui seu valor menor
que o elemento de menor ndice. Feito isso uma vez, o elemento de menor valor ja fica na
primeira posicao do vetor. Entao o algoritmo ignora esse elemento e repete o procedimento para
o resto do vetor. Esse procedimento e repetido v.n 1 vezes. Finalmente, a funcao retorna a
estrutura com o vetor ordenado. A Figura 5.8 ilustra o funcionamento desse algoritmo.

Exemplo de Utilizacao do TAD tVetorInt

Sera apresentado agora um exemplo de utilizacao do TAD tVetorInt. Vale lembrar que o Exemplo
5.23 apresenta apenas o programa principal contendo a chamada das funcoes definidas ate aqui.
Para seu funcionamento correto, todas as funcoes dever estar contidas no arquivo do programa.

1 int main () {
2 tVetorInt vet1 ;
3 tVetorInt vet2 ;
4 tVetorInt vet3 ;
5 int elem ;
6 int pos ;
7 int prodEscalar ;
8 int enter ;
9
10 vet1 = leituraVetorInt ( vet1 ,10) ;
11 vet2 = leituraVetorInt ( vet2 ,10) ;
12 vet3 = in iciaVazioVetorInt ( vet3 ) ;
13
14 printf ( " \ n \ n " ) ;
15 printf ( " Vet1 : " ) ;
16 escritaVetorInt ( vet1 ) ;
17 printf ( " Vet2 : " ) ;
18 escritaVetorInt ( vet2 ) ;
19
20 printf ( " \ n \ nQual elemento deseja localizar utilizando a pesquisa sequencial ?\ n \ n
");
21 scanf ( " % d " ,& elem ) ;
22
23 pos = p e s qu isaSe quen cialI nt ( vet1 , elem ) ;
24 if ( pos >= 0) printf ( " \ n \ nElemento encontrado na posicao % d \ n \ n " , pos ) ;
5.6. TAD IMPLEMENTACIONAL 157

25 else printf ( " \ n \ nElemento nao encontrado \ n \ n " ) ;


26
27 printf ( " \ n \ nQual elemento deseja localizar utilizando a pesquisa binaria ?\ n \ n " ) ;
28 scanf ( " % d " ,& elem ) ;
29
30 pos = pe squisaBinariaInt ( vet1 ,3) ;
31 if ( pos >= 0) printf ( " \ n \ nElemento encontrado na posicao % d \ n \ n " , pos ) ;
32 else printf ( " \ n \ nElemento nao encontrado \ n \ n " ) ;
33
34 vet3 = somaVetorInt ( vet1 , vet2 , vet3 ) ;
35
36 printf ( " \ n \ n " ) ;
37 printf ( " Vet1 : " ) ;
38 escritaVetorInt ( vet1 ) ;
39 printf ( " Vet2 : " ) ;
40 escritaVetorInt ( vet2 ) ;
41 printf ( " Vet3 : " ) ;
42 escritaVetorInt ( vet3 ) ;
43
44 prodEscalar = escalaVetorInt ( vet1 , vet2 ) ;
45
46 printf ( " \ n \ n " ) ;
47 printf ( " Vet1 : " ) ;
48 escritaVetorInt ( vet1 ) ;
49 printf ( " Vet2 : " ) ;
50 escritaVetorInt ( vet2 ) ;
51
52 printf ( " \ n \ nProduto escalar = % d \ n \ n " , prodEscalar ) ;
53
54 vet3 = leituraVetorInt ( vet3 ,0) ;
55
56 vet3 = insereVetorInt ( vet3 ,5 ,0) ;
57 printf ( " Vet3 : " ) ;
58 escritaVetorInt ( vet3 ) ;
59
60 vet3 = insereVetorInt ( vet3 ,3 ,0) ;
61 printf ( " Vet3 : " ) ;
62 escritaVetorInt ( vet3 ) ;
63
64 vet3 = insereVetorInt ( vet3 ,7 ,1) ;
65 printf ( " Vet3 : " ) ;
66 escritaVetorInt ( vet3 ) ;
67
68 vet3 = insereVetorInt ( vet3 ,10 ,3) ;
69 printf ( " Vet3 : " ) ;
70 escritaVetorInt ( vet3 ) ;
71
72 vet3 = excluiVetorInt ( vet3 ,3) ;
73 printf ( " Vet3 : " ) ;
74 escritaVetorInt ( vet3 ) ;
75 vet3 = excluiVetorInt ( vet3 ,7) ;
158 CAPITULO 5. VETORES

76 printf ( " Vet3 : " ) ;


77 escritaVetorInt ( vet3 ) ;
78 vet3 = excluiVetorInt ( vet3 ,10) ;
79 printf ( " Vet3 : " ) ;
80 escritaVetorInt ( vet3 ) ;
81 vet3 = excluiVetorInt ( vet3 ,5) ;
82 printf ( " Vet3 : " ) ;
83 escritaVetorInt ( vet3 ) ;
84
85 vet1 = ordenaMenorInt ( vet1 ) ;
86 printf ( " Vet1 : " ) ;
87 escritaVetorInt ( vet1 ) ;
88
89 vet2 = ordenaBolhaInt ( vet2 ) ;
90 printf ( " Vet2 : " ) ;
91 escritaVetorInt ( vet2 ) ;
92
93 }

Exemplo 5.23: Exemplo de Utilizacao do TAD tVetorInt

5.7 Exerccios Resolvidos

5.8 Resumo
Um vetor e uma estrutura de dados composta homogenea, isto e, ele possui, em geral,
mais de um elemento de um mesmo tipo.

Sao caractersticas importantes dos vetores a seqencializacao, a indexacao e a sua


dimensao.

Deve-se tomar cuidado para nao se acessar um vetor indevidamente, como, por exemplo
utilizando um ndice maior que seu tamanho.

A dimensao de um vetor pode ser definida, em C, tanto em tempo de compilacao, quanto


em tempo de execucao.

Na linguagem C nao existem operacoes basicas para a manipulacao de um vetor como um


todo. Seus elementos devem ser manipulado separadamente.

Quando acessado individualmente, cada elemento de um vetor pode ser encarado como
uma variavel basica.

A utilizacao de expressoes como ndice no acesso a elementos de um vetor e uma ferramenta


poderosa e deve ser usada.
5.9. LISTA DE EXERCICIOS 159

A utilizacao de funcoes para a manipulacao de vetores como um todo e importante para


a legibilidade e reusabilidade do programa e deve ser usada.
A utilizacao de TADs Implementacionais e um artifcio para facilitar a manipulacao de
vetores e aumentar a legibilidade e reusabilidade do programa.
O TAD Implementacional tVetorInt, implementado aqui na linguagem C, pode ser utili-
zado, sempre que necessario, para a manipulacao de vetores de numeros inteiros.

5.9 Lista de Exerccios


1. Faca um programa em linguagem C que leia 100 numeros reais e os imprima na ordem
inversa em que foram lidos.
2. Crie uma funcao em linguagem C que some os elementos de um vetor com 100 elementos
inteiros.
3. Faca um programa em linguagem C que leia as notas e as matrculas dos alunos de uma
turma de no maximo 50 alunos numa prova e obtenha:
(a) a melhor nota e o aluno que a obteve;
(b) a pior nota e o aluno que a obteve;
(c) a media da turma;
(d) os alunos que obtiveram nota superior a media da turma.
4. Faca um programa em linguagem C que determine os 100 primeiros numeros primos.
Considere que:
(a) um numero e primo quando so e divisvel por si mesmo;
(b) e suficiente testar a divisibilidade de um numero X por numeros primos que o ante-
cedem ate um limite igual a raiz de X.
5. Faca um programa em linguagem C que leia duas listas de caracteres com ate 100 elementos
e coloque cada um num vetor respectivo. Apos ter sido realizado a leitura das duas listas,
deve ser lida a ordem a partir da qual deve ser inserida a segunda lista na primeira lista
de caracteres. Considere o seguinte exemplo:

Primeira lista:
abcdjklm
Segunda lista:
efghi
Ordem:
5
Resultado:
abcdefghijklm
160 CAPITULO 5. VETORES

6. Faca um programa em linguagem C que leia dois vetores ordenados em M e N elementos


respectivamente e os intercale gerando um novo vetor U, tambem ordenado. Considere o
exemplo:

M: 5
N: 4
Primeiro vetor:
1 7 13 14 30
Segundo vetor:
2 3 7 16
Resultado:
1 2 3 7 7 13 14 16 30

Obs.: O terceiro vetor nao pode ser gerado copiando-se primeiramente os dois vetores e
fazendo a ordenacao posteriormente.

7. Faca um programa em linguagem C que leia N (N < 1000) numeros de matrculas de alunos
que fazem PD1 e os coloque em ordem crescente num vetor a medida que vao sendo lidas
as matrculas. Posteriormente escreva uma funcao que identifique se um certo conjunto
de M alunos fazem PD1. Deve-se utilizar o algoritmo de pesquisa binaria para fazer esta
verificacao.

8. Faca um programa em linguagem C que:

(a) leia N numeros reais colocando-os num vetor de 100 elementos (considerar N < 1000);
(b) ordene crescentemente os elementos de ndices mpares utilizando o metodo da bolha
para tal(considerar apenas os N elementos);
(c) escreva os N numeros apos o ajuste do tem (b);

9. Crie um procedimento em linguagem C que cumpra a seguinte especificacao:

(a) Dados de entrada:


i. um vetor de caracteres com um maximo de 100 elementos;
ii. o tamanho corrente do vetor (suponha que o vetor inicie na primeira posicao);
iii. um caracter a ser inserido;
iv. a posicao do vetor onde o caracter deve ser inserido.
(b) Dados de sada:
i. o vetor com o caracter inserido na posicao;
ii. o vetor deve ser mantido inalterado se a posicao onde deveria ser inserido o carcter
superior ao tamanho corrente do vetor ou se o vetor ja estiver completo.
5.10. TRABALHOS SUGERIDOS 161

10. Um armazem trabalha com 100 mercadorias diferentes identificadas pelos numeros de 1 a
100. O dono anota as quantidades de cada mercadoria vendida no mes. Ele tem uma tabela
que indica para cada mercadoria o preco de venda. Escreva um algoritmo em linguagem
C que calcule o faturamento mensal do armazem, onde:

F aturamento = (quantidadei precoi ), i = 1, 2, . . . , 100.

11. Escreva um algoritmo em linguagem C que:

(a) leia um conjunto A de 100 elementos reais;


(b) construa e imprima um outro conjunto B formado da seguinte maneira:
i. os elementos de ordem par sao os correspondentes de A divididos por 2;
ii. os elementos de ordem mpar sao os correspondentes de A multiplicados por 3.

12. Dado um vetor contendo uma frase com 80 caracteres (incluindo brancos), escreva um
algoritmo em linguagem C para:

(a) contar quantos brancos existem na frase;


(b) contar quantas vezes aparece a letra A;
(c) contar quantas vezes aparece um mesmo par de letras na frase e quais sao eles.

13. Faca um algoritmo em linguagem C para ler um vetor de comprimento N (par menor ou
igual a 50) e imprimir seu conteudo depois de feita a troca dos elementos das posicoes
pares com os elementos das posicoes mpares.

14. Faca um programa em linguagem C que, dado um nome terminado por ponto, devolva o
numero de vogais nele presentes.

15. Faca um programa em linguagem C que, dada uma frase terminada por ponto, retire todos
os espacos em branco da mesma e retorne a frase modificada.

16. Faca um programa em linguagem C que, dada duas listas de nomes, compare-as e retorne
o numero de vezes que cada palavra da segunda lista aparece na primeira lista. Assuma
que cada nome seja composto de no maximo 15 caracteres.

5.10 Trabalhos Sugeridos


162 CAPITULO 5. VETORES

Figura 5.7: Metodo do Menor


5.10. TRABALHOS SUGERIDOS 163

Figura 5.8: Metodo da Bolha


Captulo 6

Matrizes
Autores:

Clebson Oliveira
Julio Dalfior

Objetivos:

Mostrar a importancia de se utilizar a estrutura implementacional de dados matrizes para


resolver problemas

Introduzir o conceito do tipo abstrato de dado matriz (tMatriz)

Mostrar como modelar um problema atraves do TAD tMatriz

6.1 Introducao
Uma matriz e um tipo de dado composto homogeneo na qual seus elementos sao organizados
em uma estrutura multidimensional. Por exemplo, uma matriz bidimensional e formada pela
combinacao de linhas horizontais e verticais. A figura 6.1 mostra uma matriz 5 por 3. Note que
a matriz possui 5 linhas e 3 colunas. Embora matrizes possam ter mais que 2 dimensoes, neste
livro serao abordadas apenas matrizes bidimensionais. Uma matriz com m linhas e n colunas e
chamada matriz m por n.
Para mostrar que matrizes bidimensionais sao muito comuns no nosso dia-a-dia podem-
se citar varios exemplos: um cartao de bingo, uma agenda de compromissos (6.2), etc. E
interessante que haja uma maneira de representar esse tipo de estrutura, para que se possa
utiliza-la na solucao de problemas.
Na figura 6.2 tem-se que as linhas representam os dias do mes, as colunas representam os
meses do ano e os elementos da matriz representam um determinado dia do mes.
Considerando que essa matriz sera utilizada para representar se ha ou nao compromisso
naquele dia do mes, temos uma matriz bidimensional porque a cada celula dessa matriz estao

164
6.2. DEFINICAO E ACESSO 165

Figura 6.1: Uma matriz 5 por 3

Figura 6.2: Agenda de compromissos e sua representacao matricial

associadas duas informacoes (mes e dia). Por outro lado, se for necessario listar varios compro-
missos para cada dia do mes ter-se-a uma matriz tridimensional, porque a cada celula da matriz
estarao associadas tres informacoes (mes, dia e hora).
E fato que a visualizacao das matrizes utilizadas facilita o entendimento deste conceito. Por
exemplo, uma matriz bidimensional pode ser visualizada como um quadro e uma tridimensional
pode ser visualizada como um cubo formado por varios cubos menores. Apesar de ser difcil
visualizar matrizes com mais de tres dimensoes, vale ressaltar que elas existem e que e possvel
declara-las em C. Como exemplo da utilizacao de matrizes com mais de tres dimensoes pode-se
utilizar o exemplo anterior supondo que se queira representar tambem o ano. Entao, teremos
uma matriz de quatro dimensoes (ano, mes, dia e hora).

6.2 Definicao e Acesso

Para que um problema seja modelado atraves de uma matriz e necessario que se defina uma
variavel para representa-la, para que assim se possa utilizar seus elementos para guardar in-
formacoes e posteriormente aplicar operacoes sobre essa estrutura.
166 CAPITULO 6. MATRIZES

6.2.1 Definicao
Para definir uma variavel do tipo matriz e necessario que se saiba o tamanho de cada dimensao.
No entanto, existem duas maneiras de estabelecer o tamanho dessas dimensoes: estaticamente
e dinamicamente.

Estaticamente
No momento em que se define a matriz e necessario estabelecer um tamanho para cada dimensao,
sendo que esse tamanho nao podera ser alterado durante a execucao do programa. O codigo do
exemplo 6.1 mostra como isso pode ser feito em C:

1 int matriz [5][10];

Exemplo 6.1: Definicao estatica

O numero entre o primeiro par de colchetes ([5]) define o tamanho da primeira dimensao
(numero de linhas). O numero entre o segundo par de colchetes ([10]) define o tamanho da
segunda dimensao (numero de colunas), e assim por diante. No caso acima a variavel matriz
possui 5 linhas e 10 colunas, como se pode ver na figura 6.3. Os tracos na matriz representam a
informacao armazenada na celula, a qual e o compartimento onde estao localizados os elementos
da matriz.

Figura 6.3: Matriz 5 por 10

Inicializacao
Vale ressaltar que na declaracao de matrizes mostrada anteriormente, os valores das celulas da
matriz nao foram inicializados e isso pode gerar resultados inesperados. Uma vez que nao se
sabe quais valores estavam armazenados anteriormente nas areas de memoria utilizadas. Para
evitar esse tipo de problema e uma boa pratica de programacao inicializar os elementos das
matrizes antes de tentar acessar seus valores, mesmo que posteriormente esses elementos sejam
configurados com outros valores. A forma mais simples de se fazer isso, em C, pode ser vista no
exemplo 6.2.
6.2. DEFINICAO E ACESSO 167

1 int main () {
2
3 int i , j ;
4 int matriz [2][2] = {0};
5
6 return 0;
7 }

Exemplo 6.2: Inicializacao de uma matriz

Na linha 4 do exemplo 6.2 temos a inicializacao de todos os elementos da matriz com o valor
zero.

6.2.2 Acesso

Agora que a matriz ja foi inicializada, para se utilizar os dados contidos nela sera necessario
fazer uma operacao de acesso, como no exemplo 6.3:

1 # define NMESES 12
2 # define NDIAS 31
3 # define NHORAS 24
4
5 main () {
6
7 int reservaSala [ NMESES ][ NDIAS ][ NHORAS ] = {0};
8 int resposta =0;
9 .
10 .
11 .
12 resposta = reservaSala [5][1][10];
13
14 if ( resposta ==0) {
15
16 reservaSala [5][1][10]=1;
17 }
18
19 }

Exemplo 6.3: Acesso e atualizacao de um elemento

A variavel resposta recebe 0 ou 1 indicando, respectivamente, se as 11 horas do dia 2 de


junho a sala esta livre ou reservada. Note que, assim como em vetores, o primeira posicao
de cada dimensao da matriz tem ndice 0. Apos acessar um elemento da matriz chamada
reservaSala (linha 12) e verificar que em uma determinada data e horario a sala esta livre (linha
14), atualiza-se o estado da sala de livre para ocupada (linha 16).
168 CAPITULO 6. MATRIZES

6.3 O TAD implementacional tMatriz


O TAD implementacional tMatriz e usado como ferramenta para resolver problemas que possuam
caracterstica de armazenamento relacionados a matrizes. Uma de suas principais vantagens e
poder simular uma matriz cujo tamanho pode ser alterado dinamicamente, o que proporciona a
reutilizacao dessa estrutura.

6.3.1 Atributos
O primeiro passo para definir o TAD tMatriz envolve escolher os seus atributos. Como a ideia
e modelar o problema por uma matriz, e necessario que um dos atributos seja a propria matriz
que armazenara os elementos. Alem disso e necessario guardar a quantidade de elementos de
cada dimensao.
Apesar de ser possvel definir uma matriz com um numero qualquer de dimensoes e que
armazene um tipo qualquer de dado (int, float, char, etc), neste captulo serao utilizadas apenas
matrizes bidimensionais de inteiros, por serem comumente utilizadas.
Portanto, os atributos de uma matriz bidimensional de inteiros sao:

Numero de linhas (nLinhas): Armazena a quantidade de elementos de cada coluna.

Numero de colunas (nColunas): Armazena a quantidade de elementos de cada linha.

Matriz (valores): Armazena os dados da matriz propriamente dita.

No exemplo 6.4 pode ser vista uma maneira de se definir o TAD tMatrizInt, de forma a
representar matrizes bidimensionais de inteiros.

1 # define MAX_DIM 5
2
3 typedef struct {
4
5 int valores [ MAX_DIM ][ MAX_DIM ];
6 int nLinhas ;
7 int nColunas ;
8 } tMatrizInt ;

Exemplo 6.4: Estrutura basica do tipo tMatrizInt

No exemplo 6.4 a matriz valores e declarada estaticamente, sendo seu tamanho definido pela
constante MAX DIM. Como os proprios nomes evidenciam, nLinhas e nColunas sao variaveis
inteiras que representam o numero de linhas e colunas, respectivamente, da matriz.
Na figura 6.4 pode-se visualizar a estrutura da matriz dados de uma variavel do tipo tMa-
trizInt inicializada como no exemplo abaixo:

1 tMatrizInt dados ;
6.3. O TAD IMPLEMENTACIONAL TMATRIZ 169

2 int i , j ;
3
4 dados . nLinhas = 3;
5 dados . nColunas = 3;
6
7 for ( i =0; i < dados . nLinhas ; i ++) {
8 for ( j =0; j < dados . nColunas ; j ++) {
9 dados . valores [ i ][ j ]=0;
10 }
11 }

Exemplo 6.5: Inicializacao de uma estrutura tMatrizInt

Nas linhas 4 e 5 o tamanho da matriz e definido como 3 por 3. Na linha 7, o comando for
percorre as linhas da matriz de ndice zero a nLinhas-1. O segundo comando for percorre todas
as colunas (para cada laco do for anterior) de ndice zero a nColunas-1. Dessa forma percorrem-
se todos os elementos da matriz valores, dentro das dimensoes passadas, atribuindo-se a eles o
valor zero.
A figura 6.4 representa a matriz valores da estrutura dados do exemplo 6.5.

Figura 6.4: Estrutura da matriz valores pertencente a variavel dados (para MAX DIM igual a
5)

Apesar da matriz valores pertencente a variavel dados ter 5 linhas e 5 colunas, somente os
elementos pertencentes as 3 primeiras linhas e colunas foram inicializados. Isso porque definiu-se,
nas linhas 4 e 5, que a matriz deveria ter somente esse numero de linhas e colunas. Dessa forma,
em todas as operacoes efetuadas sobre essa variavel somente os elementos pertencetes a essa
submatriz (formada pelos zeros na figura) devem ser considerados. Todos os outros (marcados
com sinal -) possuem valores nao determinados, por nao terem sido inicializados, e devem ser
ignorados.

6.3.2 Operacoes
Uma das vantagens de se implementar um tipo abstrato de dado e facilitar a solucao de pro-
blemas, atraves do uso de operacoes que atuem sobre o tipo abstrato de dado definido. Dessa
170 CAPITULO 6. MATRIZES

forma, vamos definir algumas operacoes sobre o tMatrizInt: inicializacao, atualizacao de


valor, acessoa valor, exibicao, soma dos elementos de uma linha, soma dos elementos
de uma coluna e produto escalar.

Inicializacao

Uma operacao essencial para se realizar sobre tMatrizInt e a inicializacao. Ela deve ser a primeira
a ser realizada, caso contrario as demais podem gerar resultados inesperados (como termino do
programa, por exemplo). Isso porque, durante a inicializacao, preparam-se as estruturas internas
do TAD para se adequar ao caso desejado. No caso do tipo tMatrizInt proposto nesse captulo,
a operacao de inicializacao definira as dimensoes iniciais da matriz.
O codigo do exemplo 6.6 mostra como isso pode ser implementado:

1 tMatrizInt inicializa ( int nLinhas , int nColunas ) {


2
3 tMatrizInt resultado ;
4
5 resultado . nLinhas = nLinhas ;
6 resultado . nColunas = nColunas ;
7
8 return resultado ;
9 }

Exemplo 6.6: Funcao de inicializacao

Na definicao da funcao Inicializa (linha 1), os argumentos nLinhas e nColunas servem para
definir a quantidade de linhas e colunas da matriz respectivamente, e esses valores sao atribudos
a estrutura tMatriz nas linhas 6 e 7. Finalmente, na linha 16 a estrutura e retornada atraves da
variavel resultado.

Atualizacao de Valor

Como nao e interessante que uma matriz apresente todos os elementos com os mesmos valores,
e necessario definir uma forma de atualiza-los individualmente. Isso pode ser feito atraves da
operacao de escrita definida pela funcao do exemplo 6.7.

1 tMatrizInt atualizaMatriz ( tMatrizInt matriz , int linha , int coluna , int valor ) {
2
3 if ( ( linha >=0 && linha < matriz . nLinhas ) && ( coluna >=0 && coluna < matriz .
nColunas ) ) {
4 matriz . valores [ linha ][ coluna ] = valor ;
5 }
6
7 return matriz ;
8 }
6.3. O TAD IMPLEMENTACIONAL TMATRIZ 171

Exemplo 6.7: Escrita em um elemento da matriz

Na linha 3 e verificado se o elemento pertence a matriz(se o numeros de sua linha e de sua coluna
estao dentro das dimensoes da matriz). Caso tudo esteja correto, o valor passado e atribudo ao
elemento desejado e a funcao retorna o numero 0, informando que a operacao foi realizada com
sucesso. Caso contrario a funcao retorna o numero -1 para informar que a escrita nao pode ser
efetuada.

Acesso a valor
Tendo em vista a importancia de utilizar o valor de um determinado tem, e necessario construir
uma funcao para acessar os dados da matriz (exemplo 6.8).

1 int acessaMatriz ( tMatrizInt matriz , int linha , int coluna )


2 {
3
4 if ( ( linha >=0 && linha < matriz . nLinhas ) && ( coluna >=0 && coluna < matriz .
nColunas ) ) {
5 return matriz . valores [ linha ][ coluna ];
6
7 } else {
8 return -1;
9 printf ( Item inexistente \ n ) ;
10
11 }
12 }

Exemplo 6.8: Funcao de acesso aos dados da matriz

Na linha 4 e realizada a mesma verificacao que na linha equivalente do exemplo 6.7. Se o


elemento pertencer a matriz o seu valor e retornado. Caso contrario, o valor -1 e retornado e
uma mensagem de erro e impressa na tela. Note que, na maioria das aplicacoes, nao sera possvel
verificar a execucao correta da funcao analisando o valor de retorno, pois o valor retornado (-1)
pode fazer parte do universo de valores validos para a aplicacao.

Exibicao
Para poder visualizar todos os elemetos de uma matriz de inteiros e necessario definir uma
operacao de exibicao (ou impressao) da matriz na tela (exemplo 6.9).

1 int exibeMatriz ( tMatrizInt matriz ) {


2
3 int i =0 , j =0;
4
5 for ( i =0; i < matriz . nLinhas ; i ++) {
172 CAPITULO 6. MATRIZES

6 for ( j =0; j < matriz . nColunas ; j ++) {


7 printf ( " %5 d \0 " , matriz . valores [ i ][ j ]) ;
8
9 }
10 printf ( " \ n " ) ;
11 }
12 }

Exemplo 6.9: Exibicao da matriz na tela

No exemplo 6.9 tem-se, na linha 5, um comando for para varrer as linhas da matriz, e para
cada linha tem-se outro comando for, na linha 6, para varrer os elementos dessa linha. Na linha
7, o trecho %5d faz com que os numeros sejam exibidos sempre com 5 dgitos, o que faz com
que a forma de exibicao seja regular, como pode ser visto na figura 6.5.

Figura 6.5: Exemplo de sada da operacao exibe

Soma dos elementos de uma linha


Como em muitas aplicacoes e necessario obter o valor da soma dos elementos de uma linha, sera
definida uma operacao para efetuar esse calculo.

1 int somaLinhaMatriz ( tMatrizInt matriz , int linha ) {


2
3 int i =0 , soma =0;
4
5 for ( i =0; i < matriz . nColunas ; i ++) {
6 soma = soma + matriz . valores [ linha ][ i ];
7 }
8
9 return soma ;
10 }

Exemplo 6.10: Soma dos elementos de uma linha

A variavel soma e inicializada com o valor zero. O for da linha 5 percorre todos os elementos
da linha da matriz e, para cada laco, adiciona o valor da celula da matriz a soma. A variavel
soma funciona, entao, como um acumulador e, ao final dos lacos, armazena o valor da soma dos
elementos da linha.
6.3. O TAD IMPLEMENTACIONAL TMATRIZ 173

Soma dos elementos de uma coluna


Anteriormante foi definida a somda dos elementos de uma linha. Agora sera definida uma
operacao para efetuar a soma dos elementos de uma coluna.

1 int somaColunaMatriz ( tMatrizInt matriz , int coluna ) {


2
3 int i =0 , soma =0;
4
5 for ( i =0; i < matriz . nLinhas ; i ++) {
6 soma = soma + matriz . valores [ i ][ coluna ];
7 }
8
9 return soma ;
10 }

Exemplo 6.11: Soma dos elementos de uma coluna

A variavel soma e inicializada com o valor zero. O for da linha 5 percorre todos os elementos
da coluna da matriz e, para cada laco, adiciona o valor da celula da matriz a soma. A variavel
soma funciona, entao, como um acumulador e, ao final dos lacos, armazena o valor da soma dos
elementos da coluna.

Produto Escalar
Agora, sera definida uma operacao de composicao chamada produto escalar, que gera uma matriz
M de cujos elementos sao resultados de uma composicao dos elementos de A e B 1 (M=A.B).
Nesta operacao os elementos mij da matriz M sao resultantes do somatorio dos produtos entre
os elementos das linhas i da primeira matriz, e os elementos das colunas j da segunda matriz,
para que esta operacao seja executada e necessario que o numero de colunas da matriz A seja
igual ao numero de linhas da matriz B.

1 tMatrizInt produtoEscalar ( tMatrizInt matriz1 , tMatrizInt matriz2 ) {


2
3 int i =0 , j =0 , k =0;
4 tMatrizInt produto ;
5
6 if ( matriz1 . nColunas == matriz2 . nLinhas ) {
7
8 for ( i =0; i < matriz1 . nLinhas ; i ++) {
9
10 for ( j =0; j < matriz2 . nColunas ; j ++) {
11
12 produto . valores [ i ][ j ]=0;
13
14 for ( k =0; k < matriz1 . nColunas ; k ++) {
1
Vale ressaltar que A.B e diferente de B.A . Logo, o produto escalar entre matrizes nao e comutativo
174 CAPITULO 6. MATRIZES

15
16 produto . valores [ i ][ j ] += matriz1 . valores [ i ][ k ] *
17 matriz2 . valores [ k ][ j ];
18 }
19 }
20 }
21
22 produto . nLinhas = matriz1 . nLinhas ;
23 produto . nColunas = matriz2 . nColunas ;
24 } else {
25
26 produto . nLinhas = produto . nColunas =0;
27 }
28
29 return produto ;
30 }

Exemplo 6.12: Soma dos elementos de uma fila

6.4 Exerccios Resolvidos


Exerccio Resolvido 6.1 - Alocacao de sala

Deseja-se desenvolver um programa para controlar a reserva de uma sala ao longo de um


ano. Ao usuario devem ser fornecidas as seguintes operacoes:
1. Reservar a sala
2. Cancelar reserva
3. Dado um mes, listar dias em que a sala esta disponvel
4. Informar o ndice de ocupacao de cada mes
Implemente o programa utilizando o TAD tMatrizInt.

Solucao Possvel:
Para se armazenar as informacoes de reserva da sala sera utilizada uma matriz com 31 linhas
(representando os dias) e 12 colunas (representado os meses). Dessa forma, para se reservar a
sala para o dia 1o de janeiro, atribui-se ao elemento da linha 0 e coluna 0 o valor 1.
Para resolver o problema sera criada uma funcao para cada operacao.
1. Reservar a sala:
A primeira coisa a ser feita e verificar a reserva da sala para a data informada. Se a sala
estiver livre sera reservada, uma mensagem de sucesso impressa na tela e a matriz alterada
sera retornada. Caso contrario, a matriz e retornada inaltarada e uma mensagem de erro
impressa na tela.
6.4. EXERCICIOS RESOLVIDOS 175

1 tMatrizInt reservaSala ( tMatrizInt mat , int dia , int mes ) {


2
3 if ( acessaMatriz ( mat , dia , mes ) == 0) {
4 printf ( " Sala reservada com sucesso \ n " ) ;
5 return atualizaMatriz ( mat , dia , mes , 1) ;
6 } else {
7 printf ( " Sala ja reservada para a data informada \ n " ) ;
8 return mat ;
9 }
10 }

Exemplo 6.13: Reservar a sala

2. Cancelar a reserva:
Novamente, deve-se verificar a reserva da sala para a data informada. Se a sala estiver
reservada a reserva sera cancelada, uma mensagem de sucesso impressa na tela e a matriz
alterada sera retornada. Caso contrario, a matriz e retornada inaltarada e uma mensagem
de erro impressa na tela.

1 tMatrizInt cancelaReserva ( tMatrizInt mat , int dia , int mes ) {


2
3 if ( acessaMatriz ( mat , dia , mes ) != 0) {
4 printf ( " Reserva cancelada " ) ;
5 return atualizaMatriz ( mat , dia , mes , 0) ;
6 } else {
7 printf ( " Sala n~
a o reservada para a data informada \ n " ) ;
8 return mat ;
9 }
10 }

Exemplo 6.14: Cancelar reserva

3. Listar dias livres do mes:


Deve-se verificar, em um laco, todos os dias do mes escolhido e, para cada dia, verificar
a reserva da sala. Se ela estiver ocupada, entao o dia sera impresso na tela de modo a
informar isso ao usuario. Note que o numero de dia de cada mes varia e, portanto, deve
haver um controle de forma que o laco termine com o numero de dias correto para o mes
escolhido.

1 void listaDiasLivres ( tMatrizInt mat , int mes ) {


2
3 int dia ;
4 int numeroDiasMes [] = {31 ,28 ,31 ,30 ,31 ,30 ,31 ,31 ,30 ,31 ,30 ,31};
5 printf ( " Dias livres para o m^
e s % d \ n " , mes +1) ;
6
176 CAPITULO 6. MATRIZES

7 for ( dia = 0; dia < numeroDiasMes [ mes ]; dia ++) {


8 if ( acessaMatriz ( mat , dia , mes ) == 0) {
9 printf ( " % d \ n " , dia +1) ;
10 }
11 }
12 }

Exemplo 6.15: Listar dias livres do mes

4. ndide de ocupacao de cada mes:


O ndice de ocupacao de um mes nada mais e do que o numero de dias com a sala reservada
naquele mes. Alem disso, como a sala ocupada e representada pelo numero 1 e a sala vazia
pelo numero 0, somando-se a coluna que representa um determinado mes obtem-se o
numero de dias ocupados para esse mes. Assim sendo, para resolver o problema, basta
percorrer todas as colunas da matriz e, para cada uma delas, efetuar a soma de seus
elementos.

1 void im prim eIndi ceOc upaca o ( tMatrizInt mat ) {


2
3 int mes ;
4 int indice ;
5
6 printf ( " ndice de ocupac~
a o dos meses do ano \ n " ) ;
7
8 for ( mes = 0; mes < 12; mes ++) {
9 indice = somaColunaMatriz ( mat , mes ) ;
10
11 printf ( " Mes % d : % d dias ocupados \ n " , mes , indice ) ;
12 }
13 }

Exemplo 6.16: ndide de ocupacao de cada mes

Uma vez definidas as funcoes que realizam as operacoes, falta entao definir a funcao principal
do programa onde a interacao com o usuario sera feita e as funcoes serao chamadas.

1 int main ()
2 {
3 tMatrizInt reservas ;
4 int codigo , dia , mes ;
5
6 reservas = inicializa (31 , 12) ;
7
8 for ( mes =0; mes <12; mes ++) {
9 for ( dia =0; dia <31; dia ++)
10 reservas . valores [ dia ][ mes ] = 0;
11 }
6.4. EXERCICIOS RESOLVIDOS 177

12
13 do {
14 printf ( " \\ n \\ n \\ n \\ n \\ n \\ n \\ n " ) ;
15 printf ( " ## Reserva de Salas ##\\ n \\ nEscolha uma operac~ a o :\\ n " ) ;
16 printf ( " 1 - Reservar a sala \\ n " ) ;
17 printf ( " 2 - Cancelar uma reserva \\ n " ) ;
18 printf ( " 3 - Listar dias livres \\ n " ) ;
19 printf ( " 4 - Exibir ndice de ocupac~ a o \\ n " ) ;
20 printf ( " 5 - Sair \\ n " ) ;
21 printf ( " Digite o codido da opecac~ a o escolhida : " ) ;
22 scanf ( " % d " , & codigo ) ;
23
24 switch ( codigo ) {
25 case 1:
26 printf ( " \\ n \\ nDigite a data da reserva ( dia / mes ) : " ) ;
27 scanf ( " % d /% d " , & dia , & mes ) ;
28 reservas = reservaSala ( reservas , dia , mes ) ;
29 break ;
30
31 case 2:
32 printf ( " \\ n \\ nDigite a data da reserva a cancelar ( dia / mes ) : " ) ;
33 scanf ( " % d /% d " , & dia , & mes ) ;
34 reservas = cancelaReserva ( reservas , dia , mes ) ;
35 break ;
36
37 case 3:
38 printf ( " \\ n \\ nDigite o mes : " ) ;
39 scanf ( " % d " , & mes ) ;
40 listaDiasLivres ( reservas , mes ) ;
41 break ;
42
43 case 4:
44 imp rime Indic eOcu pacao ( reservas ) ;
45 break ;
46 }
47 } while ( codigo != 5) ;
48
49 return 0;
50
51 }

Exemplo 6.17: Interacao com usuario

Exerccio Resolvido 6.2 - Soma de elementos

Dada uma matriz bidimensional de inteiros, defina operacoes que realizem os seguintes
calculos:
A soma dos elementos da diagonal principal
A soma dos elementos da submatriz triangular
178 CAPITULO 6. MATRIZES

Solucao Possvel:

A soma dos elementos da diagonal principal

Para calcular a soma dos elementos da diagonal principal de uma matriz M[i,j] e necessario
notar que todos os elementos da diagonal principal apresentam ndices referentes a coluna
e a linha iguais (M[i,i]). Assim, varrer-se a diagonal principal da matriz atraves de um
loop, na linha 6, onde a cada laco soma-se o valor do elemento M[i,i] a variavel soma e
incrementa-se o valor do ndice i de uma unidade.

1 int somaDiagonal ( tMatrizInt matriz ) {


2
3 int soma =0;
4 int i ;
5
6 for ( i =0; i < matriz . nLinhas && i < matriz . nColunas ; i ++) {
7 soma = soma + acessaMatriz ( matriz , i , i ) ;
8 }
9
10 return soma ;
11 }

Exemplo 6.18: Soma da diagonal principal

A soma dos elementos da submatriz triangular superior

Como os elementos da matriz triangular superior sao os elementos que estao situados acima
dos elementos da diagonal principal, pode-se notar que para cada linha esses elementos
estao situados nas colunas a direita do elemento da diagonal principal. Dessa forma, varre-
se a matriz triangular superior atraves de um loop, na linha 6, que varre todas as linhas i
da matriz, e para cada linha tem-se outro loop, linha 7, para varrer as colunas de ndice
i+1 ate nColunas. Entao, soma-se o valor de cada elemento visitado a variavel soma,
linha 8.

1 int somaTriangular ( tMatrizInt matriz ) {


2
3 int soma =0;
4 int i , j ;
5
6 for ( i =0; i < matriz . nLinhas ; i ++) {
7 for ( j = i +1; j < nColunas ; j ++) {
8 soma = soma + acessaMatriz ( matriz , i , j ) ;
9 }
6.4. EXERCICIOS RESOLVIDOS 179

10 }
11
12 return soma ;
13 }

Exemplo 6.19: Soma da matriz triangular superior

Exerccio Resolvido 6.3 - Uns isolados

Dada uma matriz bidimensional nxn de zeros e uns, defina uma operacao para calcular
o numero de uns isolados dessa matriz. Considere que um numero um esta isolado se nenhum
dos elementos adjacentes a ele, apenas na horizontal e vertical, sao uns.

Solucao Possvel:
Para cada celula que contenha o valor um, sera necessario checar no maximo quatro celulas
adjacentes a ela para saber se a mesma contem um numero um isolado. Os loops das linhas 5 e
7 servem para varrer a matriz completamente, e a cada celula visitada checa-se se suas celulas
adjacentes sao zeros, dependendo de quantas ela possa ter. Assim, se o elemento visitado estiver
na primeira linha (linha 9) ele pode ser: o primeiro da linha (11), e entao so tera adjacentes
abaixo e a direita (12); o ultimo da linha (linha 16), e entao so tera adjacentes abaixo e a esquerda
(17); ou caso nao seja nem o primeiro e nem o ultimo, tera adjacentes abaixo, a esquerda e a
direita. Caso o elemento esteja na ultima linha (linha 27) ele pode ser: o primeiro da linha
(linha 29), e entao so tera adjacentes acima e a direita (linha 30); o ultimo da linha (linha 34),
e entao so tera adjacentes acima e a esquerda (linha 35); ou caso nao seja nem o primeiro e nem
o ultimo tera adjacentes acima, a esquerda e a direita (linha 39). Apos checar se o elemento
visitado esta na primeira ou na ultima linha da matriz, temos o caso dele estar na primeira
ou na ultima coluna, desconsiderando os elementos dessas colunas que estejam na primeira ou
na ultima linha. Se ele estiver na primeira coluna (linha 45) tera adjacentes acima, abaixo e a
direita (linha 46); e se ele estiver na ultima coluna (linha 50), tera adjacentes acima, abaixo e a
esquerda (linha 51). Finalmente, se o elemento visitado nao estiver na primeira linha, na ultima
linha, na primeira coluna e nem na ultima coluna (linha 53); ele tera adjacentes acima, abaixo,
a esquerda e a direita (linha 55).

1 int unsIsolados ( tMatrizInt matriz ) {


2
3 int i ,j , unsisolados =0;
4
5 for ( i =0; i < matriz . nLinhas ; i ++) {
6
7 for ( j =0; j < matriz . nColunas ; j ++) {
8
9 if ( i ==0) {
10
11 if ( j ==0) {
180 CAPITULO 6. MATRIZES

12 if ( acessaMatriz ( matriz , i , j +1) ==0 && acessaMatriz ( matriz , i +1 , j


) ==0 && acessaMatriz ( matriz ,i , j ) ==1)
13 unsisolados ++;
14 } else {
15
16 if ( j == matriz . nColunas -1) {
17 if ( acessaMatriz ( matriz , i , j -1) ==0 && acessaMatriz ( matriz , i
+1 , j ) ==0 && acessaMatriz ( matriz ,i , j ) ==1)
18 unsisolados ++;
19 } else {
20
21 if ( acessaMatriz ( matriz , i +1 , j ) ==0 && acessaMatriz ( matriz , i ,
j +1) ==0 && acessaMatriz ( matriz , i , j -1) ==0 && acessaMatriz
( matriz ,i , j ) ==1)
22 unsisolados ++;
23 }
24 }
25 } else {
26
27 if ( i == matriz . nLinhas -1) {
28
29 if ( j ==0) {
30 if ( acessaMatriz ( matriz , i , j +1) ==0 && acessaMatriz ( matriz , i
-1 , j ) ==0 && acessaMatriz ( matriz ,i , j ) ==1)
31 unsisolados ++;
32 } else {
33
34 if ( j == matriz . nColunas -1) {
35 if ( acessaMatriz ( matriz , i , j -1) ==0 && acessaMatriz ( matriz ,
i -1 , j ) ==0 && acessaMatriz ( matriz ,i , j ) ==1)
36 unsisolados ++;
37 } else {
38
39 if ( acessaMatriz ( matriz , i -1 , j ) ==0 && acessaMatriz ( matriz ,
i , j +1) ==0 && acessaMatriz ( matriz , i , j -1) ==0 &&
acessaMatriz ( matriz ,i , j ) ==1)
40 unsisolados ++;
41 }
42 }
43 } else {
44
45 if ( j ==0 && i >0 && i < matriz . nLinhas -1) {
46 if ( acessaMatriz ( matriz , i , j +1) ==0 && acessaMatriz ( matriz , i
+1 , j ) ==0 && acessaMatriz ( matriz , i -1 , j ) ==0 &&
acessaMatriz ( matriz ,i , j ) ==1)
47 unsisolados ++;
48 } else {
49
50 if ( j == matriz . nColunas -1 && i >0 && i < matriz . nLinhas -1) {
51 if ( acessaMatriz ( matriz , i , j -1) ==0 && acessaMatriz ( matriz ,
i +1 , j ) ==0 && acessaMatriz ( matriz , i -1 , j ) ==0 &&
6.5. RESUMO 181

acessaMatriz ( matriz ,i , j ) ==1)


52 unsisolados ++;
53 } else {
54
55 if ( acessaMatriz ( matriz , i , j +1) ==0 && acessaMatriz ( matriz ,
i , j -1) ==0 && acessaMatriz ( matriz , i +1 , j ) ==0 &&
acessaMatriz ( matriz , i -1 , j ) ==0 && acessaMatriz ( matriz ,
i , j ) ==1)
56 unsisolados ++;
57 }
58 }
59 }
60 }
61 }
62 }
63
64 return unsisolados ;
65 }

Exemplo 6.20: Soma da matriz triangular superior

6.5 Resumo
Uma matriz e um tipo de dado composto homogeneo na qual seus elementos sao organi-
zados em uma estrutura multidimensional.

E necessario inicializar a matriz para evitar situacoes de acesso a elementos que contenham
valores indeterminados.

O TAD tMatriz e composto pelos seguintes atributos: numero de linhas da matriz, numero
de colunas da matriz e a estrutura que armazenara os elementos.

Quando se tenta acessar um elemento com posicao nao definida na matriz, e necessario
retornar uma mensagem de erro para indicar que ocorreu um erro na execucao do programa.

6.6 Exerccios Propostos


1. Escreva um subprograma que determine o maior valor em uma matriz de valores inteiros
com n > 0 linhas e m > 0 colunas.

2. Altere o subprograma anterior para exibir na tela todas as posicoes da matriz em que se
encontra tal valor maximo.

3. Faca um programa que calcule a matriz resultante da soma de duas matrizes de dimensoes
m linhas e n colunas, com valores inteiros.
182 CAPITULO 6. MATRIZES

4. Escreva um subprograma que calcule a transposta de uma dada matriz. Considere como
matriz trasposta At de A a matriz de cujos elementos At [i,j] sao iguais a os elementos
A[j][i] para 1<=i<=m e 1<=j<=n, onde m representa o numero de linhas e n o numero
de colunas da matriz A.

5. Escreva uma funcao que verifica se uma matriz nxn e simetrica. Uma matriz A e simetrica
se A[i,j] = A[j,i] para todo 1<=i,j<=n.

6. Faca um subprograma que calcule a soma dos termos que se situam na diagonal secundaria
ou abaixo dela numa matriz quadrada com elementos inteiros. Assuma que o numero
maximo de linhas e colunas e 100, mas que a matriz pode estar preenchida apenas parci-
almente.
Observacao: Antes de escrever o subprograma, voce deve apresentar a declaracao do tipo
da matriz que deve ser colocada nos programas que usarao este subprograma.

7. Faca um subprograma que receba uma matriz quadrada (dimensoes N x N) totalmente


preenchida com numeros inteiros e troque os elementos acima da diagonal principal pe-
los que estao abaixo dela. Atente para o fato que a matriz recebida deve ser retornada
modificada e que voce nao pode usar uma matriz ou vetor auxiliar para fazer a troca dos
elementos.
Exemplo para matriz 3 x 3:

1 2 3
4 5 6
7 8 9

antes

1 4 7
2 5 8
3 6 9

depois

8. Uma matriz quadrada inteira e chamada de quadrado magico se a soma dos elementos
de cada linha, a soma dos elementos de cada coluna e a soma dos elementos das diagonais
principal e secundaria sao todos iguais.
Exemplo de um quadrado magico:

8 0 7
4 5 6
3 10 2
6.6. EXERCICIOS PROPOSTOS 183

Escreva uma funcao que verifica se uma matriz de n linhas e n colunas representa um
quadrado magico.

9. Um quadrado latim de ordem n contem os numeros de 1 ate n de forma que nenhum numero
e repetido na mesma linha ou coluna. Este quadrado pode ser usado, por exemplo, em
alguns torneios esportivos para assegurar que cada equipe joga com todas outras equipes
uma e somente uma vez. Escreva uma funcao booleana que recebe uma matriz quadrada
e checa se ele e realmente um quadrado latim.
Exemplo de quadrado latim:

1 2 3 4
2 3 4 1
3 4 1 2
4 1 2 3

(numero de

10. Considere 2 matrizes quadradas do tipo tMatrizInt. Faca um subprograma para cada item
abaixo (note que as matrizes devem obrigatoriamente ser passadas como parametros aos
subprogramas):

11. A matriz abaixo representa o triangulo de pascal de ordem 6:


1
1 1
1 2 1
1 3 31
1 4 641
1 5 10 10 5 1
Os elementos extremos de cada linha sao iguais a 1. Os outros elementos sao obtidos
somando-se os dois elementos que aparecem imediatamente acima e a esquerda na linha
anterior. Assim 10 = 4 + 6. Escreva um programa que, dado n, gere e exiba na tela o
triangulo de Pascal de ordem n. A impressao nao deve conter zeros acima da diagonal
principal.

12. Considere uma matriz de inteiros. Define-se como vizinhanca de uma posicao da matriz a
soma de todos os numeros que estejam em posicoes adjacentes. Escreva um programa que
determine a posicao do elemento de maior vizinhanca.
a)Exibir uma matriz com as caractersticas definidas acima (considere que a dimensao
N, isto e, seu numero de linhas ou colunas, da matriz sera lida no programa principal e
passada como parametro para o subprograma);
b)determinar se as duas matrizes sao identicas;
c)determinar se a segunda matriz e uma rotacao de 90 graus a direita sobre a primeira;
184 CAPITULO 6. MATRIZES

d)determinar se a segunda matriz e uma rotacao de 180 graus a direita sobre a primeira;
e)determinar se a segunda matriz e a imagem espelhada vertical da primeira;
f)determinar se a segunda matriz e a imagem espelhada horizontal da primeira;
As figuras abaixo ilustram cada tipo de matriz com um exemplo de dimensao
3 x 3:

1 2 3
4 5 6
7 8 9

original

1 2 3
4 5 6
7 8 9

identica

7 4 1
8 5 2
9 6 3

90 a direita

9 8 7
6 5 4
3 2 1

180 a direita

13. Escreva ainda um programa que leia 2 matrizes e determine as relacoes existentes entre
elas utilizando os subprogramas do exerccio anterior.

14. Escreva uma funcao que receba uma matriz preenchida com caracteres maiusculos do
tipotMatrizChar e um vetor do tipo tVetor contendo uma palavra em letras maiusculas
e retorne o numero de ocorrencias da palavra na matriz. A palavra pode estar escrita
na matriz de cima para baixo, da esquerda para a direita ou nas diagonais paralelas a
diagonal principal ou na propria.
6.7. TRABALHOS SUGERIDOS 185

6.7 Trabalhos Sugeridos


1. Caca-Palavras
Considere o jogo caca-palavras que consiste em procurar uma palavra dada em uma matriz
de letras. Considera-se que a palavra foi encontrada se ela estiver inteiramente disposta
em uma linha, em uma coluna ou em uma diagonal da matriz. Define-se uma palavra como
uma sequencia de letras maiusculas (para facilitar nao considere acentuacao). As palavras
estao escritas na matriz sempre de cima para baixo, da esquerda para a direita ou nas
diagonais paralelas a diagonal principal ou na propria diagonal. Faca um programa que
tenha como entrada um conjunto de n palavras e a matriz de caracteres, ambas fornecidas
pelo teclado. O programa deve ser capaz de:
1. Verificar quais palavras foram encontradas e computar o numero de ocorrencias de cada
uma delas na matriz.
2. Escrever em um arquivo texto, a lista das palavras encontradas e a listas das palavras
nao encontradas. As duas listas devem estar em ordem alfabetica.
3. Escrever em seguida, no mesmo arquivo texto, a lista das palavras encontradas na ma-
triz, ordenadas pelo seu numero de ocorrencias (considerar ordem decrescente).
Informar tambem nestes casos, a posicao na matriz do caractere inicial de cada ocorrencia
da palavra, alem da direcao que ela se encontra na matriz. As direcoes sao definidas como:
diagonal, vertical, horizontal.
4. Informe a direcao que mais palavras foram encontradas: diagonal, vertical ou horizontal.
Informe tambem a quantidade de palavras encontradas nesta direcao.
O arquivo texto de sada deve seguir o formato apresentado no exemplo a seguir:
Exemplo:
Conjunto de palavras: CAPELA, LAPIS, FLORESTA, AULA, ALEGRIA, MANGA
Matriz:

F L O R E S T A R S
H J V N O A C I W Z
C A P E L A P I S C
I K V D I P M B S F
T Y G C S A L U O P
Q L K C A D U O L C
A A S A R P R Y O A
N E N E F T E B A U
J K D D S H W L F L
I O T U G J J V A A

Arquivo de sada:
Lista de palavras encontradas em ordem alfabetica:
186 CAPITULO 6. MATRIZES

AULA
CAPELA
FLORESTA
LAPIS
Lista de palavras nao encontradas em ordem alfabetica:
ALEGRIA
MANGA
Lista de palavras encontradas, ordenadas pelo numero de ocorrencias na matriz:
CAPELA 2 (2, 0) horizontal (4, 3) diagonal
AULA 1 (6,9) vertical
FLORESTA 1 (0, 0) horizontal
LAPIS 1 (2,4) horizontal
Direcao em que mais palavras foram encontradas: horizontal
Numero de palavras encontradas nesta direcao: 3

6.8 Topicos Avancados


Definicao Dinamica de uma matriz
Alem da definicao estatica abordada na secao 6.2.1, uma matriz pode ser definida dinamica-
mente. Nesse caso o tamanho de suas dimensoes so sera estabelecido durante a execucao do
programa, conforme e visto no exemplo 6.21:

1 # include < stdio .h >


2
3 main () {
4
5 int n , m ;
6
7 printf ( Forneca as dimens~ o es da matriz : ) ;
8 scanf ( % d % d ,&n ,& m ) ;
9 int matriz [ n ][ m ];
10 .
11 }

Exemplo 6.21: Definicao dinamica

Nesse exemplo a matriz so sera definida apos a execucao da funcao scanf, onde o tamanho
de suas dimensoes sera lido do teclado.
Captulo 7

Apontadores
Autores:

Igor Magri Vale


Douglas Almonfrey
Flavio Varejao

Objetivos:

Definir o que sao apontadores;

Apresentar a sintaxe de apontadores;

Mostrar a importancia dos apontadores na passagem de parametros sem copia, no retorno


de multiplos valores nas funcoes e na alocao dinamica de memoria;

Apresentar os principais problemas no uso de apontadores;

Definir o TAD Lista Encadeada e suas principais operacoes;

Neste captulo procurou-se dar enfase a aspectos relevantes, de caracter introdutorio, para
que este texto seja utilizado como primeira leitura, e posterior consulta, sobre apontadores.

7.1 Variaveis Apontadores


Em um programa, cada variavel possui seu endereco e um conteudo. A Figura 7.1 exibe cinco
enderecos de memoria e seus respectivos conteudos.
Os apontadores sao um tipo especial de variavel, cujo conteudo nao e um simples valor, e sim
um endereco de memoria. Na Figura 7.2, pode ser observado que o conteudo de uma variavel
apontador corresponde a um endereco de uma variavel nao-apontador.
Neste captulo, serao estudados os apontadores, tambem chamados de ponteiros. Como os
apontadores sao variaveis que guardam enderecos, trata-se de um conceito de baixo nvel, muito
ligado a arquitetura de computadores. Apontadores sao ferramentas poderosas na linguagem de

187
188 CAPITULO 7. APONTADORES

Figura 7.1: Formato Abstrato da Memoria

Figura 7.2: Apontadores e Nao-Apontadores

programacao C. Dentre diversas funcoes, apontadores permitem passagem de parametros sem


copia de muitos dados, retorno de mltiplos valores em funcoes, alocacao dinamica de memoria e
construcao de listas encadeadas.
No decorrer do captulo, serao utlizados modelos de figuras ilustrativas para auxiliar na
explicacao. Na Figura 7.3, tem-se o modelo para representar uma variavel e seu endereco.
Como pode ser observado na Figura 7.3, um quadrado sera usado como formato abstrato
de variavel de memoria. Dentro deste quadrado estara representado o conteudo da variavel.
Alem disso, um crculo negro no seu vertice superior esquerdo representara abstratamente seu
endereco.
Ja para representar a ligacao entre um apontador e a variavel que esta sendo apontada por
ele, sera utilizado o modelo da Figura 7.4. Tal ligacao e ilustrada por meio de uma seta, que sai
da variavel apontador, e aponta o cculo negro (endereco) da variavel nao-apontador.

7.2 A Sintaxe dos Apontadores


Na linguagem C, o operador asterisco * e que especifica se uma variavel guarda um endereco,
ou seja, se e um apontador. A declaracao de um apontador segue o seguinte formato:
7.2. A SINTAXE DOS APONTADORES 189

Figura 7.3: Formato Abstrado de uma Variavel

Figura 7.4: Abstracao de uma Variavel Apontador

<tipo do apontador> * <nome da variavel>

No lugar do tipo do apontador, devem-se utilizar alguns dos tipos padroes da linguagem C,
como char, int e float, ou ate mesmo novos tipos definidos pelo programador. No Exemplo 7.1,
tem-se como criar apontadores dos tipos char, int, float e tAluno.

1 typedef struct {
2 char nome [30];
3 int matricula ;
4 } tAluno ;
5
6 main ( ) {
7 char * c ;
8 int * p ;
9 float * f ;
10 tAluno * x ;
11 }
190 CAPITULO 7. APONTADORES

Exemplo 7.1: Declaracao de apontadores

Assim, no codigo do Exemplo 7.1, c pode apontar para uma area de memoria que guarda
uma variavel do tipo caracter, enquanto p, f e x dos tipos inteiro, ponto flutuante e tAluno,
respectivamente.
Dois outros operadores tambem estao associados a sintaxe de apontadores: o operador en-
dereco de memoria e o operador seta. Eles serao discutidos nas duas proximas subsecoes.

7.2.1 Operador Endereco de Memoria


Em C, o operador unario &, quando aplicado a uma variavel, retorna o endereco dela. Portanto,
quando um apontador e declarado, pode-se inicializa-lo usando o operador &, que faz o apontador
receber um endereco de uma variavel ja existente.
O Exemplo 7.2 mostra o uso do operador (&) para inicializar uma variavel apontador. A
variavel p recebe o endereco da variavel d. Note que ambas variaveis sao do mesmo tipo, pois
e fundamental que o apontador aponte para uma variavel de tipo compatvel com a declaracao
dele.

1 int * p ;
2 int d = 10;
3 p = &d;
4 printf ( " % p \ n % p \ n " , &d , p ) ;

Exemplo 7.2: Inicializacao de um apontador usando o operador endereco de memoria

No Exemplo 7.2, serao impressos o endereco de d, e logo apos, o valor do apontador p,


que e o endereco de d. Portanto, dois valores iguais serao impressos na tela. O %p, utilizado
no Exemplo 7.2, e usado pela funcao printf para identificar o tipo de valor que sera impresso,
neste caso %p informa que o valor de um apontador sera impresso, ou seja, o endereco de uma
variavel.

7.2.2 O Operador Seta


Outro operador associado a apontadores e a seta ( >). Quando um apontador indica uma
variavel do tipo estrutura, para que se acesse os atributos desta variavel, ao inves do ponto (.),
utiliza-se ( >). Observe o Exemplo 7.3.

1 typedef struct {
2 char nome [30];
3 int matricula ;
4 } tAluno ;
5
6 main ( ) {
7.3. ACESSO A VARIAVEL POR MEIO DE APONTADORES 191

7 tAluno * x ;
8 tAluno y ;
9
10 x = &y;
11 x - > matricula = 2031;
12
13 printf ( " % d \ n " , y . matricula ) ;
14 }

Exemplo 7.3: Acesso ao atributo de uma estrutura por meio do operador seta

Note que no Exemplo 7.3, x e um apontador para uma estrutura tAluno e y uma variavel
tAluno. Apos fazer x apontar para o endereco de y, para acessar a matrcula de y atraves de x,
deve ser utilizado o operador >. Ja para imprimir o atual valor da matricula de y, que passou
a ser 2031, basta utilizar o ponto.

7.3 Acesso a Variavel por Meio de Apontadores


O operador unario * tem uma outra funcao alem de especificar uma multiplicacao ou se uma
variavel e apontador. Este operador tambem pode retornar o valor armazenado na variavel
apontada pelo apontador. Por isso, apontadores podem ser usados para atribuir valores a outras
variaveis. Observe o Exemplo 7.4.

1 int *p;
2 int c = 10;
3 int d;
4 p = &c;
5 d = *p;

Exemplo 7.4: Acesso ao conteudo de variaveis por meio de apontadores

No Exemplo 7.4, p recebe o endereco de c. Atraves de p portanto, pode-se agora acessar o


valor de c. Com esse recurso, na quinta linha do Exemplo 7.4, d recebe o valor de c por meio
de p.
Uma variavel pode ser atribuda atraves do uso de apontadores. No Exemplo 7.5, a variavel
c tem seu valor alterado atraves de uma atribuicao feita a p.

1 float * p ;
2 float c = 15;
3 p = &c;
4 * p = * p + 1;
5 printf ( " % f " , c ) ;

Exemplo 7.5: Alterando valor de variaveis por meio de apontadores


192 CAPITULO 7. APONTADORES

Pode ser observado no Exemplo 7.5, que p aponta para c. Assim, a expressao na linha 4
incrementa o conteudo da variavel apontada por p em uma unidade, ou seja, c passa a ter o
valor 16, o qual e impresso na tela.
Uma variavel pode receber o valor de outra por meio de apontadores. Na linha 7 do Exemplo
7.6, a variavel i apontada por v tem seu valor alterado para o de d, pois d esta apontado por j.

1 float * v ;
2 float * j ;
3 float d = 15.0;
4 float i = 17.0;
5 j = &d;
6 v = &i;
7 *v = *j;
8 v = j;

Exemplo 7.6: Alterando valor de variaveis atraves de apontadores

Um apontador tambem pode ser atribudo a outro apontador. No Exemplo 7.6, linha 8, v
passa a apontar para o mesmo endereco que j aponta.
Por fim, esse acesso a variaveis atraves de apontadores e muito importante, pois e por
meio deste recurso que as variaveis passadas como parametros de uma funcao sao alteradas
definitivamente, dentro da propia funcao.

7.4 Uso de Apontadores nas Passagens de Parametros


O conceito de funcao em C, conforme ja discutido em captulos precedentes, e fundamental.
Uma das principais vantagens de apontadores, no que diz respeito a funcao, e a passagem de
valores sem copia.
Na passagem por copia, os variaveis que sao passadas como argumentos da funcao tem seus
valores copiados. Sao essas copias que a funcao utilizara no decorrer da sua execucao. Assim,
qualquer alteracao dos valores das copias nao implicara em mudanca nas variaveis originais.
Por meio de apontadores, e possvel alterar os valores das variaveis passadas como argumentos
para uma funcao. Ao declarar que um parametro de uma funcao e apontador, deve-se passar
um endereco de uma variavel como argumento correspondente. Nesse tipo de passagem, nao e
copiado o valor da variavel argumento, mas com o endereco dela, pode-se acessar o conteudo e
modifica-lo no decorrer da execucao da funcao. O Exemplo 7.7 mostra a passagem por copia e
por apontadores.

1 void adicionaX1 ( int x , int b ) {


2 b = b + x;
3 }
4
5 void adicionaX2 ( int x , int * b ) {
6 *b = *b + x;
7.4. USO DE APONTADORES NAS PASSAGENS DE PARAMETROS 193

7 }
8
9 main ( ) {
10 int a = 0;
11
12 adicionaX1 (10 , a ) ;
13 printf ( " a = %d\n", a);
14 adicionaX2 (10 , & a ) ;
15 printf ( " a = %d\n", a);
16 }

Exemplo 7.7: Passagem de valor por copia e atraves de apontadores

Em adicionaX1 , no Exemplo 7.7, uma copia do valor de a e passada com argumento da


funcao, ou seja, b recebe uma copia do valor 0 de a. Dentro da funcao, o valor de b muda para
10, mas o de a continua o mesmo. Assim, o primeiro valor impresso e 0. Ja em adicionaX2 , o
endereco de a e passado para o ponteiro b e, na execucao da funcao, e adicionado x ao conteudo
da variavel apontada por b, o que faz a ter seu valor modificado para 10. Portanto, o que
impresso no segundo printf e a = 10. A Figura 7.5 ilustra as passagens de parametro de a para
b, em adicionaX1 e adicionaX2 .

Figura 7.5: Passagem de Valor por Copia e por Apontadores

Na Figura 7.5, repare que na passagem por copia, o valor de a e copiado para o conteudo de
b, enquanto que na passagem por apontador, b passa a apontar para o endereco de a.
Uma aplicacao da passagem de valor por apontadores e proporcionar que uma funcao retorne
multiplos valores. O Exemplo 7.8 mostra como isso e feito.

1 void troca ( int *x , int * y ) {


2 int aux ;
194 CAPITULO 7. APONTADORES

3
4 aux = * x ;
5 *x = *y;
6 * y = aux ;
7 }
8
9 main ( ) {
10 int a, b;
11 a = 10;
12 b = 20;
13
14 troca (& a , & b ) ;
15 printf ( " a = %d , b = % d \ n " , a , b ) ;
16 }

Exemplo 7.8: Retorno de multiplos valores por uma funcao

No Exemplo 7.8, desejava-se uma funcao para trocar o conteudo de a e b. Contudo, uma
funcao pode retornar apenas um valor no return. Assim, por meio dos apontadores, os dois
conteudos sao alterados e retornados no escopo da funcao, pois a e b sao passados sem copia,
ou seja, sao passados seus enderecos como parametros da funcao troca.

7.5 Alocacao Dinamica de Memoria


Apontadores tambem sao fundamentais no que diz respeito a alocacao dinamica, economia de
memoria e tempo de execucao.
Apontadores permitem alocacao de memoria em tempo de execucao (alocacao dinamica).
Para tanto, uma variavel apontador aponta para uma area dememoria livre definida durante a
execucao do programa. As Figuras 7.6 e 7.7 ilustram o processo de alocacao dinamica.

Figura 7.6: Areas de Memoria Livres

A Figura 7.6 mostra as areas livres da memoria. Em um determinado momento, um endereco,


7.5. ALOCACAO DINAMICA DE MEMORIA 195

que era livre, passa a ser apontado por uma variavel apontador, podendo assim ser utilizado, o
que e ilustrado na Figura 7.7.

Figura 7.7: Area de Memoria Alocada Dinamicamente

Em C, a funcao malloc (abreviatura de memory allocation), da biblioteca padrao stdlib.h,


aloca um bloco de bytes consecutivos na memoria do computador e devolve o endereco desse
bloco. O Exemplo 7.9 mostra seu uso.

1 int * p
2 p = ( int *) malloc (4*( sizeof ( int ) ) ) ;

Exemplo 7.9: Alocacao dinamica de quatro inteiros

No Exemplo 7.9, a expressao sizeof retorna o numero de bytes de um tipo passado como
parametro, no caso int. Alem disso, como a funcao malloc devolve um apontador do tipo void
(void *) para um bloco de bytes consecutivos, esse apontador deve ser convertido em apontador
para o tipo desejado, por meio do cast (int *) nesse caso, uma vez que a variavel p e do tipo int,
garantindo assim, a consistencia de tipos.
A Figura 7.8 ilustra um espaco de 4 inteiros, alocados dinamicamente pelo Exemplo 7.9.
Um espaco de memoria deve ser desalocado quando nao e mais necessario. Isto significa
que a area de memoria que foi apontada por um apontador agora passa a ser novamente livre.
Somente variaveis alocadas dinamicamente podem ser desalocadas em tempo de execucao.
A desalocacao de memoria, em C, esta no Exemplo 7.10.

1 int * p ;
2 p = ( int *) malloc (4 * sizeof ( int ) ) ;
3 free ( p ) ;

Exemplo 7.10: Utilizacao da funcao free para liberar memoria

No Exemplo 7.10, a funcao free, assim como malloc, esta na biblioteca stdlib.h e desaloca
uma area de memoria. Note que a funcao free deve receber como parametro uma variavel
196 CAPITULO 7. APONTADORES

Figura 7.8: Vetor de Inteiros Alocados Dinamicamente

apontador. Assim, o area de memoria apontada por ele sera entao liberada. A Figura 7.9 ilustra
a desalocacao de memoria.

Figura 7.9: Desalocacao Dinamica de Memoria

Na Figura 7.9, pode-se observar a que no decorrer da execucao do programa, a variavel


apontador deixa de apontar para area de memoria, que volta para a grupo de enderecos livres.
O processo de alocacao dinamica pode ser usado para ler linhas de um documento de texto.
Usando-se alocacao dinamica, define-se o tamanho n x m fixo da matriz limitando o tamanho do
texto e da linha que podem ser lidos. Para se conseguir ler todas as linhas, define-se o tamanho
m da matriz como o tamanho da maior linha que sera lida e n um numero alto suficiente para
armazenar todas as linhas. Entretanto, esse procedimento acarreta perda de memoria com linhas
pequenas, que nao utilizam todo o espaco alocado para elas, e tratando-se de textos com grandes
quantidades de linhas essa perda torna-se consideravel.
Considere a Figura 7.10, que ilustra como seria o processo de alocacao dinamica de uma
matriz para ler um pequeno texto de caracteres. Percebe-se facilmente para a quarta linha da
matriz, que 9 espacos seriam perdidos.
7.5. ALOCACAO DINAMICA DE MEMORIA 197

Figura 7.10: Matriz que Armazena um Texto

Para contornar o problema de perda de memoria com espacos nao utilizados, faz-se uso da
alocacao dinamica. A cada necessidade de se ler uma linha com certo numero de caracteres,
aloca-se o exato tamanho da linha, ou seja, a quantidade de caracteres da string que sera lida.
O Exemplo 7.11 mostra a implementacao em linguagem C de uma matriz de caracteres,
dinamicamente alocada. Esta matriz possui 4 linhas de tamanhos alocados de acordo com o
tamanho da string armazenada, e cada linha da matriz recebe uma linha do texto da Figura
7.10. A matriz declarada no Exemplo 7.11 e simplesmente um vetor de apontadores para strings
e os elementos do vetor g contem os enderecos dos primeiros elementos de cada string. Esse
vetor de apontadores para strings e chamado matriz de apontadores ou matriz de strings. Na
Figura 7.11 esta ilustrada a matriz de strings correspondente ao texto.

Figura 7.11: Matriz de Strings

1 main ( ) {
2 char * g [4];
3 g [0] = ( char *) malloc ( strlen ( " Joao ama Maria " ) * sizeof ( char ) ) ;
4 strcpy ( g [0] , " Joao ama Maria " ) ;
5 g [1] = ( char *) malloc ( strlen ( " Maria ama pedro " ) * sizeof ( char ) ) ;
6 strcpy ( g [1] , " Maria ama pedro " ) ;
7 g [2] = ( char *) malloc ( strlen ( " Ana ama quem ama Maria " ) * sizeof ( char ) ) ;
8 strcpy ( g [2] , " Ana ama quem ama Maria " ) ;
9 g [3] = ( char *) malloc ( strlen ( " Quem Ana ama ? " ) * sizeof ( char ) ) ;
10 strcpy ( g [3] , " Quem Ana ama ? " ) ;
198 CAPITULO 7. APONTADORES

11 }

Exemplo 7.11: Definicao de uma matriz de caracteres dinamica

7.6 Problemas Gerados por Apontadores


Apesar de serem muito importantes, quando usados inapropiadamente, apontadores sao fontes de
erros difceis de serem encontrados. Apontadores nao inicializados, objetos pendentes, referencia
pendente, e programacao macarronica sao alguns desses erros.

7.6.1 Apontadores Nao Inicializados


Quando se trata de apontadores, um erro muito comum e utiliza-lo antes de faze-lo apontar
para algum endereco valido, ou seja, sem inicializa-lo. O Exemplo 7.12 mostra como isso pode
ocorrer. O valor da variavel apontada por p recebe o valor de h, mas p nao foi inicializado ainda,
ou seja, nao apontava para nenhum espaco de memoria valido.

1 float *p , h = 15.0;
2 *p = h;

Exemplo 7.12: Apontador nao inicializado

As consequencias da utilizacao de um apontador nao inicializado sao imprevisveis, podendo


provocar uma paralisacao do sistema.

7.6.2 Objetos Pendentes


Objetos pendentes ocorrem quando uma area de memoria alocada fica inacessvel. Veja o Exem-
plo 7.13. A area de memoria apontada por p fica inacessvel, pois p passa a apontar para o mesmo
endereco de g. A Figura 7.12 ilustra como ocorre objetos pendentes.

Figura 7.12: Objetos pendentes


7.6. PROBLEMAS GERADOS POR APONTADORES 199

1 int * p = ( int *) malloc ( sizeof ( int ) ) ;


2 int * q = ( int *) malloc ( sizeof ( int ) ) ;
3 p = q;

Exemplo 7.13: Objetos pendentes

7.6.3 Referencia Pendente


Quando e liberado um endereco de memoria, que e apontado por mais de uma variavel apontador,
ocorre a referencia pendente. Assim, algumas variaveis ficam referenciando um endereco que
foi desalocado. Veja o Exemplo 7.14 que mostra um caso de referencia pendente. O endereco
da variavel h e liberado, portanto p aponta para um espaco de memoria liberado. A Figura
7.13 ilustra o problema de referencia pendente. Um espaco de memoria livre pode ser alocado
a qualquer momento por outras variaveis do sistema. Ao se utilizar variaveis que tiveram seu
espaco de memoria liberado, pode-se estar acessando areas importantes do sistema, que alocacou
o espaco de memoria que acabara de ser liberado, e isto pode provocar uma paralisacao do
sistema.

Figura 7.13: Referencia pendente

1 int * p ;
2 int * h ;
3 h = ( int *) malloc ( sizeof ( int ) ) ;
200 CAPITULO 7. APONTADORES

4 p = h;
5 free ( h ) ;

Exemplo 7.14: Referencia pendente

7.6.4 Programacao Macarronica


Um mesmo endereco de memoria pode ser apontado por varios apontadores. Isso pode tornar
confuso o entendimento do programa e a identificacao de erros. O Exemplo 7.15 mostra um
trecho de codigo difcil de entender devido ao uso indiscriminado de apontadores.

1 typedef struct {
2 int numerosala ;
3 int numeroalunos ;
4 } tsala ;
5
6 acresentaAluno ( tsala * g ) {
7 g - > numeroalunos = g - > numeroalunos + 1;
8 }
9
10 main ( ) {
11 int p ;
12 tsala sala ;
13 sala . numerosala = 1;
14 sala . numeroalunos = 0;
15 tsala * x ;
16 tsala * y ;
17
18 y = & sala ;
19 x = y;
20 acrescentaAluno ( x ) ;
21 acrescentaAluno ( y ) ;
22 }

Exemplo 7.15: Programacao macarronica causada pelo uso indiscriminado de apontadores.

No Exemplo 7.15, o numero de alunos da variavel sala e alterado duas vezes, uma pelo
apontador x, outra por meio do apontador y. Portanto, quando apontadores sao usados de uma
maneira indiscriminada, pode-se diminuir a legibilidade do codigo.

7.7 TAD Implementacional Lista Encadeada tLista


Um exemplo de implementacao utilizando os apontadores e o TAD Lista Encadeada.
Uma lista encadeada e uma estrutura cujos elementos sao acessados sequencialmente por
meio de apontadores. Nessa estrutura estao armazenados os apontadores para o comeco e
o fim de uma sequencia de blocos, conhecidos como nos, alem de uma variavel inteira que
7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 201

indica a quantidade atual destes na lista. Cada um dos nos, por sua vez, possuem informacoes
armazenadas, um apontador para o proximo no e no caso de uma lista duplamente encadeada,
tambem um apontador para o no anterior. Na Figura 7.14, tem-se um esquema de uma lista
simplesmente encadeada, com tres nos e com apontadores para o primeiro e o ultimo elemento.

Figura 7.14: Esquema de Uma Lista Simplesmente Encadeada

Note que cada um dos nos possui um apontador para o proximo, com excecao do ultimo, no
qual ha um apontador para null, pois nao ha proximo. Repare tambem que a estrutura da lista
possui apontadores para o incio e o fim da lista, alem de armazenar seu tamanho corrente, no
caso, tres elementos.
Pode-se perceber que incluir em uma lista encadeada torna-se um processo simples. Se um
novo dado necessita ser includo, basta alocar um espaco de memoria para o no, atualizar os
apontadores da sequencia de nos e a quantidade deles na lista. Excluir um dado da lista e um
processo semelhante, com a diferenca de que, ao inves de se alocar, desaloca-se uma area da
memoria.
Vale destacar que as estruturas da lista e de cada um dos dados armazenados em um no
sao exemplos do uso de abstracao, pois se encapsulam informacoes, seleciona-se o que deve
ser apresentado de uma funcao a outra e possibilita-se o trabalho em nveis de implementacao
e uso, pratica fundamental na modularizacao de um codigo. Conforme visto no captulo 4,
pode-se dizer que o tipo de abstracao utilizada na lista encadeada e uma abstracao de dados
implementacional, por meio do uso de Tipo Abstratos de Dados - TADs.
Anteriormente foi visto que TADs sao conjuntos de valores com comportamento uniforme
definido por operacoes, que sao tipicamente os componentes responsaveis pela interface entre
os diferentes nveis e modulos de implementacao. Essa interface e responsavel por encapsular e
proteger os dados.
A implementacao do TAD lista encadeada tLista sera apresentada a seguir. Tambem serao
detalhadas varias operacoes fundamentais sobre a lista. Inicialmente, sera definido o tipo tNo,
o qual e um componente importante de tLista.
202 CAPITULO 7. APONTADORES

7.7.1 Definicao do Tipo tNo


De acordo com o que foi dito ate agora, cada no deve ser responsavel por armazenar um dado,
alem de conter um apontador para o proximo no, no caso de uma lista simplesmente encadeada.
Assim, segue abaixo um exemplo de como pode ser a estrutura tNo:

1 typedef int tInfo ;


2
3 typedef struct no {
4 tInfo info ;
5 struct no * proximo ;
6 } tNo ;

Exemplo 7.16: Estrutura tNo

Ha necessidade de se incluir o termo no apos o primeiro struct porque, ao se incluir a


estrutura proximo, ocorre uma definicao recursiva do tipo no, ou seja, a estrutura no possui um
campo do mesmo tipo que ela. Assim, caso fosse retirado o primeiro termo no, nao se saberia
qual o tipo de proximo. Tambem e importante dizer que a variavel inteira info poderia ser
substituda por qualquer outro tipo, tais como char, float e, ate mesmo, estruturas compostas
por uma variedade de tipos. Bastaria alterar o termo int na definicao do tipo tInfo.
A partir de agora, devido ao uso do typedef, sempre que for utilizado o termo tNo antes de
um identificador, significa que esta sendo declarada uma estrutura como a do Exemplo 7.16.
Como frequentemente sera necessario incluir novos nos, torna-se importante criar uma funcao
que aloque dinamicamente um espaco de memoria para um no. O Exemplo 7.17 mostra uma
forma de implementar tal funcao.

1 tNo * criaNo ( tInfo elem ) {


2 tNo * no = ( tNo *) malloc ( sizeof ( tNo ) ) ;
3 no - > proximo = NULL ;
4 no - > info = elem ;
5 return no ;
6 }

Exemplo 7.17: Implementacao da operacao criaNo

Apos a alocacao do espaco de memoria, o apontador proximo e inicializado como NULL. Em


seguida, o campo info do no criado e associado ao valor do elemento passado como argumento.
Por fim, e retornado um apontador para a area de memoria inicializada na funcao.

7.7.2 Atributos de tLista


Sera utilizada uma estrutura para tLista com apontadores para o primeiro e ultimo no. Essa
estrututa e chamada cabecalho da lista e cabe ressalvar que existem diferentes implementacoes.
A adotada aqui (ver Exemplo 7.18) e apenas uma delas.
7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 203

1 typedef struct {
2 tNo * primeiro ;
3 tNo * marcador ;
4 tNo * ultimo ;
5 int tam ;
6 } tLista ;

Exemplo 7.18: Estrutura tLista

Repare que tres atributos de tLista sao apontadores para o tipo tNo, o qual foi definido na
secao anterior. Dois deles, primeiro e ultimo, apontam para o comeco e para o fim da lista,
enquanto marcador aponta para qualquer posicao. A variavel inteira tam e a responsavel por
guardar o atual tamanho, ou numero de nos, da lista.
O apontador marcador e responsavel por apontar o no corrente num processo de busca
sequencial. Por exemplo, na operacao de imprimir a lista, que sera explicada mais adiante,
a informacao e extrada do no apontado por marcador e, este vai sendo posicionado desde o
primeiro no ate o ultimo.

7.7.3 Operacoes de tLista


Como ja se tem definidas as abstracoes de dados, tNo e tLista, necessita-se criar as operacoes
que serao responsaveis por trabalhar com estes conjuntos de valores, concluindo assim, a imple-
mentacao do TAD lista encadeada.

Operacoes Construtoras
As operacoes construtoras sao responsaveis por garantir a alocacao da estrutura tLista, alem de
inicializa-la com os valores desejados, geralmente com os apontadores apontando para NULL e
as variaveis numericas com valor zero.

Inicializacao (vazia)

O Exemplo 7.19 mostra a funcao de inicializacao de uma lista.

1 void iniciaLista ( tLista * lista ) {


2 lista - > primeiro = NULL ;
3 lista - > marcador = NULL ;
4 lista - > ultimo = NULL ;
5 lista - > tam = 0;
6 }

Exemplo 7.19: Inicializacao de uma lista

Na funcao iniciaLista os apontadores primeiro, marcador e ultimo sao inicializados com


NULL, pois nao ha nos na lista e, garante-se assim, que estes apontem para uma area de memoria
204 CAPITULO 7. APONTADORES

indevida. Em seguida, o tamanho da lista e zerado uma vez que o atual tamanho da lista e nulo.

Operacoes Analisadoras
Em algumas situacoes e importante analisar a posicao atual do marcador, tal como se ele esta
no fim da lista, e se a lista esta vazia. Por exemplo, ao se percorrer a lista, uma condicao de
parada importante e quando o final da lista for alcancado e, ao se incluir ou excluir um elemento
da lista, e necessario saber se a lista esta vazia.
Essas analises sao importantes para que nao se cometa acessos indevidos com os apontadores,
um dos problemas explicados neste capitulo.

Final

O Exemplo 7.20 mostra a implementacao da funcao analisadora de fim da lista.

1 int finalLista ( tLista * lista ) {


2 return ( lista - > marcador == NULL ) ;
3 }

Exemplo 7.20: Verificacao de fim da lista

Essa funcao simplesmente retorna o resultado da comparacao do marcador da lista com


NULL. Isso porque o ultimo no da lista aponta tem como proximo este valor, bem como a
lista vazia, que tem seus apontadores iniciais todos apontando para NULL. Na Figura 7.15, o
marcador nao esta apontando para NULL, assim nao e o fim da lista e a funcao final retorna 0.

Figura 7.15: Marcador Apontando para uma Posicao da Lista

Ja na Figura 7.16 o marcador aponta para NULL, ou seja para o fim da lista. Assim, o
retorno da funcao e 1.

Vazia

O Exemplo 7.21 mostra a implementacao da funcao analisadora de lista vazia.


7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 205

Figura 7.16: Marcador Apontando para o Fim da Lista

1 int vaziaLista ( tLista * lista ) {


2 return ( lista - > tam == 0) ;
3 }

Exemplo 7.21: Verificacao se a lista esta vazia

Assim como na analise do fim da lista, vaziaLista retorna o resultado de uma comparacao.
Nesse caso, porem, se o tamanho da lista e igual zero. Como o tamanho da lista e inicializado
com este valor e, sempre que um novo no for adicionado, ou removido, este tamanho deve ser
atualizado, a unica possibilidade de ele ser zero e quando a lista estiver realmente vazia.
Um exemplo da utilizacao de vazia e quando se deseja incluir, ou excluir um no da lista. E
necessario um tratamento especial na primeira inclusao, pois o primeiro e ultimo elemento sao
os mesmos, e na exclusao, uma vez que seria um erro tentar remover elementos de uma lista
vazia.

Operacoes Modificadoras
Uma das caractersticas interessantes da lista encadeada e a facilidade de se incluir e excluir
elementos, em tempo de execucao, com praticidade, uma vez que e necessario somente alocar e
desalocar nos, respectivamente. E importante, entao, conhecer as funcoes de insercao e exclusao,
tratadas como operacoes modificadoras, pois alteram a composicao corrente da lista. Seguem,
assim, os exemplos de implementacao.

Insercao no Final

No Exemplo 7.22, o novo elemento e a lista no qual ele sera inserido sao passados como argu-
mentos. A passagem do cabecalho lista e feita por apontador, pois assim, qualquer modificacao
em seus elementos e feita diretamente na funcao, sem necessidade de passar toda lista por copia
e retorna-la ao fim da execucao da funcao. O elemento sera includo no fim da lista, mas dife-
rentes implementacoes, nas quais ele e inserido no comeco, ou numa dada posicao, tambem sao
possveis.
206 CAPITULO 7. APONTADORES

1 void incluirFimLista ( tLista * lista , tInfo elem ) {


2 tNo * no ;
3
4 no = criaNo ( elem ) ;
5 if ( vaziaLista ( lista ) ) {
6 lista - > primeiro = no ;
7 } else {
8 lista - > ultimo - > proximo = no ;
9 }
10 lista - > ultimo = lista - > marcador = no ;
11 lista - > tam ++;
12 }

Exemplo 7.22: Funcao de inclusao de um novo no na lista

Como pode ser visto no exemplo acima, inicialmente o novo no deve ser criado para armazenar
o elemento a ser includo. Para isso e utilizada a funcao inicializadora criaNo, que aloca
um espaco de memoria do tamanho de tNo, faz a chave ser igual ao elemento passado como
argumento e retorna um apontador para o espaco alocado. A Figura 7.17 mostra o estado da
lista, antes da inclusao, e o novo no a ser includo.

Figura 7.17: Lista Antes da Inclusao

Agora que se ja se tem o novo no, e necessario inclu-lo na lista, ou seja, atualizar o valor de
tam e os apontadores. Como a lista tem mais um no, seu tamanho deve ser incrementado em
uma unidade. Quanto a atualizacao dos apontadores, os apontadores ultimo e marcador devem
apontar para o no includo, respectivamente, porque ele e includo no fim da lista e no ultimo
no acessado. Antes, porem, e importante verificar se a lista esta vazia, pois caso esteja, quem
tambem vai apontar para no a ser includo, sera o apontador primeiro da lista, caso contrario,
sera apontador proximo do ultimo no da lista. Vale destacar que a analise se a lista esta vazia
e um exemplo de uso da funcao vaziaLista, que foi explicada anteriormente.
A Figura 7.18 exibe a lista apos a inclusao do novo no.

Exclusao
7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 207

Figura 7.18: Lista Apos a Inclusao

O Exemplo 7.23 mostra a implementacao de uma operacao de exclusao.

1 void excluirNoLista ( tLista * lista , int pos ) {


2 tNo * auxA , * auxB ;
3 int i ;
4
5 if ( pos < 0 || pos >= lista - > tam )
6 return ;
7
8 auxA = lista - > primeiro ;
9 for ( i =0; i < pos ; i ++) {
10 auxB = auxA ;
11 auxA = auxA - > proximo ;
12 }
13 if ( auxA != lista - > primeiro ) {
14 auxB - > proximo = auxA - > proximo ;
15 if ( auxA == lista - > ultimo ) {
16 lista - > ultimo = auxB ;
17 }
18 } else {
19 lista - > primeiro = auxA - > proximo ;
20 if ( lista - > ultimo == auxA ) {
21 lista - > ultimo = auxA - > proximo ;
22 }
23 }
24 lista - > marcador = NULL ;
25 lista - > tam - -;
26 free ( auxA ) ;
27 }

Exemplo 7.23: Funcao de exclusao de um no da lista


208 CAPITULO 7. APONTADORES

Na funcao de exclusao, sao passadas como parametros a lista e a posicao do no a ser excludo.
Assim, e importante saber, inicialmente, se o valor de pos e valido e, caso nao seja, a funcao e
encerrada.
Sao criados dois apontadores auxiliares do tipo tNo: auxA e auxB. Para buscar o no da
posicao pos, auxA aponta para o primeiro no da lista e inicia-se um laco for, ate que o valor de
i, que inicialmente e zero, seja pos. A cada iteracao desse laco, auxB vai apontar para o ultimo
no apontado por auxA e este, por sua vez, aponta para o proximo no da lista. Portanto, quando
auxA estiver apontando para o no a ser excludo, o anterior a ele e apontado por auxB, o que e
importante na hora de se atualizarem os apontadores, conforme sera explicado.
Apos ter encontrado o no da posicao pos, tem que se analisar quatro situacoes possveis: 1)
esse no a ser excludo nao ser nem o primeiro e nem o ultimo no da lista; 2) ser o ultimo; 3) ser
o primeiro; 4) e ser o unico no. No caso 1, basta fazer o apontador proximo do no apontado por
auxB apontar para o no apontado pelo proximo do no apontado por auxA. No caso 2, tambem
se deve fazer o ultimo no da lista ser auxB. Vale lembrar que, no caso de auxA ser o ultimo,
ao se fazer o proximo de auxB ser o proximo de auxA, esta se fazendo simplesmente auxB ter
como proximo o valor NULL. Ja no caso 3, coloca-se o proximo de auxA como o no inicial e,
no caso 4, faz-se tambem o apontador ultimo da lista apontar para o proximo de auxA. Nesse
ultimo caso, esta se redirecionando os apontadores, primeiro e ultimo da lista, para NULL.
Agora que ja se tem os apontadores atualizados e retirado o no que sera excludo da sequencia
de nos, coloca-se o marcador da lista como NULL, pois sera perdido o ultimo no acessado. Entao,
diminui-se o tamanho da lista e, finalmente, libera-se o espaco de memoria apontado por auxA.
A Figura 7.19 ilustra a lista da Figura 7.16 apos a exclusao do no 2.

Figura 7.19: Lista Apos a Exclusao do No 2

Operacoes Produtoras
As funcoes produtoras possibilitam extrair alguma informacao da lista. Dividem-se em duas
categorias: as que permitem posicionar o marcador no no, cuja informacao sera extrada, e as
que realmente obtem a informacao. Serao mostradas inicialmente as operacoes que marcam a
posicao de um elemento. Apresentam-se a que posiciona o marcador no primeiro no da lista e a
que posiciona no proximo no do marcador atual. A fim de ilustrar a utilizacao destas funcoes,
sera dado como exemplo o uso delas na funcao imprimir.
O Exemplo 7.24 e a implementacao da funcao que posiciona o marcador no primeiro no da
7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 209

lista.

1 void primeiroLista ( tLista * lista ) {


2 lista - > marcador = lista - > primeiro ;
3 }

Exemplo 7.24: Posicionado o marcador no comeco da lista

Para fazer o marcador de uma lista apontar para o primeiro elemento dela, basta igualar
o marcador ao apontador primeiro. O objetivo da operacao primeiroLista e posicionar o
marcador no incio da lista, para permitir que seja feita uma busca nos elementos da lista a
partir do seu incio.
A operacao proximoLista e mostrada no Exemplo 7.25.

1 void proximoLista ( tLista * lista ) {


2 if ( lista - > marcador != NULL ) {
3 lista - > marcador = lista - > marcador - > proximo ;
4 }
5 }

Exemplo 7.25: Posicionado o marcador no proximo no da lista

A funcao proximoLista e para posicionar o marcador, fazendo-o apontar para o seu proximo
elemento da lista. A verificacao se o marcador atual e diferente de NULL evita o acesso indevido
a memoria.
O Exemplo 7.26 mostra a operacao responsavel por retornar a informacao do no apontado
pelo marcador da lista.

1 tInfo obterInfo ( tLista * lista , int * x ) {


2 if ( lista - > marcador == NULL ) {
3 * x = 0;
4 }
5 * x = 1;
6 return lista - > marcador - > info ;
7 }

Exemplo 7.26: Funcao para recuperar a informacao de um no

A funcao obterInfo tem por objetivo retornar o conteudo do no apontado pelo marcador.
E gravado no endereco x se houve sucesso, ou nao, em se obter a informacao. Assim, impede-se
de acessar um local indevido da memoria.
O Exemplo 7.27 ilustra o uso dessas informacoes por meio do uso de uma funcao que imprime
todos os dados de uma lista. Para a funcao e passada a lista cujos elementos serao impressos.
Inicialmente primeiroLista e utilizada para posicionar o marcador no incio da lista e, em
seguida, comeca um laco enquanto o final dela nao e alcancado, verificacao feita pela funcao
finalLista, conforme descrito anteriormente.
210 CAPITULO 7. APONTADORES

1 void imprimirLista ( tLista lista ) {


2 tInfo x ;
3 int erro = 0;
4
5 primeiroLista (& lista ) ;
6 while (! finalLista (& lista ) ) {
7 x = obterInfo (& lista , & erro ) ;
8 if ( erro ) {
9 printf ( " Elemento : % d \ n " , x ) ;
10 }
11 proximoLista (& lista ) ;
12 }
13 }

Exemplo 7.27: Impressao de todos elementos de uma lista

Para cada posicao do marcador, obterInfo retorna o valor de info do no apontado pelo
marcador da lista. Esse valor e impresso caso tenha ocorrido sucesso na obtencao da informacao,
ou seja, a variavel erro ter o valor 1. O valor a ser impresso e inteiro, visto que x e do tipo tInfo
(int, como declarado anteriormente). Por fim, proximoLista posiciona o marcador no proximo
no da lista e, somente quando este marcador for NULL, o laco enquanto sera encerrado.

Operacoes Destrutoras

Quando a lista nao e mais necessaria, e importante desalocar o espaco de memoria ocupado por
seus nos. As funcoes responsaveis por desalocar a lista e seus elementos sao conhecidas como
destrutoras.

Finalizacao

A seguinte funcao para a finalizacao da lista e um exemplo de operacao destrutora. Repare


que a desalocacao de memoria e feita no por no. Note que ao final da operacao, os valores dos
atributos de lista sao modificados para apontarem para NULL para evitar que fiquem referenci-
ando areas ja desalocadas.

1 void destroiLista ( tLista * lista ) {


2 tNo * aux ;
3
4 primeiroLista ( lista ) ;
5 while (! finalLista ( lista ) ) {
6 aux = lista - > marcador ;
7 proximoLista ( lista ) ;
8 free ( aux ) ;
9 }
10 lista - > primeiro = NULL ;
11 lista - > marcador = NULL ;
7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 211

12 lista - > ultimo = NULL ;


13 lista - > tam = 0;
14 }

Exemplo 7.28: Desalocacao das areas de memoria ocupados pelos nos da lista

Semelhante a funcao de imprimir, primeiroLista e utilizada para posicionar o marcador


no inicio da lista e, em seguida, inicia-se um laco enquanto o final dela nao e alcancado.
A cada iteracao, um no auxiliar aux guarda o endereco de memoria apontado pelo marcador
da lista. Em seguida, posiciona-se o marcador no proximo no da lista e a regiao apontada por
aux e liberada com o uso do free.
Quando se chega ao final da lista, o laco enquanto sera encerrado e os atributos de listas sao
incializados, assim, seus apontadores apontam para NULL e o tamanho e zero.

7.7.4 Uso
A fim de ilustrar a utilizacao do TAD implementacional lista encadeada, sera utilizado um
cadastro simples de alunos na universidade. Veja o Pseudocodigo 7.1.

Pseudocodigo 7.1 Uso de uma lista simplesmente encadeada.


Descricao: Programa de cadastro de alunos.
Dados de Entrada: nomes e as respectivas matriculas.
Sada do Programa: lista impressa.
Inicializac~
ao da lista;
Leitura de um nome;
Leitura da matricula;
Validac~
ao da matricula
Se a matricula for menor ou igual a zero:
Parar de ler informac~
oes. Ir para "Leitura da posic~
ao";
Se n~ao for menor que zero:
Continuar a ler informac~
oes. Ir para "Leitura de um nome";
Leitura da posic~
ao
Validac~
ao da Posic~
ao
Se a posic~
ao for maior ou igual a zero:
Se a posic~
ao contiver algum no:
Excluir o no, imprimir a lista e voltar para "Leitura da posic~
ao";
Se for menor que zero:
Parar de ler informac~
oes. Ir para "Destruic~
ao da lista";
Destruic~
ao da lista.

O Pseudocodigo 7.1 descreve os passos para o programa de cadastro simples, no qual apenas
pode-se ler e armazenar algumas entradas. Apos a leitura, alguns dados podem ser excludos e
as informacoes restantes serem impressas.
212 CAPITULO 7. APONTADORES

No cadastro, havera apenas o nome e o numero de matrcula do aluno, mas vale lembrar que,
para um registro de informacoess mais detalhado, basta acrescentar mais atributos no exemplo
de estrutura tInfo do exemplo apresentado.
Observe o Exemplo 7.29, no qual se tem a transcricao do Pseudocodigo 7.1. Foram utilizadas
todas as funcoes estudadas nesta secao, porem com algumas modificacoes, uma vez que o campo
info, da estrutura do no, nao e mais um inteiro, e sim, uma outra estrutura. So estao mostradas
as estruturas e as operacoes anteriores que sofreram modificacao.

1 typedef struct {
2 int matricula ;
3 char nome [30];
4 } tInfo ;
5
6 void imprimirLista ( tLista lista ) {
7 tInfo x ;
8 int erro = 0;
9
10 primeiroLista (& lista ) ;
11 while (! finalLista (& lista ) ) {
12 x = obterInfo (& lista , & erro ) ;
13 if ( erro ) {
14 printf ( " Aluno : % s \ n " , x . nome ) ;
15 printf ( " Matricula : % d \ n \ n " , x . matricula ) ;
16 }
17 proximoLista (& lista ) ;
18 }
19 }
20
21 main ( ) {
22 tLista lista ;
23 tInfo dados ;
24 int pos ;
25
26 iniciaLista (& lista ) ;
27
28 do {
29 printf ( " Entre com o nome do aluno : " ) ;
30 scanf ( " % s " , & dados . nome ) ;
31 printf ( " Entre com a matricula do aluno : " ) ;
32 scanf ( " % d " , & dados . matricula ) ;
33 if ( dados . matricula <= 0)
34 break ;
35 incluirFimLista (& lista , dados ) ;
36 } while (1) ;
37
38 imprimirLista ( lista ) ;
39
40 printf ( " Digite a posic~ a o do elemento a ser excludo : " ) ;
41 scanf ( " % d " , & pos ) ;
7.8. EXERCICIOS RESOLVIDOS 213

42 while ( pos >= 0) {


43 excluirNoLista (& lista , pos ) ;
44 imprimirLista ( lista ) ;
45 scanf ( " % d " , & pos ) ;
46 }
47
48 destroiLista (& lista ) ;
49 }

Exemplo 7.29: Cadastro de alunos na universidade

O programa implementado no Exemplo 7.29 apenas le as informacoes do teclado enquanto


nao se digita um valor invalido, no caso matricula menor ou igual a 0. Se for digitado tal valor
para matricula o comando break faz a execucao do programa sair do laco infinito (a condicao
e sempre 1). Cada nome e matricula sao armazendos na lista e, apos o termino de leitura de
dados, eles sao impressos na tela.
Em seguida, pode-se apagar as informacoes fornecendo a posicao na lista do dado a ser
apagado. A cada elemento excludo, a lista e atualizada e impressa e, quando for passado
um valor de posicao negativo, encerra-se o laco de exclusao. Por fim, antes de se encerrar o
programa, a lista e destruda.

7.8 Exerccios Resolvidos

Exerccio Resolvido 7.1 - Acesso ao Conteudo de Variaveis por Apontadores

Para o trecho de codigo do Exemplo 7.30, ilustre os estados das variaveis apontadores e
nao-apontadores. Utilize para a ilustracao os padroes adotados na Figura 7.3 e Figura 7.4, para
representar uma variavel nao apontador e apontador respectivamente.

1 int *p;
2 int *q;
3 int a;
4 int b;
5 int c;
6
7 b = 10;
8 c = 15;
9 a = c;
10 p = &b;
11 q = &c;
12 *p = *q;

Exemplo 7.30: Codigo para o Exerccio Resolvido 7.1


214 CAPITULO 7. APONTADORES

Solucao Possvel:
Inicialmente sao criados dois apontadores para inteiro, p e q, e tres variaveis inteiras: a b e
c. Em seguida, b passa a armazenar o valor 10, enquanto c, 15. Ja a rebece o valor de c, ou
seja, 15. Isso e ilustrado nas tres primeiras cenas da Figura 7.20.

Figura 7.20: Solucao do Exerccio Resolvido 7.1

Por fim, p e q passam a apontar b e c, repectivamente. Assim, na ultima linha, o conteudo


da variavel apontada por p recebe o valor de c, pois este e a variavel apontada por q, o que faz
b receber o valor 15. Vejas os tres ultimos passos da Figura 7.20.

Exerccio Resolvido 7.2 - Multiplo Retorno em uma Funcao

A funcao calculaPot, do Exemplo 7.31, calcula a potencia quadrada de um inteiro. Modi-


fique esta funcao para que ela passe a retornar tambem a potencia cubica deste numero. Faca
as devidas alteracoes na main.

1 int calculaPot ( int valor ) {


2 int potQuadrada ;
3 potQuadrada = valor * valor ;
4 return potQuadrada ;
7.8. EXERCICIOS RESOLVIDOS 215

5 }
6
7 main ( ) {
8 int x ;
9 int quadrado ;
10
11 scanf ( " % d " , & x ) ;
12 quadrado = calculaPot ( x ) ;
13 printf ( " Quadrado de % d = % d \ n " , x , quadrado ) ;
14 }

Exemplo 7.31: Funcao calculaPot

Solucao Possvel:
O dado de entrada do programa e um numero inteiro, e as sadas devem ser as pontencias
quadrada e cubica deste numero, calculadas pela funcao apresentada no Exemplo 7.31, com suas
devidas alteracoes. O Pseudocodigo 7.2 exibe as etapas para a solucao.

Pseudocodigo 7.2 Calculo das potencias quadradas e cubicas de um numero inteiro.


Descricao: Programa que retorna as potencias quadrada e cubica de um numero inteiro, calculadas por
uma funcao apenas.
Dados de Entrada: um numero inteiro.
Sada do Programa: potencias quadrada e cubica do valor de entrada.
Leitura do numero inteiro;
Passagem de par^ametros para a func~
ao calculaPot;
Calculo das pot^
encias pela func~
ao calculaPot.

Para atender a etapa Calculo das potencias pela funcao calculaPot, a funcao calcu-
laPot deve ser alterada para tambem calcular e retornar o cubo do valor. Contudo, o retorno
dessa funcao ja e o quadrado do parametro de entrada. Nesse sentido, a passagem por apon-
tadores e a ferramenta para multiplos retornos numa funcao, o que torna importante o passo
Passagem de parametros para a funcao calculaPot
As modificacoes no Exemplo 7.31 podem ser observadas no Exemplo 7.32.

1 int calculaPot ( int valor , int * potCubica ) {


2 int potQuadrada ;
3 potQuadrada = valor * valor ;
4 * potCubica = valor * valor * valor ;
5 return potQuadrada ;
6 }
7
8 main ( ){
9 int x;
10 int quadrado ;
11 int cubo ;
12
216 CAPITULO 7. APONTADORES

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


14 quadrado = calculaPot (x , & cubo ) ;
15 printf ( " Quadrado de % d = % d \ nCubo de % d = % d \ n " , x , quadrado , x , cubo ) ;
16 }

Exemplo 7.32: Multiplo retorno em calculaPot

Como pode ser observado, calculaPot passa a ter um parametro que ira receber o endereco
da variavel cubo, declarada na main. Dessa forma, ao termino da execucao de calculaPot,
cubo ira conter a potencia cubica do valor.

Exerccio Resolvido 7.3 - Insercao no Incio de Lista Simplesmente Encadeada

Foi visto anteriormente como inserir um elemento no final da lista. Agora, pretende-se criar
uma funcao que insere no comeco de uma lista simplesmente encadeada.

Solucao Possvel:
Para inserir um novo no, no comeco da lista, os passos que devem ser seguidos pela funcao
aparecem no Pseudocodigo 7.3, onde tambem pode-se observar os dados de entrada e o retorno
da funcao.

Pseudocodigo 7.3 Insercao no comeco de uma lista simplesmente encadeada.


Descricao: Funcao que insere um no no comeco de uma lista simplesmente encadeada.
Dados de Entrada: a lista e o elemento a ser enserido.
Sada da Funcao: a lista apos a insercao.
Criac~
ao de um no para armazenar o novo elemento;
Verificar se a lista esta vazia
Se estiver vazia:
O novo no tambem passa a ser o ultimo da lista;
Se n~ao estiver vazia:
Faz o novo no ter como seu proximo o antigo primeiro;
O primeiro da lista passa a ser o no criado;
Posicionar o marcador no no includo;
Incrementar o tamanho da lista.

O Exemplo 7.33 mostra a implementacao em C. Para tal funcao, a passagem da lista sera
feita por apontador, pois assim, qualquer modificacao em seus elementos sera feita diretamente
na funcao e, por conseguinte, o retorno e void. A lista poderia ser passada por copia, mas a
lista modificada teria estar apos um comando return.

1 void i nc luirInicioLista ( tLista * lista , tInfo elem ) {


7.8. EXERCICIOS RESOLVIDOS 217

2 tNo * no ;
3
4 no = criaNo ( elem ) ;
5 if ( vaziaLista ( lista ) ) {
6 lista - > ultimo = no ;
7 } else {
8 no - > proximo = lista - > primeiro ;
9 }
10 lista - > primeiro = lista - > marcador = no ;
11 lista - > tam ++;
12 }

Exemplo 7.33: Funcao de inclusao de um novo no no comeco da lista

No Exemplo 7.33, um novo no e criado com a utilizacao de criaNo. Ao inclu-lo no inico, os


apontadores primeiro e marcador devem apontar para o no ser includo, respectivamente porque
ele e includo no comeco da lista e no ultimo no acessado.
Contudo, antes de atualizar o apontador primeiro, e fundamental verificar se a lista esta
vazia, pois caso nao esteja, o novo no deve guardar, como seu proximo, o antigo primeiro da
lista. Ja se a lista estiver vazia, basta fazer o apontador ultimo tambem apontar para o novo
no. Por fim, o tamanho da lista deve ser incrementado em uma unidade.
A figura 7.21 mostra a Figura 7.17, apos a inclusao do novo no no comeco da lista.

Figura 7.21: Lista da Figura 7.17, Apos a Inclusao do No no Comeco

Exerccio Resolvido 7.4 - Ordenacao de uma Lista Simplesmente Encadeada

Para ilustrar a odenacao de uma lista, considere que ela e formada por nos cuja informacao
armazenada seja um nome e uma idade. Ulizando as estruturas e funcoes ja apresentadas, alem
de novas funcoes, crie um programa que le as informacoes do teclado ate que uma idade invalida
seja digitada (menor que zero). O programa deve imprimir a lista ordenada crescentemente por
218 CAPITULO 7. APONTADORES

idade, apos a leitura das informacoes.

Solucao Possvel:
De forma geral, o programa deve seguir o Pseudocodigo 7.4.

Pseudocodigo 7.4 Ordenacao de uma lista simplesmente encadeada.


Descricao: Programa que ordena um grupo de pessoas por idade.
Dados de Entrada: nomes e as respectivas idades.
Sada do Programa: lista de nomes ordenados crescentemente por idade.
Inicializac~ao da lista;
Leitura de um nome;
Leitura da idade;
Validac~
ao da idade
Se a idade for menor que zero:
Parar de ler informac~
oes. Ir para "Ordenac~
ao da Lista";
Se n~ao for menor que zero:
Continuar a ler informac~
oes. Ir para "Leitura de um nome";
Ordenac~
ao da lista;
Impress~ao da lista ordenada;
Destruic~ao da lista.

O passo Ordenacao da lista pode ser decomposto como segue no Pseudocodico 7.5. A
ideia consiste em posicionar o marcador no comeco da lista e, enquanto o final desta nao e
atingido, buscar o menor elemento a frente do atual marcador. Se encontrar algum, deve-se
trocar as informacoes entre o no marcador e o que guarda a menor idade a frente.

Pseudocodigo 7.5 Processo para a ordenacao de uma lista simplesmente encadeada.


Processo componente "Ordenac~ao da lista":
Posicionar o marcador no comeco da lista;
Enquanto n~
ao chegar no fim da lista:
Buscar o no que guarda uma idade menor que a do marcador;
Se existir um no com menor idade:
Trocar as informac~
oes entre o marcador e o no encontrado;
Posicionar o marcador no proximo no da sequ^
encia da lista;
FIM-Processo componente "Ordenac~ao da lista".
7.8. EXERCICIOS RESOLVIDOS 219

No Exemplo 7.34, tem-se a trascricao dos Pseudocodigos 7.4 e 7.5 para C.

1 typedef struct {
2 char nome [30];
3 int idade ;
4 } tInfo ;
5
6 void imprimirLista ( tLista lista ) {
7 tInfo x ;
8 int erro = 0;
9
10 primeiroLista (& lista ) ;
11 while (! finalLista (& lista ) ) {
12 x = obterInfo (& lista , & erro ) ;
13 if ( erro ) {
14 printf ( " Nome : % s \ n " , x . nome ) ;
15 printf ( " Idade : % d \ n \ n " , x . idade ) ;
16 }
17 proximoLista (& lista ) ;
18 }
19 }
20
21 tNo * menorIdade ( tLista lista ) {
22 tNo * aux , * menor ;
23
24 menor = lista . marcador ;
25 aux = menor - > proximo ;
26 while ( aux != NULL ) {
27 if ( aux - > info . idade < menor - > info . idade )
28 menor = aux ;
29 aux = aux - > proximo ;
30 }
31 return menor ;
32 }
33
34 void ordenaPorIdade ( tLista * lista ) {
35 tNo * aux ;
36 tInfo x ;
37
38 primeiroLista ( lista ) ;
39 while (! finalLista ( lista ) ) {
40 aux = menorIdade (* lista ) ;
41 if ( lista - > marcador != aux ) {
42 x = lista - > marcador - > info ;
43 lista - > marcador - > info = aux - > info ;
44 aux - > info = x ;
45 }
46 proximoLista ( lista ) ;
47 }
48 }
49
220 CAPITULO 7. APONTADORES

50 main ( ) {
51 tLista lista ;
52 tInfo dados ;
53 int pos ;
54
55 iniciaLista (& lista ) ;
56 do {
57 printf ( " Entre com o nome : " ) ;
58 scanf ( " % s " , & dados . nome ) ;
59 printf ( " Entre com a idade : " ) ;
60 scanf ( " % d " , & dados . idade ) ;
61 if ( dados . idade < 0)
62 break ;
63 incluirFimLista (& lista , dados ) ;
64 } while (1) ;
65 ordenaPorIdade (& lista ) ;
66 imprimirLista ( lista ) ;
67 destroiLista (& lista ) ;
68 }

Exemplo 7.34: Cadastro de nomes e idades

Novamente, o programa implementado apenas le as informacoes do teclado enquanto nao se


digita uma idade invalida (nesse cado, menor que 0). Se for digitado tal valor para a idade, o
comando break faz a execucao do programa sair do laco infinito.
Cada nome e idade sao armazendos na lista e, apos o termino de leitura de dados, eles sao
ordenados por ordem crescente de idade. Na funcao ordenaPorIdade, posiciona-se o marcador
no comeco da lista e, enquanto o final desta nao e atingido, busca-se o menor elemento a frente
do atual marcador. Caso seja encontrado algum, trocam-se as informacoes dos nos marcador e
aux, o qual guarda o no com menor idade obtido da funcao menorIdade.
A funcao menorIdade procura e retorna um no que guarda uma idade menor do que a que
esta no marcador, e que se encontra a frente deste na lista. Assim, a variavel menor guarda o no
que contem a menor idade e e inicializado como sendo o atual marcador. A partir do proximo no
apos ao atual marcador, enquanto o final da lista nao e atingido, busca-se um no que contenha
uma idade menor do a que esta armazenada em menor. Se for encontrada alguma, menor passa
a apontar para o no que contem esta menor idade. Repare que e utilizada uma variavel aux
para percorrer a lista, pois nao se pretende alterar a posicao do marcador, o qual e utilizado na
funcao ordenaPorIdade.
No fim da execucao do programa, a lista ordena e impressa e, antes de se encerrar o programa,
ela e destruda.

Exerccio Resolvido 7.5 - Media de valores de uma Lista Simplesmente Encadeada

Considere uma lista de funcionarios de uma empresa na qual sao armazenados o nome e o
salario destes. Pretende-se imprimir na tela todos os funcionarios cujos salarios estao acima da
7.8. EXERCICIOS RESOLVIDOS 221

media.

Solucao Possvel:
O programa principal segue os passos descritos no Pseudocodigo 7.6.

Pseudocodigo 7.6 Media dos valores de uma lista simplesmente encadeada.


Descricao: Programa que imprime os funcionarios cujos salarios estao acima da media.
Dados de Entrada: nomes e os respectivos salarios.
Sada do Programa: lista de nomes que tem o salario acima da media.
Inicializac~ao da lista;
Leitura de um nome;
Leitura da idade;
Validac~
ao da idade
Se a idade for menor que zero:
Parar de ler informac~
oes. Ir para "Verificar quem esta acima da media";
Se n~ao for menor que zero:
Continuar a ler informac~
oes. Ir para "Leitura de um nome";
Verificar quem esta acima da media;
Destruic~ao da lista.

O passo Verificar quem esta acima da media pode ser descrito como o Pseudocodigo
7.7. Para achar a media, basta somar todos os salarios e dividir pelo total de nos. Tendo a
media, procura-se, no por no, quem tem o salario maior que ela.

Pseudocodigo 7.7 Processo para ver quem esta com salario acima da media
Processo componente "Verificar quem esta acima da media":
Posicionar o marcador no comeco da lista;
Enquanto n~ao chegar no fim da lista:
Somar os salarios;
Posicionar o marcador no proximo no da sequ^
encia da lista;
Achar a media (Divide-se o total somado pelo tamanho da lista);
Posicionar o marcador no comeco da lista;
Enquanto n~ao chegar no fim da lista:
Se existir um no com salario maior que a media:
Imprimir as informac~
oes do funcionario.
Posicionar o marcador no proximo no da sequ^
encia da lista;
FIM-Processo componente "Verificar quem esta acima da media".
222 CAPITULO 7. APONTADORES

No Exemplo 7.35, o programa implementado contem uma main semelhante a do Exemplo


7.34. A diferenca e que agora e fornecido o salario em vez da idade. Alem disso, no lugar da
ordena, e chamada a funcao que procura e imprime os funcionarios com salario acima da media.

1 typedef struct {
2 char nome [30];
3 float salario ;
4 } tInfo ;
5
6 void imprimirLista ( tLista lista ) {
7 tInfo x ;
8 int erro = 0;
9
10 primeiroLista (& lista ) ;
11 while (! finalLista (& lista ) ) {
12 x = obterInfo (& lista , & erro ) ;
13 if ( erro ) {
14 printf ( " Nome : % s \ n " , x . nome ) ;
15 printf ( " Salario : % f \ n \ n " , x . salario ) ;
16 }
17 proximoLista (& lista ) ;
18 }
19 }
20
21 void acimaMedia ( tLista lista ) {
22 tInfo x ;
23 int erro = 0;
24 float total , media ;
25
26 total = 0;
27 primeiroLista (& lista ) ;
28 while (! finalLista (& lista ) ) {
29 x = obterInfo (& lista , & erro ) ;
30 if ( erro )
31 total += x . salario ;
32 proximoLista (& lista ) ;
33 }
34
35 media = total / lista . tam ;
36
37 primeiroLista (& lista ) ;
38 while (! finalLista (& lista ) ) {
39 x = obterInfo (& lista , & erro ) ;
40 if ( erro ) {
41 if ( x . salario > media ) {
42 printf ( " Nome : % s \ n " , x . nome ) ;
43 printf ( " Salario : % f \ n \ n " , x . salario ) ;
44 }
45 }
46 proximoLista (& lista ) ;
7.9. RESUMO 223

47 }
48 }
49
50 main ( ) {
51 tLista lista ;
52 tInfo dados ;
53 int pos ;
54
55 iniciaLista (& lista ) ;
56 do {
57 printf ( " Entre com o nome : " ) ;
58 scanf ( " % s " , & dados . nome ) ;
59 printf ( " Entre com o salario : " ) ;
60 scanf ( " % f " , & dados . salario ) ;
61 if ( dados . salario < 0)
62 break ;
63 incluirFimLista (& lista , dados ) ;
64 } while (1) ;
65 acimaMedia ( lista ) ;
66 destroiLista (& lista ) ;
67 }

Exemplo 7.35: Cadastro de nomes e salarios

Na funcao acimaMedia, total e inicializado como 0 e, a partir do primeiro elemento da


lista, esse total e incrementado com o salario de cada no. Quando o final da lista e alcancado,
a media de salarios e computada como sendo o total obtido dividido pelo tamanho da lista, ou
seja, o numero de funcionarios.
Com a media calculada, inicia-se um nova busca na lista, desde o primeiro elemento ate o
final da lista, a fim de que se encontre os salarios acima da media. Caso algum no contenha um
salario maior que a media, os dados do funcionario sao impressos na tela.

7.9 Resumo
Apontadores sao um tipo de variavel que guarda o endereco de outras variaveis. Trata-se
de um conceito de baixo nvel, ligado essencialmente a arquitetura de computadores.

Em C, a declaracao de uma variavel apontador segue o formato:

<tipo do apontador> * <nome da variavel>

No lugar de tipo do apontador, podem ser utilizados os tipos padroess da linguagem C,


assim como os tipos definidos pelo programador.

O operador & retorna o endereco de memoria de uma variavel. Ja o operador seta ( >)
e utilizado para acessar, por meio de apontadores, os atributos de uma estrutura.
224 CAPITULO 7. APONTADORES

Os apontadores podem alterar o conteudo da variavel apontado por ele. Por meio de
apontadores tambem e possvel a passagem de parametros sem copia. A passagem de
parametros por apontadores permite multiplos retornos em uma funcao, evita a copia de
muitos dados e possibilita a alteracao das variaveis do programa no decorrer da execucao
da funcao.

Dentre os principais problemas com o uso de apontadores estao: apontadores nao iniciali-
zados, objetos pendentes, referencias pendentes e programacao macarronica.

A alocacao dinamica e uma forma de reservar espacos de memoria no decorrer da execucao


do programa, o que evita o desperccio de recursos da maquina. Em C, as funcoes malloc
e free sao responsaveis por alocar e desalocar areas de memoria, respectivamente.

O TAD Implementacional Lista Encadeada e uma forma eficiente de armazenar dados num
programa, pois aloca-se e desaloca-se os espacos para os dados dinamicamente, o que torna
muito simples as operacoes de incluir e excluir elementos.

7.10 Lista de Exerccios


1. Cite tres vantagens e tres desvantagens de se utilizar apontadores.

2. Explique quais as funcionalidades dos operadores * e &.

3. Liste as diferencas entre variaveis apontadores e nao apontadores. Faca um codigo em


C, que realize a soma de duas variaveis float a e b, usando dois apontadores g e h, que
apontem para a e b respectivamente. Quais sao os tipos dos apontadores g e h?

4. No Exemplo 7.36, escrito na linguagem C, encontre os erros de sintaxe na utilizacao de


apontadores. Justifique cada erro encontrado.

1 main ( ) {
2 int p ;
3 int * d ;
4 int q = 10;
5 float * j ;
6 float t = 15.0;
7 j = &t;
8 p = &q;
9 d = j;
10 }

Exemplo 7.36: Exerccio Proposto 4


7.10. LISTA DE EXERCICIOS 225

5. No Exemplo 7.37, escrito na linguagem C, encontre os problemas causados pela utilizacao


indevida de apontadores. Justifique cada problema encontrado.

1 main ( ) {
2 int * p = ( int *) malloc ( sizeof ( int ) ) ;
3 int * q = ( int *) malloc ( sizeof ( int ) ) ;
4 int * j ;
5 int * h ;
6 int * v ;
7 int d = 20;
8 int e = 30;
9 *q = e;
10 *j = d;
11 p = &d;
12 h = &e;
13 v = q;
14 free ( q ) ;
15 }

Exemplo 7.37: Exerccio Proposto 5

6. Ilustre as atribuicoes e referenciamentos feitos no trecho de codigo do Exemplo 7.6. Utilize


para a ilustracao os padroes adotados na Figura 7.3 e Figura 7.4, para representar uma
variavel nao apontador e apontador respectivamente.

7. Implemente na linguagem C, uma matriz de strings alocada estaticamente e uma alocada


dinamicamente. Explique um caso em que a alocacao dinamica seja importante para
economia de memoria.

8. Explique o que e alocacao e desalocacao de memoria em tempo de execucao e quais funcoes


em C executam essas funcionalidades. Cite uma vantagem de se desalocar memoria nao
mais utilizada.

9. Crie uma funcao que insere um novo elemento na lista numa dada posicao. Considere que
os argumentos da funcao sao:
- Um apontador para a lista no qual o elemento sera inserido.
- Um elemento de um tipo tInfo previamente definido.
- Um inteiro que indica a posicao na qual ocorrera a insercao.
Sugestao: verifique se a posicao fornecida a funcao e valida.

10. Considere duas listas simplesmente encadeadas Ae B, contendo inteiros ordenados


crescentemente. Assim, pede-se implementar uma funcao que retorne uma lista encade-
ada ordenada formada pela intercalacao dos elementos de Ae B, considerando que a
226 CAPITULO 7. APONTADORES

lista resultante nao possua chaves repetidas e que Ae Btambem nao possuem chaves
repetidas.

11. Numa lista duplamente encadeada, os nos possuem um apontador para o no anterior a
ele na lista, alem do ja apresentado apontador para o proximo. Com base nisso, imple-
mente todas as funcaoes e estruturas discutidas no captulo na forma de lista duplamente
encadeada.

12. Pilhas sao um cso particular da lista simplesmente encadeada, no qual insere-se e retira-se
um elemento apenas do fim, uma poltica conhecida como LIFO (last in first out). Seja
entao P=( a(1), a(2), ..., a(n) ) uma pilha. Assim, a(1) e o elemento da base da pilha;
a(n) e o elemento topo da pilha; e a(i+1) esta acima de a(i). As operacoes associadas sao:

- criar (P) - criar uma pilha P vazia.


- push (P, x) - inserir o elemento x no topo de P (empilha).
- vazia (P) - verifica se P esta vazia.
- topo (P) - acessa o elemento do topo da pilha, sem desempilhar.
- pop (P) - elimina o elemento do topo de P (desempilha).

Com base nessas informacoes, implemente uma pilha de dados pilha de inteirosem C,
utilizando as estruturas e funcoes apresentadas sobre lista simplemente encadeada.

13. Duas pilha sequenciais numericas estao ordenadas crecentemente a partir do topo. Trans-
fira os elementos dessas pilhas para uma terceira pilha, inicialmente vazia, de modo que ela
fique ordenada decrescentemente (maior valor no topo). Suponha que nao haja restricoes
quanto a capacidade das pilhas.

14. O problema do abre/fecha parenteses. Este problema consiste em verificar se uma ex-
pressao matematica esta corretamente formada em termos de abre/fecha parenteses.
Exemplos:
7-((X*((X+Y)/(J-3))+Y)/(4-2.5))
((A+B)
)A+B(-C
(A + B)) - (C + D
Numa expressao correta o numero de )deve ser igual ao numero de (. Cada )deve
ser precedido por um (. Para isto, pode utilizar-se de um contador inicialmente igual a
zero que, ao percorrer a expressao da direita para a esquerda, e decrementado quando se
encontra um (e incrementado quando se encontra um ). Assim, o contador no final
da expressao deve ser igual a zero e, em nenhum momento, o ele deve ser menor que zero.
7.10. LISTA DE EXERCICIOS 227

Entao, implemente um programa C que leia uma expressao com parenteses e verifique
se ela esta corretamente formada em termos de abre/fecha parenteses usando o metodo
acima, utilizando as estruturas e funcoes de pilha criadas nos exerccios anteriores, mas
modificadas para receber caracteres.

15. Outro caso particular da lista simplesmente encadeada e a fila. Agora, a insercao e feita
apenas no fim da lista e a remocao no incio. Seja entao F=( a(1), a(2), ..., a(n) ) uma
fila. Dessa forma, a(1) e o comeco da fila; a(n) e o final da pilha; e a(i+1) esta atras de
a(i). As operacoes associadas sao:

- criar (F) - criar uma fila F vazia.


- insere (F, x) - inserir o elemento x no final de F.
- vazia (F) - verifica se F esta vazia.
- inicio (F) - acessa o elemento do incio da fila, sem retira-lo.
- retira (F) - elimina o elemento do final de F.

Com base nessas informacoes, implemente uma fila de dados fila de nomesem C, utili-
zando as estruturas e funcoes apresentadas sobre lista simplemente encadeada.

16. Duas filas sequenciais de nomes estao ordenadas crecentemente a partir do incio. Transfira
os elementos que ocorrem nessas duas filas para uma terceira fila, inicialmente vazia, de
modo que ela tambem fique ordenada crescentemente, ou seja, o primeiro nome em ordem
alfabetica no comeco da fila.

17. Uma palavra e um palndromo se tem a mesma sequencia de letras, quer seja lida da
esquerda para a direita ou da direita para a esquerda (exemplo: raiar). Implemente uma
solucao para verificar se uma palavra e um palndromo, usando pilha(s) e/ou fila(s).

18. Suponha uma fila de inteiros F. Mude a posicao de um elemento desta fila, tendo apenas
uma pilha vazia P e uma variavel do tipo inteiro x como auxiliares. Considere apenas as
operacoes associadas aos tipos fila e pilha.

19. Implemente uma lista encadeada que armazene as informacoes sobre os DVDs de uma
locadoras. Tais informacoes sao ttulo do filme, nome do diretor, principais atores e numero
de copias disponveis na locadora. Apresente um menu e crie funcoes que atendam as
seguintes opcoes:

- Dado um ttulo de filme, verificar se locadora possui em seu acervo.


- Dado um ttulo de filme, verificar se ha alguma copia disponvel.
- Dado um diretor, imprimir as informacoes sobre os filmes dirigido por ele.
228 CAPITULO 7. APONTADORES

- Dado um ator, imprimir as informacoes sobre os filmes nos quais ele foi um dos atores
principais.

20. Sabe-se que um texto e uma sequencia de caracteres contendo apenas letras, espacos em
branco e sinais de pontuacao. Uma palavra e definida como um segmento do texto que
consiste apenas de letras. Escreva uma funcao que recebe um texto do teclado e imprime
uma relacao de todas as palavras que ocorrem no texto juntamente com o numero de
ocorrencias de cada palavra.

7.11 Trabalhos Sugeridos


1. Notas de Alunos
O Colegiado de Engenharia de Computacao mantem uma listagem com informacoes sobre
o desempenho de cada aluno do curso. Esta listagem contem as notas obtidas pelo aluno
em cada uma das disciplinas que ele cursou e e organizada da seguinte maneira:

Matrcula do Aluno Codigo da Disciplina Carga Horaria Nota


00018 0001 60 4.5
11111 3232 30 8.7
00234 0500 75 9.0
00018 0001 60 7.0
... ... ... ...
09999 0786 60 7.5

Para facilitar a consulta a esta listagem, o coordenador do colegiado precisa de um pro-


grama em C que realize algumas operacoes sobre os dados da lista. Ele te pediu para que
voce elabore um programa interativo que permita ao usuario:

(a) Ler os dados da listagem e coloca-los em quatro listas. O usuario deve poder incluir
tantos dados quanto quiser. A indicacao de fim de entrada de dados e feita atraves
de um aluno de Matrcula 0 (zero).
(b) Mostrar as notas de um aluno numa disciplina. Lembre-se que o aluno pode ter
cursado mais de uma vez uma mesma disciplina por motivo de reprovacao.
(c) Incluir uma nota de um aluno em uma disciplina.
(d) Retificar uma nota de um aluno em uma disciplina.
(e) Excluir uma nota de um aluno em uma disciplina.
(f) Excluir todos os dados de um aluno jubilado.
(g) Calcular o o coeficiente de rendimento (C.R.) de um aluno. Lembre-se que o C.R. e
calculado da seguinte maneira:
7.11. TRABALHOS SUGERIDOS 229

C.R. = ( (Carga Horaria x Nota)) / ( (Carga Horaria))

(h) Identificar o aluno de melhor CR.


(i) Identificar os alunos com CR abaixo de 5.0.
(j) Identificar se um determinado aluno ja cumpriu a carga horaria mnima do curso
(defina um valor).

Exemplo de Programa

Escolha Operacao:
0 - Sair
1 - Ler dados
2 - Mostrar Notas
3 - Incluir Nota
4 - Corrigir Nota
5 - Excluir Nota
6 - Excluir Aluno
7 - CR de Aluno
8 - Melhor Aluno
9 - Maus Alunos
10 - Formando

Opcao: 1
Dados do aluno: 18 1 60 4.5
Dados do aluno: 1111 3232 30 8.7
Dados do aluno: 234 500 75 9.0
Dados do aluno: 18 1 60 7.0
... ... ...
Dados do aluno: 0 0 0 0.0

Opcao: 2
Matrcula: 18
Disciplina: 1
Notas: 4.5 7.0

Opcao: 3
Matrcula: 1111 Disciplina: 316
Carga Horaria: 60
Nota: 7.0
230 CAPITULO 7. APONTADORES

Opcao: 4
Matrcula: 1111 Disciplina: 316
Carga Horaria: 60
Nota: 7.0
Nova Nota: 8.5

Opcao: 5
Matrcula: 1111 Disciplina: 316
Nota: 7.0

Opcao: 6
Matrcula: 234

Opcao: 7
Matrcula: 1111
CR: 7.2

Opcao: 8
Matrcula: 234

Opcao: 9
Matrculas: 194

Opcao: 10
Matrcula: 1111
Nao cumpriu carga horaria mnima.

... ... ...

Opcao: 0
Ate a proxima!

2. Controlador de Trafego Aereo

Descricao

Controle de Trafego Aereo e um servico prestado por controladores, em terra, que guiam
aeronaves (geralmente avioes) no ar e no solo, para garantir um fluxo de trafego seguro e
7.11. TRABALHOS SUGERIDOS 231

ordenado. Os controladores de trafego aereo forencem indicacoes e autorizacoes de voo,


de acordo com as caractersticas operacionais das aeronaves e as condicoes de trafego em
determinado momento. Estas autrorizacoes podem incidir sobre a rota, altitude e/ou ve-
locidade propostas pelo operador da aeronave para determinado voo, devendo os pilotos
cumprirem as instrucoes recebidas.

Espaco Aereo

Em muitos pases, os servicos de trafego aereo sao prestados em toda a extensao do espaco
aereo e estes servicos sao utilizados por todos os usuarios (aeronaves privadas, militares e
comerciais). Os espacos aereos onde o controlador e responsavel por prover separacao entre
as aeronaves sao chamados de espaco aereo controlado em oposicao ao espaco aereo
espaco aereo nao controlado no qual pilotos das aeronaves sao responsaveis por manter
a separacao entre a sua aeronave e outras. Dependendo do tipo de voo e de classe do espaco
aereo, o controlador de trafego aereo pode emitir instrucoes que os pilotos devem seguir
ou apenas informacoes de voo para ajudar os pilotos operando no espaco aereo. Em todos
os casos, entretanto, o piloto tem a responsabilidade final pela seguranca da aeronave, e
pode nao cumprir as intrucoes numa emergencia.
Os servicos de controloe de trafego aereo incluem o controle de rotas das aeronaves que
estao no espaco aereo do aeroporto, o controle da autorizacao de pousos e decolagens e o
controle das pistas de taxi-aereo e patios.

O Trabalho

O trabalho consiste em implementar um programa que faca o controle dos pousos em um


aeroporto que apresente tres portoes de embarque e desembarque (A,B,C). O trabalho deve
ser feito em dois modulos: controle de espacos aereos e controle de pista, e a estrutura
para armazenamento dos dados deve ser uma lista encadeada dinamica.

Controle de espaco aereo


Como o controle da torre consiste em controlar as aeronaves enquanto elas estiverem
no espaco aereo do aeroporto, pode-se dividir essa tarefa em duas subtarefas:

Controle de rotas

A partir do momento em que as aeronaves entram no espaco aereo do aeroporto, o


controle das suas rotas devera ser realizado pelo seu programa. Para isso considere
que a funcao basica do seu programa e impedir que ocorram colisoes, e para isso uma
das aeronaves em risco devera ser redirecionada para a rota mais proxima a uma das
232 CAPITULO 7. APONTADORES

rotas envolvidas. Existem duas ocoasioes em que ocorrerao colisoes: se duas aero-
naves estiverem utilizando a mesma rota ou se duas aeronaves estiverem utilizando
rotas que apresentem interseccao. Considere que duas rotas apresentam interseccao
se uma delas e multipla da outra.

Controle de aproximacao

Dado que o controle de rotas ja foi realizado sera necessario definir qual sera a ordem
das aeronaves a pousar. Para isso devera ser considerada a sequencia de prioridades
abaixo:
CLASSE > ALTITUDE > VELOCIDADE
Assim, define-se que existem apenas duas classes (militar e comercial) e que duas
alttudes sao consideradas iguais se a diferenca entre elas for menor que 2000 pes.
Alem disso, a classe militar tem prioridade frente a classe comercial; quanto menor
a altitude de uma aeronave maior sera a sua prioridade e quanto maior a velocidade
de uma aeronave maior sera a sua prioridade. Considere que cada operacao de pouso
dura 5 minutos.
Controle de pista
Apos o pouso de uma aeronave e necessario que o trafego em solo tambem seja
controlado. Com isso, a segunda parte do consistira em controlar o momento em
que cada aeronave devera encostar em uma plataforma que esta livre. Caso todas
as tres plataformas estejam ocupadas no momento em que uma aeronave pouse, esta
devera esperar no patio de espera ate que alguma das plataformas seja desocupada.
Considere que cada operacao de desembarque dure 30 minutos.

Especificacao

Modulo 1

Essa primeira parte do trabalho consiste em determinar a fila de aeronaves que ira pusar e
as novas rotas das aeronaves que precisam mudar de rota. Para isso, considere que quando
duas aeronaves apresentem chance de colisao a aeronave que apresentar numero de rota
maior devera ser redirecionada para uma rota com numero igual ao primeiro numero primo
maior que a rota de maior numero.

Modulo 2
7.11. TRABALHOS SUGERIDOS 233

A segunda parte consiste em determinar a que horas a operacao de desembarque de cada


aeronave foi realizada.

Entrada

As informacoes referente as aeronaves serao fornecidas atraves do teclado. As informacoes


serao as seguites: identificacao da aeronave (0103, 0407, 1114,...); numero inteiro que ser-
vira para indentificar qual rota que a aeronave esta percorrendo (1, 2, 4, 6,...); classe da
aeronave (comercialou militar); altitude na qual a aeronave se encontra no momento
em que entra no espaco aereo do aeroporto (7700, 9060, 1120, ...); velocidade com qual
a aeronave esta (650, 680, 810, ...) e a hora de entrada da aeronave no espaco aereo do
aeroporto (14:30, 14:29, 14:31, ...). Considere que a hora apresentara o caracter :para
separar hora de minuto.

Obs.: As horas de entrada no espaco aereo do aeroporto sao muito proximas,


com diferenca entre o menor e o maior horario de no maximo 5 minutos. Cada
nova aeronave deve ser adicionada na lista ja na ordem de prioridade de pouso,
do mais para o menos prioritario. Assim, apos todas as insercoes, as colisoes
deve ser verificadas, aeronave por aeronave, na sequencia de pouso obtida.

Exemplo:
0103
2
militar
7100
670
14:30

0307
10
comercial
8200
770
14:29

0200
13
militar
1000
710
14:30
234 CAPITULO 7. APONTADORES

0708
10
comercial
8600
640
14:29

1102
4
militar
9100
700
14:30

Sada

Os dados de sada do programa devem ser exibidos no console. O formato da sada deve
seguir o exemplo abaixo.

Exemplo:
0200
13
15:00

0103
2
15:05

1102
17
15:10

0307
19
15:30

0708
23
15:35
Captulo 8

Arquivos
Autores:

Estefhan Dazzi Wandekokem


Flavio Varejao

Objetivos:
Definir e apresentar arquivos;

Definir e diferenciar variaveis transientes e variaveis persistentes;

Definir e diferenciar arquivos de texto e arquivos binarios;

Mostrar algumas operacoes que podem ser feitas com arquivos;

Mostrar outras funcoes e exemplos de programas que usam arquivos;

8.1 Variaveis Transientes X Variaveis Persistentes


Quando os programas sao executados, eles lidam com variaveis. Elas sao a forma abstrata
pela qual os programadores enxergam os dados que seus programas manipulam. Internamente,
variaveis indicam celulas de memoria. Por exemplo, numa arquitetura de 32 bits (que pode ser
um Pentium da Intel), normalmente o tipo inteiro de C correspondera a 32 bits, o que pode
significar que uma variavel int dessa linguagem ocupara 4 bytes (4 celulas de memoria).
A memoria principal de um computador (muitas vezes chamada de memoria RAM) e o local
onde os programas em execucao, e tambem as variaveis que eles utilizam, sao armazenados.
Chamam-se essas variaveis armazenadas na memoria principal de variaveis transientes porque
seu tempo de existencia e limitado pelo tempo que o programa se encontra em memoria. Assim
que o usuario ou o Sistema Operacional decidir que o programa deve ser finalizado, todas essas
variaveis deixarao de existir.
Os computadores teriam sua utilidade muito reduzida se a informacao so pudesse ser arma-
zenada enquanto os programas estivessem na memoria principal (e a maquina, ligada). Para

235
236 CAPITULO 8. ARQUIVOS

resolver esse problema, existem as variaveis persistentes, as quais tem seu conteudo armazenado,
e possvel de ser acessado, independentemente do programa que as criou estar na memoria princi-
pal. Variaveis persistentes devem ser armazenadas em algum dispositivo de memoria secundaria,
como discos rgidos, CDs, memoria flash e DVDs, os quais tem a capacidade de manter por
um longo tempo o valor da informacao neles contida, independente do conteudo da memoria
principal do computador, ou mesmo de ele estar ligado. Deve-se ressaltar que o tempo de acesso
e de gravacao das variaveis armazenadas em meio secundario e muito maior que o das variaveis
armazenadas na memoria principal, por isso essa ultima e tao importante.
O conceito por tras das variaveis persistentes e o de arquivo, e esse sera o tema desse
captulo. Por meio de arquivos, essas variaveis podem ser armazenadas e as informacoes que
elas guardam, acessadas e processadas no futuro, tanto pelo programa que as criou quanto por
outros programas.

8.2 Tipos de Arquivos


Existem dois tipos de arquivos: arquivos texto e arquivos binarios. Ambos tem a mesma fi-
nalidade: armazenar variaveis persistentes. Quando o programador cria um arquivo em seu
programa, deve indicar a qual tipo ele pertencera. Isso determina o formato das variaveis per-
sistentes que ele gravara e acessara do arquivo.

8.2.1 Tipos de Arquivos - Arquivos Texto


Um arquivo texto e uma sequencia de caracteres. Se um arquivo texto for aberto num editor
de texto convencional, o usuario podera ler seu conteudo (ou ao menos ver os caracteres la
contidos), e podera ate mesmo modifica-lo de uma forma que faca sentido.
Por exemplo, na linguagem C, tome uma variavel int que guarde o numero 43. Uma forma
de escrever essa variavel num arquivo texto seria gravar 43, mas note que, com isso, dois
caracteres foram gravados: o 4 seguido do 3. Isso poderia significar, por exemplo, para
um arquivo numa arquitetura que salve caracteres como variaveis de 1 byte, que dois bytes
foram gravados no arquivo, o primeiro armazenando o caractere 4 e o segundo armazenando o
caractere 3.

Figura 8.1: Exemplo de um arquivo texto.


8.2. TIPOS DE ARQUIVOS 237

8.2.2 Tipos de Arquivos - Arquivos Binarios

Por meio de um arquivo binario, as variaveis armazenadas na memoria principal podem ter seus
bytes armazenados num arquivo fsico, sem traducao para caracteres e com correspondencia
de um para um. Isso significa que, usando-se arquivos binarios, torna-se possvel criar um
espelhoda memoria principal, mas salvo em memoria secundaria.
A fim de exemplificar esse conceito na linguagem C, sera citada a gravacao de uma struct,
uma das razoes pela qual arquivos binarios sao tao uteis nessa linguagem. Ja foi estudado que
uma struct e um tipo especial de variavel que armazena outras variaveis. Tome, por exemplo, a
struct

struct pessoa {
char nome[50];
int idade;
float salario;
}

Como guardar essa informacao em um arquivo? Pode-se guarda-la num arquivo texto,
escrevendo-se o nome da pessoa, entao sua idade e finalmente seu salario. Sabendo o formato
como essa informacao foi escrita, o programador pode criar um programa que a le.
De modo alternativo, pode-se armazena-la num arquivo binario usando um comando (que
sera mostrado depois) que trata toda a struct como uma unica variavel. Ela pode ser lida
tambem se usando um comando que a trata da mesma forma. Isso e possvel, pois se sabe o
formato como essa struct foi armazenada no arquivo binario. Esse formato poderia ser, para
uma arquitetura de 32 bits de palavra, na linguagem C, de 50 bytes que armazenam cada uma
das variaveis char, mais 32 bits (4 bytes) que armazenam uma variavel int, e mais 32 bits (4
bytes) que armazenam uma variavel float.

Figura 8.2: Exemplo de um arquivo binario.


238 CAPITULO 8. ARQUIVOS

8.3 Definicao de arquivos


Para serem utilizados nos programas, os arquivos sao tratados como variaveis. Entao, como
todas as outras variaveis, eles devem ser declarados. Dessa forma, tambem podem ser usados
como argumentos para funcoes.
Para lidar com arquivos, existem funcoes responsaveis por realizar todas as operacoes ne-
cessarias. Por exemplo, criar um novo arquivo em branco, excluir um ja existente, gravar
informacao ou ler as ja contidas num arquivo, dentre outras.
Na linguagem C, o tipo FILE, definido na biblioteca stdio.h, e usado para se tratar arquivos.
Um ponteiro para FILE deve ser declarado sempre que uma variavel arquivo for utilizada no
programa. Por exemplo, o seguinte comando declara uma variavel do tipo ponteiro para arquivo
denominada arq:

FILE *arq;

Os ponteiros para FILE tratam arquivos referenciando-os a areas de memoria chamadas


buffers, as quais armazenam, na memoria principal, informacoes que foram lidas de um arquivo
fsico ou que serao escritas em um, mas que ainda nao foram descarregadas (gravadas no disco
rgido, por exemplo). De qualquer forma, o programador pode abstrair o conceito de buffer;
ele usara a variavel FILE* declarada, associara um arquivo fsico a ela, e entao a usara como
argumento para as funcoes de arquivo.

8.4 Operacao sobre arquivos


Arquivos demandam algumas operacoes, como abertura e fechamento, para seu funcionamento.
Os valores ja armazenados nos arquivos devem poder ser lidos, e novos valores devem poder ser
escritos, para que tenham utilidade. As operacoes que realizam essas tarefas sao fornecidas pela
linguagem de programacao, em varios formatos.

8.4.1 Abertura
Apos a declaracao de uma variavel do tipo arquivo, essa deve ser associada a um nome de
arquivo, o qual identifica um arquivo de fato, com correspondencia na memoria secundaria.
Se o arquivo com o nome especificado ja existir, entao o programador tera acesso a seu
conteudo, e podera le-lo e altera-lo. Mas um nome de arquivo que ainda nao existe tambem
pode ser utilizado. Nesse caso, um novo arquivo, em branco, com o nome especificado, sera
criado. Esses detalhes sao resolvidos passando-se as informacoes corretas a funcao responsavel
por abrir o arquivo.
Na linguagem C, essa funcao e a fopen(). Seu prototipo e:

FILE *fopen (const char *nomearq, const char *modo);


8.4. OPERACAO SOBRE ARQUIVOS 239

A funcao retorna um ponteiro para FILE, que devera ser recebido pela variavel usada para
manipular o arquivo. O primeiro argumento de fopen(), nomearq, e uma string, e refere-se ao
nome do arquivo que sera aberto. O segundo argumento, modo, tambem uma string, e um
codigo passado a fopen(), responsavel por indicar que tipo de arquivo e operacao sera realizada
sobre esse arquivo. Os codigos sao descritos na tabela 8.1:

Parametro Efeito
r Abre um arquivo-texto para leitura
w Cria um arquivo-texto para escrita
a Abre um arquivo-texto para gravar ao fim dele
r+ Abre um arquivo-texto para leitura/escrita
w+ Cria um arquivo-texto para leitura/escrita
a+ Abre ou cria (se nao existir) um arquivo-texto para ler dele ou gravar ao fim dele
rb Abre um arquivo binario para leitura
wb Cria um arquivo binario para escrita
ab Abre um arquivo binario para gravar ao fim dele
r+b Abre um arquivo binario para leitura/escrita
w+b Cria um arquivo binario para leitura/escrita
a+b Abre ou cria um arquivo binario para gravar ao fim dele

No exemplo 8.1, a seguir, serao criados dois arquivos: o arquivo texto somente escrita.txt,
o qual nao existia antes da execucao de fopen(), e que sera usado somente para gravacao de
informacao (no caso, caracteres); e leio e escrevo.meu, binario, no qual as informacoes poderao
ser lidas e escritas. Note como as variaveis FILE* sao declaradas, inicialmente, e depois recebem
o retorno da funcao fopen().

1 FILE * arq_ex1 , * arq_ex2 ;


2 /* ... */
3 arq_ex1 = fopen ( " somente_escrita . txt " , " w " ) ;
4 arq_ex2 = fopen ( " leio_e_escrevo . meu " , " a + b " ) ;

Exemplo 8.1: Abertura de arquivo

Um ponto importante a ser ressaltado sobre a funcao fopen() e a avaliacao de seu valor de
retorno. Muitas vezes ocorrem erros na abertura de um arquivo, como, por exemplo, passar para
fopen() o codigo rmas o arquivo com o nome especificado nao existir no disco. Esses erros
podem ser detectados porque a funcao fopen() retornara NULL para indicar que houve falha na
abertura de um arquivo.
No exemplo 8.2, e declarada uma variavel de arquivo. Em seguida, e chamada fopen() para
realizar a abertura de arquivo. Imediatamente depois, e verificado se ele foi aberto corretamente.
Se nao foi, o usuario sera notificado e o programa, encerrado. Esse encerramento e feito com o
240 CAPITULO 8. ARQUIVOS

uso da funcao exit() (definida na biblioteca stdlib.h), a qual finaliza o programa e retorna para
o Sistema Operacional o codigo numerico passado como argumento.

1 FILE * arq_ex3 ;
2 arq_ex3 = fopen ( " alunos . txt " , " r " ) ;
3 if ( arq_ex3 == NULL ) {
4 printf ( " Erro com a abertura de arquivo ! O programa sera abortado ... " ) ;
5 exit (1) ;
6 }

Exemplo 8.2: Abertura de arquivo com verificacao de sucesso

Todas as vezes que fopen() for usada deve-se verificar imediatamente se a abertura de arquivo
ocorreu sem problemas. Essa abordagem evitara muitos problemas posteriores.
Quando um arquivo e aberto, passa a estar associado a ele um indicador de posicao, res-
ponsavel por indicar em qual ponto do arquivo novas informacoes sao lidas ou gravadas (ele pode
ser imaginado como o cursor em um texto sendo editado num editor de texto). No momento em
que um arquivo e aberto, esse indicador estara no comeco do arquivo; e sempre que as variaveis
persistentes do arquivo forem sendo lidas ou gravadas, o indicador de posicao se movimenta,
ficando sempre a frente da ultima variavel lida ou gravada.

8.4.2 Fechamento

Quando um arquivo aberto nao for mais utilizado num programa, ele deve ser fechado. Esse
fechamento desassocia o arquivo fsico, em disco (por exemplo), com a variavel do tipo arquivo
usada para abr-lo. Um arquivo aberto pode ser fechado usando-se uma funcao que execute essa
tarefa e que o receba como argumento.
E muito importante lembrar-se de fechar um arquivo. Arquivos abertos sao recursos que
o sistema operacional deve gerenciar; dessa forma, muitos arquivos abertos significam mais
recursos sendo gerenciados.
Na linguagem C, a funcao fclose() e usada para fechar um arquivo. Seu prototipo e:

int fclose (FILE *fp);


bel=exe:fseek,
Ela retorna 0 se a operacao de fechamento foi bem-sucedida, e diferente de 0 caso nao.
Recebe como parametro um ponteiro para FILE, o qual e o arquivo a ser fechado.
Apos fechar um arquivo, ele deve ser aberto novamente, usando-se fopen(), caso se queira
usa-lo no programa outra vez.
Quando fclose() e chamada, qualquer informacao que ainda esteja no buffer sera gravada no
arquivo. Somente apos isso o arquivo fsico e finalmente desassociado da variavel FILE* usada.
8.5. OPERACOES SOBRE ARQUIVOS TEXTO 241

8.5 Operacoes sobre arquivos texto


Ja foi mostrado que um arquivo texto e aquele que armazena caracteres. Dessa forma, somente
caracteres sao lidos e gravados em um arquivo-texto; algumas funcoes especiais existem, contudo,
para que strings (vetores de caracteres) e numeros possam ser lidos e gravados.

8.5.1 Leitura
A linguagem C fornece uma serie de funcoes para leitura de informacoes em arquivos de texto.
Serao mostradas a seguir duas delas: fscanf() e fscanf()

fscanf()
A funcao fscanf() funciona de forma semelhante a scanf(), ja estudada em captulos anteriores.
A diferenca esta no fato de que fscanf() trabalha com arquivos, e nao com a entrada padrao
(normalmente o teclado), caso de scanf()
Seu prototipo e:

int fscanf ( FILE *fp, const char *format, ... );

A passagem de argumentos e semelhante a de scanf()., com o acrescimo de que deve ser


colocado como primeiro argumento o ponteiro para FILE representando o arquivo do qual serao
lidas as variaveis.
No formato comum de uso, fcanf(), assim como scanf()., encerra a leitura de uma string (ou
seja, argumento de codigo %s) assim que um caractere de espaco em branco, tabulacao ou
quebra de linha e encontrado. Dessa forma, fscanf() nao e recomendada para a leitura de strings.
C oferece outra funcao para esse fim, que sera estudada em seguida: fgets().
O exemplo 8.3 mostra como a funcao fscanf() pode ser usada para ler um inteiro, uma string
(nao formada por espacos ou quebras de linha) e um float de um arquivo-texto. Note que e
necessario o uso de & antes da variavel int e float, ja que deve-se passar o endereco delas a
fscanf(), e que isso nao e necessario com a variavel string (como ocorre com o uso de scanf() ).

1 \ begin { verbatim }
2 # include < stdio .h >
3 # define NOME_ARQUIVO " meu_texto . txt "
4 # define TAM_STR 50
5
6 int main ( void )
7 {
8 int inteiro ;
9 float real ;
10 char str [ TAM_STR ];
11 FILE * arq ;
12
13 if ( !( arq = fopen ( NOME_ARQUIVO , " r " ) ) ) {
242 CAPITULO 8. ARQUIVOS

14 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;


15 exit (1) ;
16 }
17 fscanf ( arq , " % d \ n % s \ n % f " , & inteiro , str , & real ) ;
18 }

Exemplo 8.3: Uso de fscanf().

Dessa forma, um arquivo texto que contenha os seguintes caracteres:

1145
segunda_linha
45.99

caso lido com o codigo acima, resultaria o valor 1145 na variavel inteiro, segunda linhaem
str e 45.99 na variavel real.

fgets()
Outra funcao de C utilizada para ler informacoes de um arquivo texto e fgets(), que permite ler
strings completas. Seu prototipo e:

char *fgets (char *str, int length, FILE *fp);

Essa funcao le os caracteres do arquivo fp passado como argumento e armazena-os na string str,
tambem passada como argumento, ate que ou length-1 caracteres sejam lidos ou um caractere de
nova linha (\n) seja lido. Se um caractere de nova linha for lido, ele sera armazenado em str. Ja
quando a leitura de caracteres do arquivo finaliza, fgets() encerrara a string str armazenando \0 no
proximo caractere (ou seja, finalizando a string).
Dessa forma, um arquivo texto que contenha os seguintes caracteres: primeira linha\nsegunda
linha, tal como mostrado a seguir,

primeira linha
segunda linha

pode ser lido usando-se duas chamadas a funcoes fgets(). Duas strings serao lidas e gravadas
nas variaveis string: str1 contera primeira linha\n, e str2 contera segunda linha. O exemplo 8.4
mostra como isso pode ser feito:

1 char str1 [50];


2 char str2 [40];
3 FILE * arquivo ;
4
5 If ( !( arquivo = fopen ( NOME_ARQUIVO , " r " ) ) ) {
6 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;
8.5. OPERACOES SOBRE ARQUIVOS TEXTO 243

7 exit (1) ;
8 }
9
10 fgets ( str1 , 50 , arquivo ) ;
11 fgets ( str2 , 40 , arquivo ) ;

Exemplo 8.4: Uso de fgets().

Note que o tamanho maximo passado a fgets() corresponde ao tamanho das strings definidas.

8.5.2 Escrita
A linguagem C oferece a funcao fprintf(), que pode ser usada para gravar em arquivos de texto. Ela
sera estudada a seguir.

fprintf()
A funcao fprintf() e semelhante a printf().
Seu prototipo e:

int fprintf ( FILE *fp, const char *format, ... );

A passagem de parametros e semelhante a de printf(), com o acrescimo de que deve ser colocado
como primeiro argumento o ponteiro para FILE representando o arquivo no qual serao gravadas as
informacoes.
A gravacao de strings pode ser feita sem problemas com o uso de fprintf().
O exemplo 8.5 mostra o uso dessa funcao. Nesse programa, tres variaveis, uma float, uma de
caracteres e uma string, sao criadas e tem valores associados; entao, o arquivo e aberto e elas sao
gravadas la.

1 # include < stdio .h >


2 # define NOME_ARQUIVO " meu_texto . txt "
3 # define TAM_STR 50
4
5 int main ( void )
6 {
7 float real = 547.32;
8 char carac = T ;
9 char str [ TAM_STR ] = " teste para string " ;
10 FILE * arq ;
11
12 if ( !( arq = fopen ( NOME_ARQUIVO , " w " ) ) ) {
13 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;
14 exit (1) ;
15 }
16 fprintf ( arq , " % f \ nIsso e uma string constante \ n % s \ n % c " , real , str , carac ) ;
17 }
244 CAPITULO 8. ARQUIVOS

Exemplo 8.5: Uso de fprintf().

A execucao desse programa resultara num arquivo com o seguinte texto:

547.32
Isso e uma string constante
teste para string
T

8.6 Operacoes sobre arquivos binarios


Como ja explicado, arquivos binarios armazenam bytes que correspondem diretamente aos valores
das variaveis na memoria principal (as variaveis transientes), e nao somente armazenam valores
caracteres, como os arquivos de texto.
Sao muito uteis principalmente por poderem ser usados para ler e escrever diretamente tipos
definidos pelo programador, como estruturas.

8.6.1 Leitura
A linguagem C possui a funcao fread() que pode ser usada para ler um arquivo binario.

fread()

O prototipo da funcao fread() e:

size t fread (void *buffer, size t num bytes, size t count, FILE *fp);

O primeiro parametro dessa funcao, buffer, e um ponteiro para uma regiao de memoria que
recebera as variaveis lidas do arquivo. O numero de bytes a serem lidos e especificado por num bytes;
e essa variavel que informa a funcao qual o tipo de variavel persistente a ser lida, pelo uso do operador
sizeof() (que retorna o tamanho do tipo passado como argumento). O parametro count indica
quantas variaveis do tamanho num bytes deverao ser lidas com essa chamada da funcao fread(). E
fp e um ponteiro para o arquivo de onde serao lidos os valores.
O tipo de retorno da funcao, size t, e definido no arquivo stdio.h, e e aproximadamente igual
a um inteiro sem sinal. Esse retorno pode ser avaliado para verificar se algum erro ocorreu, pois
fread() retornara a quantidade de itens lidos. Esse valor deve ser igual a count; se nao for, entao o
arquivo chegou ao final antes de ler a quantidade solicitada ou um erro ocorreu.
O exemplo 8.6 mostra a leitura de um inteiro, depois de um float, e entao de um caractere, de
um arquivo binario. Considere que o arquivo arq1 ja foi aberto nesse trecho de codigo.
8.6. OPERACOES SOBRE ARQUIVOS BINARIOS 245

1 int inteiro ;
2 float real ;
3 char carac ;
4 /* ... */
5 fwrite (& inteiro , sizeof ( int ) , 1 , arq1 ) ;
6 fwrite (& real , sizeof ( float ) , 1 , arq1 ) ;
7 fwrite (& carac , sizeof ( char ) , 1 , arq1 ) ;

Exemplo 8.6: Uso de fwrite().

Note que as tres variaveis devem estar gravadas em sequencia no arquivo. Note tambem que
buffer e um ponteiro para as variaveis que armazenarao valores lidos do arquivo.

8.6.2 Escrita
Ja para se realizar a escrita de variaveis persistentes num arquivo binario, a linguagem C oferece a
funcao fwrite().

fwrite()
Seu prototipo e:

size t fwrite (void *buffer, size t num bytes, size t count, FILE *fp);

Essa funcao se assemelha muito com fread(). O parametro buffer e um ponteiro para a variavel
que sera escrita no arquivo. Ja num bytes e usado com o operador sizeof() para informar a funcao
quantos bytes contem o tipo da variavel a ser escrita. count indica quantas variaveis (do tamanho
de num bytes) serao escritas. E, finalmente, fp e o ponteiro para FILE que indica em qual arquivo
devem ser escritas as variaveis.
O exemplo 8.7 mostra um programa completo que gera uma struct, a grava em um arquivo e
depois fecha-o. Entao, esse arquivo e reaberto e essa struct e lida dele (somente para exemplo, pois
os valores da struct ainda estavam corretos). Depois, esses dados lidos serao gravados em um outro
arquivo, texto.

1 # include < stdio .h >


2
3 typedef struct {
4 char nome [50];
5 int idade ;
6 char sexo ;
7 } TPessoa ;
8
9 int main ( void )
10 {
11 FILE * arq ;
246 CAPITULO 8. ARQUIVOS

12 TPessoa p ;
13
14 p . nome = " Marcia Maia " ;
15 p . idade = 46;
16 p . sexo = F ;
17
18 if ( !( arq = fopen ( " arquivo_bin . teste " , " wb " ) ) ) {
19 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;
20 exit (1) ;
21 }
22 fwrite (& p , sizeof ( TPessoa ) , 1 , arq ) ;
23 fclose ( arq ) ;
24
25 if ( !( arq = fopen ( " arquivo_bin . teste " , " rb " ) ) ) {
26 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;
27 exit (1) ;
28 }
29 fread (& p , sizeof ( TPessoa ) , 1 , arq ) ;
30 fclose ( arq ) ;
31
32 if ( !( arq = fopen ( " arquivo_texto . txt " , " w " ) ) ) {
33 printf ( Erro na abertura de arquivo ! Abortando o programa ...) ;
34 exit (1) ;
35 }
36 fprintf ( arq , " % s \ n % d \ n % c " , p . nome , p . idade , p . sexo ) ;
37 fclose ( arq ) ;
38 }

Exemplo 8.7: Gravacao e leitura de struct em arquivos

Pode-se ver que ao final da execucao do programa, arquivo texto.txt contera os dados da pessoa,
e poderao ser lidos assim que o arquivo for aberto por um editor de texto qualquer (fato que nao
ocorre com o arquivo arquivo bin.teste, binario).

8.7 Outras funcoes uteis para arquivos

As secoes anteriores mostraram as funcoes basicas para se trabalhar com arquivos em C. Porem,
essas funcoes nao sao suficientes para realizar todas as tarefas que arquivos permitem.
Serao apresentadas duas funcoes nessa secao: feof(), usada para verificar se o indicador de
posicao num arquivo chegou ao final dele; e fseek(), usada para posicionar o indicador de posicao
num local especfico do arquivo.
Os exemplos apresentados nessa sessao serao de programas completos e mais complexos dos que
os anteriormente mostrados.
8.7. OUTRAS FUNCOES UTEIS PARA ARQUIVOS 247

8.7.1 feof()
Uma funcao essencial usada para trabalhar com arquivos e feof(). Seu prototipo e:

int feof (FILE *fp);

Essa funcao retornara 0 se o arquivo fp passado como argumento ainda nao chegou ao final.
A verificacao se da pelo indicador de posicao no arquivo. Assim, a funcao feof() e utilizada para
verificar quando um arquivo, tanto de texto quando binario, termina.
No exemplo 8.8, existe um arquivo previamente criado, binario, que armazena muitas variaveis
struct do tipo TFuncionario. O programa abre esse arquivo, le todas as variaveis la presentes (uma
quantia inicialmente desconhecida), e vai gravando-as num arquivo texto.
Note que esse programa usa o tipo TFuncionario como um TAD (tipo abstrato de dados):
existe uma funcao para inicializar o funcionario, e funcoes que operam com um funcionario (lendo,
gravando...). A funcao main(), que usa essas funcoes, nao acessa os campos de uma struct TFun-
cionario. Alias, o programador da funcao main(), que usa o TAD funcionario, nem sequer precisa
saber como ele e implementado!
O programa funciona da seguinte forma: a funcao main() abre os arquivos, e entao, usa a funcao
le func() para ler um funcionario de cada vez do arquivo de leitura (se a leitura do funcionario nao
ocorrer corretamente, a funcao le func() usa a funcao invalida func() para indicar que a leitura
desse funcionario nao foi feita corretamente). Entao, a main() verifica se esse funcionario foi lido
corretamente, usando a funcao func valido(). Se sim, grava-o no arquivo texto, usando a funcao
grava func().

1 # include < stdio .h >


2 # define ARQUIVO_LEITURA binario
3 # define ARQUIVO_ESCRITA texto . txt
4
5 typedef struct {
6 char nome [50];
7 char sexo ;
8 int idade ;
9 int matricula ;
10 float salario ;
11 } TFuncionario ;
12
13 TFuncionario inicializa_func ( void ) ;
14 TFuncionario le_func ( FILE * arqbin ) ;
15 TFuncionario invalida_func ( void ) ;
16 int func_valido ( TFuncionario f ) ;
17 void grava_func ( TFuncionario f , FILE * arqtex ) ;
18
19 int main ( void )
20 {
21 FILE * arqbin , * arqtex ;
22 TFuncionario f = inicializa_func () ;
248 CAPITULO 8. ARQUIVOS

23
24 /* Bloco de codigo responsavel pela correta abertura dos arquivos */
25 if ( !( arqbin = fopen ( ARQUIVO_LEITURA , " rb " ) ) ||
26 !( arqtex = fopen ( ARQUIVO_ESCRITA , " rb " ) ) ) {
27 printf ( " Erro na abertura de arquivos ! Abortando ... " ) ;
28 exit (1) ;
29 }
30
31 /* Bloco de codigo que contem o loop while , que usa a func~ a o feof () para
32 ler o arquivo */
33 while ( ! feof ( arqbin ) ) {
34 f = le_func ( arqbin ) ;
35 if ( func_valido ( f ) ) {
36 grava_func (f , arqtex ) ;
37 } else {
38 printf ( " Erro na leitura de funcionarios . Abortando ... " ) ;
39 exit (1) ;
40 }
41 }
42 }
43
44 TFuncionario inicializa_func ( void )
45 {
46 TFuncionario f ;
47 f . matricula = 0;
48 return ( f ) ;
49 }
50
51 TFuncionario le_func ( FILE * arqbin )
52 {
53 TFuncionario f ;
54
55 if ( ( fread (& f , sizeof ( TFuncionario ) , 1 , arqbin ) ) != 1) {
56 f = invalida_func () ;
57 }
58
59 return ( f ) ;
60 }
61
62 TFuncionario invalida_func ( void )
63 {
64 TFuncionario f ;
65 f . matricula = -1;
66 return ( f ) ;
67 }
68
69
70 int func_valido ( TFuncionario f )
71 {
72 if ( f . matricula < 0) {
73 return (0) ;
8.7. OUTRAS FUNCOES UTEIS PARA ARQUIVOS 249

74 }
75 else {
76 return (1) ;
77 }
78 }
79
80 void grava_func ( TFuncionario f , FILE * arqtex )
81 {
82 fprintf ( arqtex , Nome : % s \ nSexo : % c \ nIdade : % d \ nMatricula : % d \ n
83 Salario : % f \ n \n , f . nome , f . sexo , f . idade , f . matricula , f . salario ) ;
84 }

Exemplo 8.8: Uso de feof() e outras funcoes no TAD TFuncionario

8.7.2 fseek()
Muitas vezes, deseja-se ter controle sobre o indicador de posicao num arquivo. Ao se saber, por
exemplo, qual o ndice de uma estrutura especfica ja gravada, do total de todas as estruturas que
fazem parte de um arquivo binario, pode-se buscar essa estrutura desejada sem ter que carregar para
a memoria todas as outras. Isso pode ser feito posicionando-se o indicador de posicao no arquivo e
lendo somente a proxima estrutura (que sera a desejada).
A funcao em C que permite fazer isso e fseek(). Seu prototipo e:

int fseek (FILE *fp, long numbytes, int origin);

A funcao retornara 0 se for bem-sucedida; caso contrario, retornara diferente de 0. Seus argu-
mentos sao: fp, um ponteiro para arquivo ja aberto por fopen(); numbytes, um numero que indica
quantos bytes a partir do parametro origin estara o indicador de posicao; e, finalmente, origin sera
uma das tres macros mostradas abaixo (que estao definidas no arquivo STDIO.H):

Posicao Parametro
Incio do arquivo SEEK TEST
Posicao atual SEEK CUR
Final do arquivo SEEK END

Assim, para se posicionar duas variavel int a frente da posicao inicial do arquivo arq (ou seja,
pronto para ler a terceira variavel int armazenada), faca:

fseek (arq, 2 * sizeof(int), SEEK SET);

O exemplo 8.9 mostra a funcao obtem func posicao(). Essa funcao e usada para ler structs
TFuncionario (definidas no Exemplo 8.8) contidas no arquivo binario (tambem definido no exemplo)
passando-se o ndice do funcionario que se deseja ler. Ela recebe um ponteiro para o arquivo binario
250 CAPITULO 8. ARQUIVOS

de leitura, ja aberto com fopen(), de onde tentara ler a struct de ndice i, um parametro inteiro
passado. Assim, se i=1, o retorno da funcao sera a primeira struct; se i=3, sera a terceira; etc.
Ela usara a funcao invalida func(), tambem definida nesse exemplo, para indicar que houve erro na
leitura do funcionario especificado.

1 funcionario obtem_func_posicao ( int i , FILE * arqbin )


2 {
3 funcionario f ;
4
5 if ( fseek ( arqbin , (i -1) * sizeof ( funcionario ) , SEEK_SET ) ) {
6 f = invalida_func () ;
7 return ( f ) ;
8 }
9 if ( ( fread (& f , sizeof ( funcionario ) , 1 , arqbin ) ) != 1) {
10 f = invalida_func () ;
11 }
12 return ( f ) ;
13 }

Exemplo 8.9: Uso de fseek() para posicionamento num arquivo binario

8.8 Exercicios Resolvidos


Nessa secao sera proposto e resolvido um exerccio que engloba muitos temas que foram estudados
nesse e nos captulos anteriores.

8.8.1 O Tipo Abstrato de Dados TDicionario


O exerccio consiste na na implementado de um TAD (Tipo Abstrato de Dados) Dicionario. Esse
tipo correspondera a uma serie de duplas (chave, valor), em que tanto chave quanto valor sao strings;
a interpretacao desses dados e que chave corresponde a palavras que serao explicadas por valor. Por
exemplo, duas entradas do dicionario poderiam ser (Esprito Santo, estado brasileiro localizado
na regiao Sudeste) e (C, Linguagem de programacao de nvel medio).
A uma variavel do tipo TDicionario declarada corresponderao varios valores de (chave, valor).
Essas entradas deverao ficar armazenadas num arquivo, denominado arquivo de dados. Assim, sempre
que se desejar consultar se alguma palavra esta definida no dicionario (exemplo: ha alguma entrada
para cachorro ?), o arquivo de dados daquela variavel dicionario devera ser consultado. O mesmo
para quando uma palavra for inserida num dicionario: ela sera acrescentada ao arquivo de dados
desse dicionario, ao final dele.
Supoe-se que o arquivo de dados sera muito grande e que nao podera ser carregado todo para
a memoria principal. Tambem, carregar, do disco para a memoria, sequencialmente, as entradas do
dicionario salvas no arquivo de dados (a fim de fazer uma busca, por exemplo) nao sera uma solucao
8.8. EXERCICIOS RESOLVIDOS 251

aceitavel pelo tempo que isso demanda: o acesso ao disco se da na ordem de milissegundos, muito
superior aos tempos envolvidos nas operacoes da memoria e do processador.
Por esse motivo, um segundo arquivo estara tambem associado a cada variavel TDicionario: um
arquivo de ndices. Esse arquivo contera duplas (chave, ndice), em que chave corresponde as mesmas
chaves includas no arquivo de dados, e ndice indica o ndice (numerico) dessa chave no arquivo de
dados.
Segue exemplo de uma variavel TDicionario denominada meu dic, e associada a arquivos com
nome meu dicionario. Note que ambos os arquivos sao binarios, assim, o que e mostrado abaixo e
apenas o aspecto deles eles nao poderiam ser lidos diretamente de um editor de texto convencional.

Arquivo de dados: meu dicionario dados

Esprito Santo
estado brasileiro localizado na regi~ ao Sudeste
C
linguagem de programac~
ao de nvel medio
cachorro
mamfero considerado "o melhor amigo do homem"

Arquivo de ndices: meu dicionario indice

Esprito Santo
0
C
1
cachorro
2

O arquivo de ndices, por ser consideravelmente menor que o de dados, podera ser carregado todo
para a memoria e entao acessado, a fim de realizar-se uma busca. Dessa forma, pode-se descobrir
que cachorro e, sim, uma entrada, e esta no ndice 2 (terceira entrada) do arquivo de dados.
As entradas do arquivo de dados serao structs com dois campos strings, e as do arquivo de ndices
structs com um campo string e um campo int. Uma variavel TDicionario sera uma struct com campos
que indicam o nome dos seus dois arquivos associados, um campo que armazena a matriz de ndices
em memoria, e um campo que guarda o total de entradas armazenadas no dicionario.
As operacoes que o TAD dicionario aceitara sao: inicializacao de um novo dicionario, abertura
de um dicionario ja existente, busca de uma palavra e acrescimo de uma palavra. Seguem abaixo
definicoes dessas funcoes:

TDicionario inicializa dic (char *nome do dicionario)

Ao chamar essa funcao, o usuario passara o nome do dicionario como argumento (uma string).
Entao os dois arquivos serao criados; seus nome serao a string nome do dicionario acrescida de
252 CAPITULO 8. ARQUIVOS

dados, para o caso do arquivo de dados, e indice, para o arquivo de ndices. Na memoria
ficara armazenada uma tabela de ndices (ou seja, um vetor de structs), inicialmente vazia.

TDicionario carrega dic (char *nome do dicionario)

Essa funcao e usada para abrir um dicionario ja existente. Sua chamada acarreta na abertura do
arquivo de ndices (com nome indicado por nome do dicionario) e seu carregamento para a memoria;
apos isso, o arquivo de ndices sera fechado.

int busca dic (TDicionario dic, char *palavra, char **retorno)

O uso dessa funcao acarreta na busca, na matriz de ndices em memoria (indicada por dic), da
string indicada por palavra. Se for encontrada, entao se sabera seu ndice; o arquivo de ndice entao
sera aberto, o indicador de posicao sera posicionado nesse ndice indicado, e a informacao desejada (o
valor correspondente a essa chave) sera lida e armazenada no argumento retorno. A funcao retornara
1 para indicar que a palavra foi encontrada e 0 senao.

int insere dic (TDicionario dic, char *chave, char *valor)

Essa funcao sera usada para inserir, no dicionario dic, a palavra chave com a sua descricao valor.
Inicialmente uma busca pela chave devera ser realizada (na tabela de ndices em memoria); se ela
nao existir, a sim podera ser inserida. Nesse caso, o arquivo de dados e aberto, e a chave e o
valor sao armazenados ao fim dele; ele e fechado apos isso. A matriz de ndices em memoria devera
tambem ser modificada, com o acrescimo, ao fim dela, da chave e do seu ndice no arquivo de dados
(e possvel saber o ndice observando a quantidade de entradas ja armazenadas, indicada por dic). O
arquivo de ndices devera entao ser aberto, a matriz de ndices em memoria (ja atualizada) gravada
nele, e logo em seguida fechado.

Implementacao do TAD TDicionario

1 # include < stdio .h >


2 # include < string .h >
3 # include < stdlib .h >
4
5 # define TAM_CHAVE 50
6 # define TAM_VALOR 500
7 # define TAM_NOME_ARQ 20
8 # define MAX_ENTRADAS 200
9
10 /* duplas ( chave , valor ) */
11 typedef struct {
12 char chave [ TAM_CHAVE ];
13 char valor [ TAM_VALOR ];
8.8. EXERCICIOS RESOLVIDOS 253

14 } entrada_dic ;
15
16 /* duplas ( chave , indice ) */
17 typedef struct {
18 char chave [ TAM_CHAVE ];
19 int indice ;
20 } indice_dic ;
21
22 /* definicao da variavel do TAD dicionario */
23 typedef struct {
24 char nome_arq_dados [ TAM_NOME_ARQ ];
25 char nome_arq_indices [ TAM_NOME_ARQ ];
26 indice_dic matriz_indices [ MAX_ENTRADAS ];
27 int tam ;
28 } TDicionario ;
29
30 /* funcao que inicializa um novo dicionario */
31 TDicionario inicializa_dic ( char * nome_do_dicionario )
32 {
33 TDicionario dic ;
34 FILE * arq ;
35
36 /* acrescenta " _indice " ao nome_do_dicionario */
37 strcpy ( dic . nome_arq_indices , nome_do_dicionario ) ;
38 strcat ( dic . nome_arq_indices , " _indice " ) ;
39
40 /* criacao do arquivo de indices */
41 if ( ( arq = fopen ( dic . nome_arq_indices , " wb " ) ) == NULL ) {
42 printf ( " Erro na criacao de arquivos ! Abortando ... " ) ;
43 exit (1) ;
44 }
45 fclose ( arq ) ;
46
47 /* acrescenta " _dados " ao nome_do_dicionario */
48 strcpy ( dic . nome_arq_dados , nome_do_dicionario ) ;
49 strcat ( dic . nome_arq_dados , " _dados " ) ;
50
51 /* criacao do arquivo de dados */
52 if ( ( arq = fopen ( dic . nome_arq_dados , " wb " ) ) == NULL ) {
53 printf ( " Erro na criacao de arquivos ! Abortando ... " ) ;
54 exit (1) ;
55 }
56 fclose ( arq ) ;
57
58 dic . tam = 0;
59
60 return ( dic ) ;
61 }
62
63 /* funcao que carrega um dicionario */
64 void carrega_dic ( TDicionario * dic , char * nome_do_dicionario )
254 CAPITULO 8. ARQUIVOS

65 {
66 FILE * arq ;
67 int i ;
68
69 /* acrescenta " _dados " ao nome_do_dicionario */
70 strcpy ( dic - > nome_arq_dados , nome_do_dicionario ) ;
71 strcat ( dic - > nome_arq_dados , " _dados " ) ;
72
73 /* acrescenta " _indice " ao nome_do_dicionario */
74 strcpy ( dic - > nome_arq_indices , nome_do_dicionario ) ;
75 strcat ( dic - > nome_arq_indices , " _indice " ) ;
76
77 /* abertura do arquivo de indices */
78 if ( ( arq = fopen ( dic - > nome_arq_indices , " rb " ) ) == NULL ) {
79 printf ( " Erro na abertura de arquivos ! Abortando ... " ) ;
80 exit (1) ;
81 }
82
83 /* leitura e contagem do numero de entradas no arquivo de indice */
84 dic - > tam = 0;
85 for ( i =0; ! feof ( arq ) ; i ++) {
86 fread (& dic - > matriz_indices [ i ] , sizeof ( indice_dic ) , 1 , arq ) ;
87 dic - > tam ++;
88 }
89 }
90
91 /* funcao que busca uma chave no dicionario passado . Se encontrar
92 retornara 1 e retornara seu valor no argumento retorno */
93 int busca_dic ( TDicionario * dic , char * chave , char * retorno )
94 {
95 int i ;
96 FILE * arq ;
97
98 entrada_dic entrada ;
99
100 /* procura na tabela de indices em memoria a chave */
101 for ( i =0; i < dic - > tam ; i ++) {
102 if (! strcmp ( dic - > matriz_indices [ i ]. chave , chave ) ) {
103 /* se estou aqui , entao encontrei a palavra ! */
104
105 if ( ( arq = fopen ( dic - > nome_arq_dados , " rb " ) ) == NULL ) {
106 printf ( " Erro na abertura de arquivos ! Abortando ... " ) ;
107 exit (1) ;
108 }
109 /* posiciona , le e retorna o valor da chave */
110 fseek ( arq , i * sizeof ( entrada_dic ) , SEEK_SET ) ;
111 fread (& entrada , sizeof ( entrada_dic ) , 1 , arq ) ;
112 strcpy ( retorno , entrada . valor ) ;
113 fclose ( arq ) ;
114 return 1;
115 }
8.8. EXERCICIOS RESOLVIDOS 255

116 }
117 /* se cheguei ate aqui , entao nao encontrei a palavra */
118 return 0;
119 }
120
121 /* funcao que insere , no dicionario , a chave ( palavra ) passada
122 e seu valor . Retorna 1 se inserir , 0 senao */
123 int insere_dic ( TDicionario * dic , char * chave , char * valor )
124 {
125 char pega_valor [ TAM_VALOR ];
126 FILE * arq ;
127 entrada_dic entrada ;
128 indice_dic chave_ind ;
129
130 if ( ! busca_dic ( dic , chave , pega_valor ) ) {
131 /* Se a chave nao existe no dic . , anexo ao arquivo de dados */
132
133 if ( ( arq = fopen ( dic - > nome_arq_dados , " ab " ) ) == NULL ) {
134 printf ( " Erro na abertura de arquivos ! Abortando ... " ) ;
135 exit (1) ;
136 }
137
138 strcpy ( entrada . chave , chave ) ;
139 strcpy ( entrada . valor , valor ) ;
140
141 fwrite (& entrada , sizeof ( entrada_dic ) , 1 , arq ) ;
142 fclose ( arq ) ;
143
144 /* insere ao final da tabela e arquivo de indices */
145
146 strcpy ( dic - > matriz_indices [ dic - > tam ]. chave , chave ) ;
147 dic - > matriz_indices [ dic - > tam ]. indice = dic - > tam ;
148 dic - > tam ++;
149
150 if ( ( arq = fopen ( dic - > nome_arq_indices , " ab " ) ) == NULL ) {
151 printf ( " Erro na abertura de arquivos ! Abortando ... " ) ;
152 exit (1) ;
153 }
154
155 strcpy ( chave_ind . chave , chave ) ;
156 chave_ind . indice = dic - > tam ;
157 fwrite (& chave_ind , sizeof ( indice_dic ) , 1 , arq ) ;
158 fclose ( arq ) ;
159
160 return 1;
161 }
162 else printf ( " A chave passada ja existe no dicionario !\ n " ) ;
163 return 0;
164 }
165
166 int main ()
256 CAPITULO 8. ARQUIVOS

167 {
168 TDicionario dic , outro_dic ;
169 entrada_dic entrada ;
170
171 dic = inicializa_dic ( " meu_dicionario " ) ;
172
173 strcpy ( entrada . chave , " Espirito Santo " ) ;
174 strcpy ( entrada . valor , " estado brasileiro localizado na regiao Sudeste " ) ;
175 insere_dic (& dic , entrada . chave , entrada . valor ) ;
176
177 strcpy ( entrada . chave , " C " ) ;
178 strcpy ( entrada . valor , " linguagem de programacao de nivel medio " ) ;
179 insere_dic (& dic , entrada . chave , entrada . valor ) ;
180
181 carrega_dic (& outro_dic , " meu_dicionario " ) ;
182 strcpy ( entrada . chave , " C " ) ;
183 strcpy ( entrada . valor , " linguagem de programacao de nivel medio " ) ;
184 insere_dic (& outro_dic , entrada . chave , entrada . valor ) ;
185 }

Exemplo 8.10: Implementacao do TAD TDicionario

Alguns aprimoramentos para o TAD TDicionario


Pelo fato das insercoes de novos valores se darem sempre ao final das estruturas de dados, ja que
insere-se ao final da tabela de indices em memoria, do arquivo de indices e tambem do arquivo de
dados, os ndices das entradas acabam sempre coincidindo. Isso faz com que, por exemplo, a terceira
entrada do dicionario seja a terceira entrada do arquivo de dados, do arquivo de ndices e da tabela
de ndices em memoria (e que a tabela armazene o ndice 2 para esse elemento).
Uma alteracao pode ser feita com a tabela de ndices em memoria. Ela pode ser mantida
ordenada se cada novo elemento for inserido nela respeitando a ordem alfabetica de sua chave. Com
essa ordenacao, as entradas podem ser buscadas rapidamente na tabela, mediante o uso, por exemplo,
de uma busca binaria, um algoritmo de busca que trabalha particionando o vetor de busca (ordenado)
em duas metades, e entao, recursivamente, refaz a busca ou na metade superior ou inferior, via a
observacao do valor a ser buscado. Uma sugestao para a implementacao desse aprimoramento e
a criacao de duas novas funcoes: uma delas, que seria usada pela funcao busca dic(), realizaria a
busca binaria de um valor string num vetor de indice dic passado; a outra funcao, a ser usada por
insere dic(), faria a insercao ordenada no vetor de indice dic passado (note que essa funcao poderia
usar a funcao de busca binaria anteriormente descrita).
O arquivo de ndices poderia ser mantido desordenado, via a insercao de novos valores sempre
ao seu final. O importante, para demandar o menor tempo computacional possvel, e usar menos
entrada e sada (como o acesso aos dados do disco). Mas, como a tabela de ndices em memoria deve
ser mantida organizada, ao ser carregado um dicionario (via a funcao carrega dic() ), as informacoes
devem ser lidas do arquivo e inseridas ordenadamente no vetor em memoria.
Com esse aperfeicoamento descrito, um valor seria encontrado muito mais rapidamente na tabela
8.9. RESUMO 257

de ndices em memoria, para entao ser descoberto seu ndice no arquivo de dados, e a dupla (chave,
informacao) desejada finalmente ser carregada para memoria.

8.9 Resumo
Variaveis transientes sao armazenadas na memoria principal. Tem tempo de acesso rapido, mas
duracao limitada: so existem enquanto o programa que as esta utilizando estiver na memoria.
Variaveis persistentes nao perdem suas informacoes quando o programa que as criou nao esta
mais em memoria, pois estao armazenadas em meio secundario: disco rgido, memoria flash,
disquete, CD...

Arquivos texto so armazenam caracteres, e sao compreensveis ao serem lidos em um editor de


texto. Ja arquivos binarios armazenam variaveis no formato em que elas sao armazenadas em
memoria;

Um arquivo deve ser aberto para poder ser utilizado, e apos essa utilizacao, deve ser fechado;

As variaveis persistentes podem ser escritas e lidas dos arquivos. Existem funcoes especficas
para essas tarefas, algumas designadas para arquivos binarios e outras para arquivos texto;

A linguagem C oferece uma serie de funcoes para lidar com arquivos, e esse captulo apre-
sentou algumas, como fopen(), usada para abrir arquivos, e fread(), usada para ler variaveis
persistentes de arquivos binarios.

8.10 Exerccios
1. Escreva um programa em C que abra um arquivo texto e que conte a quantidade de caracteres
armazenados nele. Imprima o numero na tela. O programa deve solicitar ao usuario que digite
o nome do arquivo.

2. Escreva um programa em C que solicite ao usuario a digitacao do nome de um arquivo texto


ja existente, e que entao gere um outro arquivo, que sera uma copia do primeiro.

3. Considere um arquivo texto que armazene numeros em ponto flutuante em cada uma de suas
linhas. Ou seja, o arquivo inicia com uma quantidade de um ou mais caracteres representando
numeros inteiros, entao segue um caractere de ponto (.) e mais alguns inteiros; em seguida,
uma quebra de linha; na proxima linha esse formato prossegue, ate que, no fim da ultima linha,
nao ocorre quebra de linha. Escreva um programa em C que determine o valor maximo, o valor
mnimo e a media desses valores armazenados no arquivo. Imprima esses valores na tela.

4. Considere o programa escrito para a questao anterior. Exiba na tela, agora, o valor n corres-
pondente a quantidade de numeros reais contidos no arquivo e o valor maximo, mnimo e a
258 CAPITULO 8. ARQUIVOS

media calculados anteriormente, porem agora divididos por n. Depois, gere um novo arquivo
que contenha cada um dos numeros reais do arquivo divididos por n, um por linha. Procure
reutilizar, nessa questao, o maior numero possvel das funcoes que voce tenha construdo para
a questao anterior. Modularizar seu codigo (usar funcoes) e uma forma de organizacao e de
poupar trabalho duplicado.

5. Considere um arquivo texto que armazene caracteres variados, ou seja, um texto digitado.
Escreva um programa que o leia e gere um novo arquivo que contenha somente as letras do
arquivo original, na ordem em que la aparecem (ou seja, caracteres de A-Z ou a-z).

6. Para um arquivo do mesmo tipo do da questao anterior (que armazene caracteres variados),
escreva um programa em C que determine a media dos comprimentos de todas as palavras
que se encontram nele. Entende-se por palavra um conjunto de caracteres de letras que esta
separado de outros conjuntos de caracteres no arquivo por um (ou mais) caractere de espaco
em branco ( , tabulacoes ou quebra de linha); e seu comprimento sera a quantidade de
caracteres que o formam.

7. Considere um arquivo texto como o usado na questao anterior. Faca um programa que o leia
e que permita ao usuario consultar uma das linhas do arquivo, solicitando a ele que informe
o ndice n dessa linha. O programa deve imprimir a linha especificada ou a mensagem de que
ela nao existe.

8. Escreva um programa em C que receba via teclado o nome de um arquivo texto e uma palavra.
O programa deve imprimir todas as linhas que possuem essa palavra. Dica: procure usar
algumas funcoes que voce tenha construdo para a questao anterior; provavelmente, algumas
delas poderao ser reaproveitadas.

9. Escreva um programa em C que receba via teclado o nome de um arquivo texto. O programa
deve solicitar ao usuario que digite o ndice da linha inicial e da linha final, e o programa deve
imprimi-las e todas as linhas entre elas. Se o ndice superior de linhas nao existe, esse erro deve
ser informado ao usuario (mas as linhas existentes devem, ainda, ser impressas). Novamente,
procure usar algumas funcoes que voce tenha escrito para as questoes anteriores.

10. Considere, agora, os numeros inteiros armazenados num arquivo texto como os usados nas
questoes anteriores. Entende-se por um numero inteiro nesse arquivo um conjunto de caracteres
representando numeros inteiros (ou seja, no intervalo 0-9) separados de outros conjuntos de
caracteres por um (ou mais) caractere de espaco em branco ( , tabulacoes ou quebras de
linha). Escreva um programa em C que produza dois arquivos texto: o primeiro com os numeros
pares da sequencia original e o segundo com os numeros impares. Os arquivos de sada devem
conter um numero por linha.
8.10. EXERCICIOS 259

11. Escreva um programa em C que leia um texto fornecido pelo usuario via teclado e o armazene
em um arquivo. O fim da entrada de texto e sinalizada por um ponto no incio de uma nova
linha. O ponto utilizado para marcar o fim da entrada de texto nao deve aparecer no arquivo
gerado pelo programa.

12. Considere um arquivo que apresente o nome de um aluno na primeira linha; na seguinte a
nota de sua primeira prova; e na seguinte a nota de sua segunda prova. Essas 3 linhas de
informacoes se repetem no total de alunos no arquivo de entrada. Faca um programa que
imprima os nomes de todos os alunos que tem a media das duas notas menor que 7.0.

13. Escreva um programa que leia um arquivo texto contendo linhas de dados. Em cada linha do
arquivo ha o nome de um aluno e duas notas. Esses dados estao separados por ponto e vrgula.
Existe um ponto e vrgula no final de cada linha. O programa deve ler esses dados e imprimir
os valores lidos, a media das duas notas, e se o aluno foi aprovado ou nao (com nota maior ou
igual a 5).

14. Escreva um programa em C que solicita ao usuario a digitacao de um nome de arquivo. Esse
arquivo sera criado e o usuario informara numeros em ponto flutuante, que serao gravados no
arquivo. O arquivo sera um arquivo binario. Considere que ele pode armazenar no maximo 50
valores, mas que o usuario pode encerrar a entrada de novos valores com a digitacao de um
numero negativo.

15. Escreva um programa em C que receba o nome de um arquivo binario, o qual armazena numeros
em ponto flutuante, no mesmo formato que na questao anterior. O programa deve ler todos
esses valores e grava-los na memoria; entao, ordena-los, e regrava-los num arquivo texto, com
um numero por linha.

16. Escreva um programa em C que receba via teclado o nome de um arquivo binario. Esse arquivo
armazenara nome, idade e sexo de pessoas, como no TAD TFuncionario. O programa deve
solicitar ao usuario a digitacao desses dados e armazena-los no arquivo depois. O programa
termina quando o usuario digitar enter na entrada do nome, ou seja, informar um nome vazio.

17. Escreva um programa em C que receba via teclado o nome do arquivo binario da questao
anterior. Entao, ele deve abrir o arquivo. Deve existir um menu que ofereca ao usuario a
possibilidade de poder: 1) imprimir (na tela) todos os registros la contidos; 2) acessar os
dados de uma pessoa passando o ndice dela no arquivo (1 para a primeira pessoa no arquivo,
5 para a quinta pessoa no arquivo...); 3) o programa deve informar a quantidade total de
homens e mulheres armazenadas no arquivo; 4) O programa deve permitir ao usuario digitar
novas pessoas. Esses novos dados digitados devem ser gravados no arquivo (e posteriormente
descarregados da memoria) somente quando o usuario escolher, no menu, a opcao para sair
do programa.
260 CAPITULO 8. ARQUIVOS

8.11 Trabalhos Sugeridos


Falta acesso aos trabalhos sugeridos dos outros captulos.
Referencias Bibliograficas

[1] BRIAN KERNIGHAN and DENNIS RITCHIE. The C Programming Language. second edition,
1988.

[2] PAUL STRATHERN. Turing e o Computador em 90 Minutos. first edition, 2000.

[3] ANDREW S TANENBAUM and JAMES R GOODMAN. Structured Computer Organization.


fourth edition, 1999.

261

Você também pode gostar