Apostila C
Apostila C
Apostila C
Em Construção
v0.001
20 de Agosto de 2009
2
Conteúdo
1 Introdução 19
1.1 Sucessos e Fracassos da Computação . . . . . . . . . . . . . . . . 19
1.2 Um Pouco da História da Computação . . . . . . . . . . . . . . . 21
1.2.1 O Inı́cio . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2.2 A Era Moderna . . . . . . . . . . . . . . . . . . . . . . . . 21
1.2.3 O Desenvolvimento durante as Grandes Guerras . . . . . 24
1.2.4 As Gerações . . . . . . . . . . . . . . . . . . . . . . . . . . 26
1.3 O Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.3.1 Microcomputadores . . . . . . . . . . . . . . . . . . . . . 28
1.3.2 Memórias . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.3.3 Bits e Bytes . . . . . . . . . . . . . . . . . . . . . . . . . . 31
1.3.4 Periféricos . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.4 O Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
1.5 Um programa em C . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.6 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2 Algoritmos 41
2.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.2 Primeiros Passos . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
2.3 Representação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.3.1 Linguagem Natural . . . . . . . . . . . . . . . . . . . . . . 44
2.3.2 Fluxogramas . . . . . . . . . . . . . . . . . . . . . . . . . 44
2.3.3 Pseudo-Linguagem . . . . . . . . . . . . . . . . . . . . . . 45
2.4 Modelo de von Neumann . . . . . . . . . . . . . . . . . . . . . . . 47
2.5 Estruturas Básicas de Algoritmos . . . . . . . . . . . . . . . . . . 48
2.5.1 Comandos de leitura . . . . . . . . . . . . . . . . . . . . . 49
2.5.2 Comandos de escrita . . . . . . . . . . . . . . . . . . . . . 49
2.5.3 Expressões . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3
4 CONTEÚDO
5 Operadores e Expressões 85
5.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.2 Operador de Atribuição . . . . . . . . . . . . . . . . . . . . . . . 85
5.3 Operadores Aritméticos . . . . . . . . . . . . . . . . . . . . . . . 86
5.4 Operadores Relacionais e Lógicos . . . . . . . . . . . . . . . . . . 87
5.4.1 Operadores Relacionais . . . . . . . . . . . . . . . . . . . 87
5.4.2 Operadores Lógicos . . . . . . . . . . . . . . . . . . . . . 87
5.5 Operadores com Bits . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.6 Operadores de Atribuição Composta . . . . . . . . . . . . . . . . 91
5.7 Operador vı́rgula . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.8 Operador sizeof() . . . . . . . . . . . . . . . . . . . . . . . . . . 92
5.9 Conversão de Tipos . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.10 Regras de Precedência . . . . . . . . . . . . . . . . . . . . . . . . 94
5.11 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6 Comandos de Controle 97
6.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.2 Blocos de Comandos . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.3 Comandos de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.3.1 Comando if . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3.2 Comando switch . . . . . . . . . . . . . . . . . . . . . . . 99
6.3.3 Comando Ternário . . . . . . . . . . . . . . . . . . . . . . 101
6.4 Laços de Repetição . . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.4.1 Comando for . . . . . . . . . . . . . . . . . . . . . . . . . 103
6.4.2 Comando while . . . . . . . . . . . . . . . . . . . . . . . 106
6.4.3 Comando do-while . . . . . . . . . . . . . . . . . . . . . 107
6.5 Comandos de Desvio . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.5.1 Comando break . . . . . . . . . . . . . . . . . . . . . . . 108
6.5.2 Comando continue . . . . . . . . . . . . . . . . . . . . . 109
6.5.3 Comando goto . . . . . . . . . . . . . . . . . . . . . . . . 109
6.5.4 Função exit() . . . . . . . . . . . . . . . . . . . . . . . . 109
6.5.5 Comando return . . . . . . . . . . . . . . . . . . . . . . . 109
6.6 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
6 CONTEÚDO
8 Funções 129
8.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
8.2 Forma Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
8.3 Protótipos de Funções . . . . . . . . . . . . . . . . . . . . . . . . 131
8.4 Escopo de Variáveis . . . . . . . . . . . . . . . . . . . . . . . . . 131
8.4.1 Variáveis Locais . . . . . . . . . . . . . . . . . . . . . . . 132
8.5 Variáveis Globais . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
8.6 Parâmetros Formais . . . . . . . . . . . . . . . . . . . . . . . . . 135
8.6.1 Passagem de Parâmetros por Valor . . . . . . . . . . . . . 135
8.6.2 Passagem de Parâmetros por Referência . . . . . . . . . . 136
8.6.3 Passagem de Vetores e Matrizes . . . . . . . . . . . . . . . 136
8.7 O Comando return . . . . . . . . . . . . . . . . . . . . . . . . . 137
8.8 Recursão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
8.9 Argumentos - argc e arga . . . . . . . . . . . . . . . . . . . . . . 141
8.10 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
9 Ponteiros 145
9.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
9.2 Operações com Ponteiros . . . . . . . . . . . . . . . . . . . . . . 146
9.2.1 Declaração de Ponteiros . . . . . . . . . . . . . . . . . . . 146
9.2.2 Os Operadores Especiais para Ponteiros . . . . . . . . . . 147
9.2.3 Atribuição de Ponteiros . . . . . . . . . . . . . . . . . . . 148
9.2.4 Incrementando e Decrementando Ponteiros . . . . . . . . 149
9.2.5 Comparação de Ponteiros . . . . . . . . . . . . . . . . . . 151
9.3 Ponteiros e Vetores . . . . . . . . . . . . . . . . . . . . . . . . . . 151
9.4 Ponteiros e Cadeias de Caracteres . . . . . . . . . . . . . . . . . 152
9.5 Alocação Dinâmica de Memória . . . . . . . . . . . . . . . . . . . 153
9.6 Ponteiros e Matrizes . . . . . . . . . . . . . . . . . . . . . . . . . 155
9.7 Vetores de Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . 158
9.8 Ponteiros para Ponteiros . . . . . . . . . . . . . . . . . . . . . . . 159
9.9 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
CONTEÚDO 7
10 Estruturas 165
10.1 Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
10.2 Definições Básicas . . . . . . . . . . . . . . . . . . . . . . . . . . 165
10.3 Atribuição de Estruturas . . . . . . . . . . . . . . . . . . . . . . . 168
10.4 Matrizes de Estruturas . . . . . . . . . . . . . . . . . . . . . . . . 168
10.5 Estruturas e Funções . . . . . . . . . . . . . . . . . . . . . . . . . 169
10.6 Ponteiros para Estruturas . . . . . . . . . . . . . . . . . . . . . . 170
10.7 Exercı́cios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9
10 LISTA DE FIGURAS
Lista de Tabelas
11
12 LISTA DE TABELAS
13
14 LISTA DE ALGORITMOS
Listings
15
16 LISTINGS
Introdução
19
20 CAPÍTULO 1. INTRODUÇÃO
espacial. Ora, já passamos por 2001 e não existe a menor possibilidade de se
ver um computador como o HAL ou tão louco de pedra como ele.
Na computação, um exemplo fantástico de sucesso é a Internet. A Internet
está se tornando essencial para o funcionamento do mundo moderno. Freqüen-
temente ouvimos dizer que ela é o meio de comunicação que mais rapidamente se
difundiu pelo mundo. Pode parecer verdade, já que conhecemos tantos internau-
tas e as empresas estão se atropelando para fazer parte desta onda e aproveitar
as suas possibilidades. Esta corrida provocou alguns acidentes e muitos sonhos
de riqueza se esvaı́ram no ar. Hoje, pode-se fazer quase tudo pela Internet,
namorar, comprar, pagar contas, fazer amigos, estudar, jogar etc. Quem sabe,
em um futuro próximo, voltaremos à Grécia Antiga e nos reuniremos em uma
enorme praça virtual para, democraticamente, discutir nossas leis, dispensando
intermediários.
meio de somas. A semelhança entre marfim e ossos, fez com que o dispositivo
fosse conhecido como os ossos de Napier.
Um dos primeiros instrumentos modernos de calcular, do tipo mecânico, foi
construı́do pelo filósofo, matemático e fı́sico francês Blaise Pascal (Figura 1.3).
Em 1642 aos 19 anos, na cidade de Rouen. Pascal desenvolveu uma máquina de
calcular, para auxiliar seu trabalho de contabilidade. A engenhoca era baseada
em 2 conjuntos de discos interligados por engrenagens: um para a introdução
dos dados e outro para armazenar os resultados. A máquina utilizava o sistema
decimal para calcular, de maneira que quando um disco ultrapassava o valor 9,
retornava ao 0 e aumentava uma unidade no disco imediatamente superior.
• Arredondamento automático;
1.2. UM POUCO DA HISTÓRIA DA COMPUTAÇÃO 23
• Precisão dupla;
• Alarmes para avisar fim de cálculo;
• Impressão automática de resultados em placas de cobre.
Na Alemanha
Ele gerava tanto calor que teve de ser instalado em um dos poucos espaços
da Universidade que possuı́a sistemas de refrigeração forçada. Mais de 19000
válvulas, eram os elementos principais dos circuitos do computador. Ele também
tinha quinze mil relés e centenas de milhares de resistores, capacitores e indu-
tores. Toda esta parafernália eletrônica foi montada em quarenta e dois painéis
com mais 2,70 metros de altura, 60 centı́metros de largura e 30 centı́metros de
comprimento, montados na forma da letra U. Uma leitora de cartões perfurados
e uma perfuradora de cartões eram usados para entrada e saı́da de dados.
Os tempos de execução do ENIAC são mostrados na Tabela 1.2. Compare
estes tempos com os tempos dos computadores atuais que estão na ordem de
nano segundos, ou 10−9 segundos.
Operação Tempo
soma 200 µs
multiplicação 2,8 ms
divisão 6,0 ms
Na Inglaterra
János von Neumann, emigrante húngaro que vivia nos EUA, sugeriu que a
memória do computador deveria ser usada para armazenar as instruções do
computador de maneira codificada, o conceito de programa armazenado. Esta
idéia foi fundamental para o progresso da computação. Os primeiros computa-
dores, como o ENIAC, eram programados por fios que os cientistas usavam para
conectar as diversas partes. Quando um programa terminava, estes cientistas
trocavam os fios de posição de acordo com a nova tarefa a ser executada. Com o
programa armazenado na memória, juntamente com os dados, não era mais ne-
cessário interromper as atividades. Carregava-se o programa na memória, uma
tarefa extremamente rápida, junto com os dados e dava-se partida no programa.
Ao término da execução do programa passava-se imediatamente para a próxima
tarefa sem interrupções para troca de fios.
Em 1949, na Inglaterra, dois computadores que usavam a memória para
armazenar tanto programas como dados foram lançados. Na Universidade de
Cambridge foi lançado o EDSAC, (Electronic Delay Storage Automatic Calcula-
tor) e em Manchester o computador chamado de Manchester Mark I. O EDSAC
é considerado o primeiro computador de programa armazenado a ser lançado.
Curiosamente, a Universidade de Manchester reivindica que o primeiro compu-
tador de programa armazenado foi o chamado “Baby”, um protótipo do Mark
I, que começou a operar onze meses antes do EDSAC.
Outro fato curioso em relação à Inglaterra e que foi divulgado recentemente
relata que um computador chamado COLOSSUS entrou em operação secre-
tamente na Inglaterra em 1943. Este computador foi usado para auxiliar na
quebra dos códigos de criptografia alemães durante a segunda grande guerra.
1.2.4 As Gerações
Costumava-se dividir os projetos de computadores em gerações. Hoje em dia
como a taxa de evolução é muito grande não se usa mais este tipo de termino-
logia. No entanto é interessante mencionar estas divisões.
1.3 O Hardware
O hardware corresponde aos circuitos eletrônicos e componentes mecânicos que
compõem o computador. Um tı́pico diagrama em blocos de um computador
digital monoprocessado esta mostrado na Figura 1.7. A Unidade Central de
Processamento () , em inglês Central Processing Unit (CPU), como o próprio
nome diz, é a unidade onde os dados são processados, ou seja alterados, no
computador. Ou seja, dentro das UCPs os dados são somados, subtraı́dos etc.
A UCP também controla a movimentação dos dados dentro de todo o sistema.
Os módulos que constituem a UCP são os seguintes:
Unidade
Unidade de
de Entrada
Controle e
Saída
Unidade
Arimética Unidade
Central
de
Processamento
de montagem teria a impressão que cada carro leva muito pouco tempo para
ser montado. Isto porque no final da linha de montagem sai um carro a cada
poucos minutos. O mesmo acontece com a linha de montagem de instruções.
1.3.1 Microcomputadores
Uma UCP integrada em um único circuito (chip) é comumente chamada de
microprocessador . Os microprocessadores atuais incluem outros circuitos que
normalmente ficavam fora da UCP, tais como processadores de ponto flutuante
e memórias cache. Alguns exemplos de microprocessadores são mostrados na
Tabela 1.3
Microprocessador Empresa
Arquitetura Intel X86 Intel, AMD
PowerPC Consórcio Apple/IBM/Motorola
Power IBM
MIPS MIPS Technologies
ARM ARM Technologies
SPARC SUN
1.3.2 Memórias
Os dados no computador podem ser armazenados em diversos nı́veis de memória
semicondutoras ou em periféricos (discos, fitas, etc). Quanto mais rápida a
memória, usualmente mais cara ela é. A idéia por traz da separação em nı́veis
é colocar mais perto do processador, em memórias rápidas e mais caras, os
dados que o processador irá precisar mais freqüentemente. A medida que vamos
nos afastando do processador as memórias vão, ao mesmo tempo, ficando mais
baratas, aumentando de capacidade e diminuindo de velocidade. A Figura 1.8
ilustra esta hierarquia.
PROCESSADOR
MEMÓRIA
DISCO
CACHE
REGISTRADORES
BIT
BYTE
8 BITS
PALAVRA
32 BITS
4 BYTES
1.3.4 Periféricos
Como já mencionamos antes, os dados não ficam guardados somente na memória,
há também os periféricos . Há periféricos de entrada, outros de saı́da e alguns
que servem tanto para entrada como saı́da de dados. Periféricos não servem
somente para armazenar dados. Há periféricos que são usados para permitir a
interação entre os usuários e o computador. A tabela 1.5 ilustra alguns destes
periféricos.
1.4 O Software
Tudo isto que sobre o que acabamos de escrever constitui o hardware do com-
putador, o que se vê e o que se toca. A partir de agora falaremos brevemente
no software, o que não se vê nem se toca, mas também está lá.
Para que um computador execute alguma tarefa primeiro se desenvolve um
algoritmo , que é uma espécie de receita que “diz precisamente, ao computa-
dor”, como o problema deve ser resolvido. Esta definição informal de algoritmo
é enganosamente simples, e a chave para entender o engano está nas palavras
“dizer precisamente ao computador”. Por exemplo, uma receita em gastrono-
mia normalmente não é um algoritmo. Receitas são entendidas pela comunidade
de cozinheiros, que as seguem facilmente durante o preparo do prato. No en-
tanto, receitas estão cheias de expressões como, por exemplo, “mexer até ficar
no ponto” e “colocar sal a gosto”. Fora da comunidade de cozinheiros estas
expressões são passı́veis de várias interpretações. Para escrever algoritmos pre-
cisamos de uma linguagem matematicamente precisa e sem ambigüidades.
A escrita de um algoritmo consta de uma definição do estado inicial do
problema a ser resolvido e de regras precisas que estabelecem a cada instante
1.4. O SOFTWARE 33
Em seguida este algoritmo deve ser traduzido para uma linguagem que possa
ser entendida pelo computador ou que possa ser traduzida para esta linguagem.
No inı́cio da computação eletrônica com programas armazenados, estes eram
escritos diretamente em linguagem de máquina que é a linguagem que o com-
putador realmente “entende”. Estas instruções são conjuntos de bits indicando
a operação que deve ser executada e, caso necessário, onde como achar os os
dados que serão operados. Por esta razão também costuma-se dizer que são
programas escritos em binário.
Com a evolução da computação os programas passaram a ser escritos em
assembly , que é uma representação em mnemônicos das instruções de máquina.
Deste modo era é mais fácil escrever os algoritmos. Por exemplo, um fragmento
de um programa escrito em assembly do processador PowerPC é:
li r3,4 * O primeiro numero a ser somado e 4.
li r4,8 * 8 e o segundo numero
add r5,r4,r3 * Some os conteúdos de r3 (4) e r4 (8)
* e armazene o resultado em r5
Este pequeno trecho de programa armazena os números 4 e 5 em registrado-
res internos do processador em seguida os soma e armazena o resultado em um
terceiro registrador. As informações após os asteriscos são comentários usados
para explicar o que o programa está fazendo naquela instrução.
O PowerPC é um microprocessador criado em 1991 por um consórcio for-
mado pela IBM, Apple e Motorola Os microprocessadores PowerPC podem ser
usados para equipar desde sistemas embutidos até computadores de alto desem-
penho. A Apple usou este microprocessador para equipar suas máquinas até
2006.
Um programa escrito em assembly deve ser traduzido para a representação
binária, tarefa que normalmente se chama de montar o programa. A palavra
assembler frequentemente é usada erradamente para significar a linguagem e
não o programa que traduz o programa de assembly para linguagem binária de
máquina. Este tipo de programação pode levar a se escrever programas muito
eficientes, devido ao controle quase que total do programador sobre a máquina.
34 CAPÍTULO 1. INTRODUÇÃO
Início
Ligação
Criação de
Algoritmo Depuração e
Testes
Sim
Codificação do Erros de
Algoritmo Execução?
Não
Compilacação do
Programa Uso do programa
Sim Sim
Erros de Erros de
Compilação? Execução?
Não
Para resolver os exercı́cios deste livro você irá precisar de um compilador para a
linguagem C e de um editor de textos simples (não processador do como o Word).
O editor pode ser tão simples quanto o Notepad, na verdade recomendamos
1.5. UM PROGRAMA EM C 37
fortemente que o editor seja simples para que você possa ter contacto com
todas as etapas do processo de desenvolvimento de um programa. Para compilar
empregaremos o compilador gcc que é gratuito e pode ser obtido na Internet
como veremos adiante. Não será necessário nenhum ambiente mais complexo,
tal como um “Integrated Development Environment ” (IDE).
A coleção de compiladores da GNU (GNU Compiler Collection) usualmente
abreviada por gcc, é uma coleção de compiladores produzidos pelo projeto GNU.
A abreviação gcc, originalmente, significava GNU C Compiler. Este aplicativo
é distribuı́do gratuitamente pela Free Software Foundation (FSF) sob a licença
GNU GPL e GNU LGPL. Este é o compilador padrão para os sistemas ope-
racionais livres do tipo Unix, como o LINUX, e diversos sistemas operacionais
proprietários como o Apple Mac OS X. Atualmente o gcc pode compilar C++,
Objective-C, Java, Fortran e ADA, entre outras linguagens. Vamos conside-
rar, como exemplo, um programa chamado teste.c. Para compilar e gerar o
executável para este programa digitamos o comando
gcc -o teste teste.c -Wall
em uma janela de comandos no sistema Windows ou em um terminal nos siste-
mas Unix. O sufixo .c no nome do programa normalmente é usado para indicar
que o arquivo é de um programa C. Este comando deve ser digitado no diretório
onde está o arquivo fonte teste.c. O arquivo executável será armazenado no
mesmo diretório.
Nos sistemas Unix normalmente o gcc faz parte da distribuição padrão e
nada precisa ser feito. No Windows uma maneira fácil de obter uma versão
do gcc é instalar o MinGW (Minimalist GNU for Windows). MinGW é uma
coleção de arquivos e bibliotecas distribuı́das livremente as quais combinadas
com outras ferramentas da GNU permitem que programas para Windows sejam
produzidos sem a necessidade de bibliotecas extras e pagas. O MinGW dispõe
de um programa instalador que facilita enormemente o processo. Este programa
pode ser obtido no sı́tio oficial do MinGW. Caso após a instalação, o comando
indicado não funcione uma das razões para a falha pode ser que o sistema ope-
racional não sabe onde se encontra o compilador gcc. Suponha que o programa
gcc foi instalado no diretório C:\MinGW\bin. Uma solução é digitar o caminho
completo do compilador. Neste caso o comando se torna
C:\MinGW\bin\gcc -o teste teste.c -Wall
Para que não seja necessário digitar o caminho completo, é preciso adicio-
nar este caminho à variável PATH do Windows. Consulte o manual para obter
informações de como fazer este passo no seu sistema Windows.
1.5 Um programa em C
Vamos terminar este capı́tulo mostrando um exemplo simples de programa es-
crito em C(Listagem 1.1). A única coisa que este programa faz é imprimir Alo
Mundo! e terminar [1, 2, 3].
A primeira linha do programa avisa ao compilador que irá usar funções de
entrada e saı́da de dados guardadas na biblioteca stdio. Neste caso a função
usada é printf. A segunda linha é o inı́cio real do programa. A linha indica que
38 CAPÍTULO 1. INTRODUÇÃO
esta é a função main que todo programa C deve conter, pois é nesta função que
o programa obrigatoriamente começa sua execução. A função vai retornar um
valor inteiro (int) ao final de sua execução e não vai precisar receber nenhum
argumento para sua execução (void). As chaves ({ e }) marcam o inı́cio e o
fim da função. Para imprimir o texto Alo Mundo! o programa usa a função
printf. O inı́cio e o fim do texto a ser impresso são marcados pelo caractere ".
A função termina com o comando return 0, que avisa ao sistema operacional,
que foi quem iniciou a execução do programa, que o programa terminou sem
problemas. Este programa simples ilustra alguns das estruturas básicas que
serão usadas nos programas C que serão apresentados neste livro.
1.6 Exercı́cios
1.6: Se você já usa computadores, liste alguns aplicativos que você normalmente
usa.
1.9: Procure em manuais, internet e outras fontes quais são os tempos de acesso
das memórias RAMs atuais.
Algoritmos
2.1 Introdução
O objetivo deste capı́tulo é fazer uma breve introdução ao conceito de algorit-
mos e apresentar algumas formas mais comuns de representar algoritmos para
facilitar o entendimento dos demais capı́tulos deste livro. Iremos apresentar as
construções mais comuns empregadas no desenvolvimento de algoritmos e apre-
sentaremos exemplos básicos de algoritmos usando algumas destas formas de
representação e construções.
Para resolver um problema no computador é necessário que seja primeira-
mente encontrada uma maneira de descrever este problema de uma forma clara
e precisa. É preciso que encontremos uma seqüência de passos que permitam
que o problema possa ser resolvido de maneira automática e repetitiva. Além
disto é preciso definir como os dados que serão processados serão armazena-
dos no computador. Portanto, a solução de um problema por computador é
baseada em dois pontos: a seqüência de passos e a forma como os dados serão
armazenados no computador. Esta seqüência de passos é chamada de algoritmo.
Usamos algoritmos em diversas atividades que realizamos diariamente. Uma
grande parte destas atividades não estão relacionadas com computação. Um
exemplo simples e prosaico, de como um problema pode ser resolvido caso
forneçamos uma seqüência de passos que mostrem a maneira de obter a solução,
é uma receita para preparar um bolo.
Uma vez que foi criado um algoritmo para resolver um determinado pro-
blema usando computadores passamos para a próxima fase que é a escrita deste
algoritmo em alguma linguagem de programação.
A noção de algoritmo é central para toda a computação. A criação de algo-
ritmos para resolver os problemas é uma das maiores dificuldades dos iniciantes
em programação em computadores. Isto porque não existe um conjunto de re-
gras, ou seja um algoritmo, que nos permita criar algoritmos. Caso isto fosse
possı́vel a função de criador de algoritmos desapareceria. Claro que existem
linhas mestras e estruturas básicas, a partir das quais podemos criar algorit-
mos, mas a solução completa depende em grande parte do criador do algoritmo.
41
42 CAPÍTULO 2. ALGORITMOS
2.3 Representação
As formas mais comuns de representação de algoritmos são as seguintes:
Não existe consenso entre os especialistas sobre qual seria a melhor ma-
neira de representar um algoritmo. Atualmente a maneira mais comum de
representar-se algoritmos é através de uma pseudo-linguagem ou pseudo-código.
Esta forma de representação tem a vantagem de fazer com que o algoritmo seja
escrito de uma forma que está mais próxima de uma linguagem de programação
de computadores.
2.3.2 Fluxogramas
Esta forma de representação de algoritmos emprega várias formas geométricas
para descrever cada uma das possı́veis ações durante a execução do algorit-
mos. Existem algumas formas geométricas que usualmente são empregadas
2.3. REPRESENTAÇÃO 45
neste método. Estas formas estão mostradas na Figura 2.1. Cada uma destas
formas se aplica a uma determinada ação como está indicado na figura. Estas
formas são apenas alguns exemplos, existindo outras, no entanto, nesta apostila
estas serão suficientes para os exemplos que serão mostrados.
2.3.3 Pseudo-Linguagem
Este modo de representar algoritmos procura empregar uma linguagem que es-
teja o mais próximo possı́vel de uma linguagem de programação de computado-
res de alto nı́vel, mas evitando de definir regras de construção gramatical muito
rı́gidas. A idéia é usar as vantagens do emprego da linguagem natural, mas
restringindo o escopo da linguagem. Normalmente estas linguagens são versões
ultra reduzidas de linguagens de alto nı́vel do tipo Pascal ou C. O algoritmo 2.2
foi escrito em uma pseudo-linguagem. A maioria destas linguagens são muito
parecidas e têm a vantagem de serem facilmente entendidas. Vamos apresentar
agora um outro exemplo (Algoritmo 2.4) escrito em pseudo-linguagem. Este
46 CAPÍTULO 2. ALGORITMOS
Início
Obter a
Obter b
Sim
a =0
Não
Não há raízes
x=-b/a
reais
Imprime x
Fim
algoritmo serve para descobrir qual é a maior nota de um grupo de três notas
de um aluno. O algoritmo inicialmente lê a primeira nota e guarda esta nota
como a maior nota. Em seguida, lê cada uma das outras notas e compara com
a nota guardada como a maior nota. Caso a nota lida seja maior substitui o
valor anterior pelo novo valor.
1. Busca instrução;
2. Decodifica instrução;
48 CAPÍTULO 2. ALGORITMOS
3. Executa instrução;
0 1 2 3 4 5 6 7
2 8 10
16 17 18 19 20 21 22 23
24 25 26 27 28 29 30 31
imprimir x
imprime o valor atual que está na memória representada pelo nome x.
Da mesma forma que nos comandos de leitura é possı́vel colocar uma lista
de nomes de variáveis após o comando. Por exemplo, o comando
imprimir x1, x2
imprime os valores das variáveis x1 e x2. O meio de apresentação dos resultados,
normalmente, é um monitor de vı́deo.
O comando imprimir pode ser usado para mandar mensagens de texto
para o usuário do algoritmo das formas mais variadas. Alguns exemplos de
comandos de impressão são os seguintes:
Notar que os textos entre aspas indicam um texto que deve ser impresso no
periférico de saı́da sem nenhuma modificação. Vamos considerar que as variáveis
destes exemplos valem x = 10, x1 = 5 e x2 = 8. Os três comandos mostrariam
no periférico de saı́da os seguintes resultados:
2.5.3 Expressões
Expressões são usadas para definir os cálculos requeridos pelo algoritmo, por
exemplo −b/a. Iremos discutir dois tipos básicos de expressões: expressões arit-
méticas e expressões lógicas.
Expressões manipulam dados dentro dos algoritmos. Uma pergunta impor-
tante neste momento é: que tipo de dados poderemos manipular? As linguagens
de programação normalmente estabelecem regras precisas para definir que tipos
de dados elas irão manipular. Nesta discussão vamos estabelecer, ainda que
informalmente, algumas regras que limitam os conjuntos de dados existentes na
Matemática e estabelecem que dados poderão ser manipulados pelos algoritmos.
Isto ocorre porque os computadores possuem limitações que os impedem de ma-
nipular todos os tipos de dados que um ser humano pode tratar. Mais adiante,
quando formos estudar a linguagem C iremos apresentar mais formalmente as
regras desta linguagem para estas representações. Existem três tipos básicos de
dados que iremos discutir:
Dados numéricos: como o nome indica são os números que serão operados.
Dados alfa-numéricos: são os dados representados por caracteres. Como ca-
racteres podem ser letras, algarismos e sinais diversos estes dados recebem
este nome.
2.5. ESTRUTURAS BÁSICAS DE ALGORITMOS 51
Dados Lógicos: estes dados podem assumir dois valores verdadeiro e falso.
Estes dados resultam de expressões do tipo x > 0.
+3
3
-324
-50
+0.5
0.5
-8.175
2.0
‘‘Linguagem de programaç~
ao’’
‘‘Qual é o seu nome?’’
‘‘12345’’
se a = 0 então
imprimir ‘‘A equaç~
ao nao tem soluç~
ao’’
senão
x ← −b/a
imprimir ‘‘A raiz da equaç~
ao vale ’’, x
fim se
Expressões Aritméticas
Expressão Expressão em
Matemática Pseudo-linguagem
a
b+c a/(b+c)
a+b
c+d (a+b)/(c+d)
2
b − 4ac b*b-4*a*c
1
1
1+ a+b
1/(1 + 1/(a+b))
São usados para controlar o fluxo de execução das instruções. Nas linguagens de
programação existem diversos tipos de comandos de controle. Para estes exem-
plos iniciais vamos mostrar somente o comando mais básico que serve para o
computador escolher entre dois possı́veis caminhos qual o algoritmo deve seguir.
Este comando, representado em fluxograma, pode ser visto na Figura 2.4 e em
pseudo linguagem tem a forma mostrada no algoritmo 2.6.
Algoritmando
Falso
Condição
Verdadeiro
Continuo
algoritmando
Vestir para
ir ao cinema
Falso
Chovendo?
Verdadeiro
Pegar
guarda-chuva
Ir ao
cinema
Algoritmando
Falso
Testa
Condição
Verdadeiro
Bloco de comandos
do enquanto
Continuo
algoritmando
2.7 Exercı́cios
2.1: Uma empresa paga R$10.00 por hora normal trabalhada e R$ 15.00 por
hora extra. Escreva um algoritmo que leia o total de horas normais e o total
de horas extras trabalhadas por um empregado em um ano e calcule o salário
anual deste trabalhador.
2.2: Assuma que o trabalhador do exercı́cio anterior deve pagar 10% de imposto
se o seu salário anual for menor ou igual a R$ 12000.00. Caso o salário seja maior
que este valor o imposto devido é igual a 10% sobre R$ 12000.00 mais 25% sobre
o que passar de R$ 12000.00. Escreva um programa que calcule o imposto devido
pelo trabalhador.
2.3: Escreva um algoritmo que descubra a maior nota de uma turma de alunos.
O tamanho da turma deve ser o primeiro dado pedido ao usuário.
2.4: Modifique o algoritmo anterior de modo que ele imprima também quantas
vezes a maior nota aparece.
2.5: Nos exercı́cios anteriores assumimos que os usuários sempre digitam uma
nota entre 0 e 10. Vamos assumir agora que o usuário sempre digita um número,
mas este número pode estar fora do intervalo 0 a 10. Ou seja, poderemos ter
uma nota menor que zero ou maior que 10. Modifique o algoritmo anterior
para que ele verifique a nota digitada e, caso o aluno tenha digitado uma nota
inválida, uma mensagem avisando o usuário seja impressa e uma nova nota seja
pedida. O algoritmo deve insistir até que o usuário digite um valor válido.
2.7: Escreva um algoritmo que leia três números e os imprima em ordem cres-
cente.
2.8: Escreva um algoritmo que leia um número inteiro entre 100 e 999 e imprima
na saı́da cada um dos algarismos que compõem o número. Observe que o número
é lido com um valor inteiro, e, portanto, ele tem de ser decomposto em três
números: os algarismos das centenas, dezenas e unidades.
2.9: Escreva um algoritmo que leia uma hora em horas, minutos e segundos e
some um segundo a hora lida.
2.10: Escreva um algoritmo que leia duas datas em dia, més e ano e imprima
a data mais recente.
Capı́tulo 3
3.1 Introdução
Variáveis e constantes são os elementos básicos que um programa manipula.
Uma variável corresponde a um espaço reservado na memória do computador
para armazenar um determinado tipo de dado. Variáveis devem receber no-
mes para poderem ser mais facilmente referenciadas e modificadas sempre que
necessário. Muitas linguagens de programação exigem que os programas de-
clarem todas as variáveis antes que elas possam ser usadas. Estas declarações
especificam de que tipo são as variáveis usadas pelos programas e as vezes um
valor inicial. Tipos podem ser por exemplo: inteiros, reais, caracteres, etc. As
expressões combinam variáveis e constantes para calcular novos valores.
61
62 CAPÍTULO 3. TIPOS DE DADOS, CONSTANTES E VARIÁVEIS
void: Este tipo serve para indicar que um resultado não tem um tipo defi-
nido. Uma das aplicações deste tipo em C é criar um tipo vazio que pode
posteriormente ser modificado para um dos tipos anteriores.
unsigned: Este modificador pode ser aplicado aos tipos int e char e faz com
o bit de sinal não seja usado, ou seja o tipo passa a ter um bit a mais.
signed: Este modificado também pode ser aplicado aos tipos int e char. O
uso de signed com int é redundante.
long: Modificador que pode ser aplicado aos tipos int e double aumentando o
número de bytes reservado para armazenamento de dados.
dı́gito: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
dı́gito octal: 0, 1, 2, 3, 4, 5, 6, 7
dı́gito hexa: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, A, b, B, c, C, d, D, e,
E, f, F
3.3. CONSTANTES NUMÉRICAS 63
sinal: +, -
ponto decimal: .
sufixo: sufixo sem sinal, sufixo longo
sufixo sem sinal: u, U
sufixo longo: l, L
sufixo flutuante: f, F, l, L
O uso destas caracteres será mostrado com exemplos nas seções seguintes.
número inteiro pode ou não ter sinal, isto é o sinal é opcional. Em seguida temos
um dı́gito sem zero que é obrigatório. Isto é dados inteiros devem começar
por, pelo menos, um algarismo entre 1 e 9. A seguir temos a palavra dı́gito
entre chaves. As chaves indicam que o fator entre elas pode ser repetido zero ou
mais vezes. Portanto, um número inteiro, após o algarismo inicial obrigatório,
pode ser seguido por uma seqüência de zero ou mais algarismos. Como sufixo
podemos ter opcionalmente as letras u (U) ou l (L) ou uma mistura, em qualquer
ordem, das duas. Para constantes inteiras o sufixo U (u) representa o modificador
unsigned. O sufixo L (l) representa o modificador long. Um par de L’s (l’s)
indica que a constante é do tipo long long. A tabela 3.2 mostra exemplos de
números inteiros:
Tipo Constantes
int 1997 -3 +5 7
unsigned int 1997U 45u 12345U 0U
long int 1234L 1997L -3l +0L
unsigned long int 1997UL 45Lu 23Ul 0LU
long long 134LL 1997Ll -3ll +0LL
unsigned long long 1997ULL 45LLu 23Ull 0LLU
Na Tabela 3.3 mostramos exemplos de constantes octais e o seu valor na base 10.
Números escritos na base 8 somente podem ser escritos com algarismos entre 0
e 7 inclusive.
3.3. CONSTANTES NUMÉRICAS 65
Base 8 Base 10
025 21
077 63
011 9
010ul 8
0175 125
Base 16 Base 10
0xF 15
0X25 37
0XAB 171
0XBEEF 48879
Esta equação está escrita na base 10. Por exemplo, aplicando a equação 3.1
para converter o número 0175 da base 8 para a base 10 ficamos com
(0175)8 = 1 × 82 + 7 × 81 + 5 × 80 = (125)10
66 CAPÍTULO 3. TIPOS DE DADOS, CONSTANTES E VARIÁVEIS
Descrição Número
sinal fraç~ao expoente +23.45e-10
fraç~
ao 123.45
fraç~
ao expoente 123.45E+10
fraç~
ao sufixo 123.45F
seq dı́gitos ponto decimal 123.
Caractere Significado
’a’ caractere a
’A’ caractere A
’\0141’ Constante octal correspondente ao caractere ’a’
’(’ caractere abre parênteses
’9’ algarismo 9
’\n’ Nova linha, posiciona o cursor no inı́cio da nova linha.
Caractere Significado
’\n’ Passa para uma nova linha.
’\t’ Tabulação horizontal, move o cursor
para a próxima parada de tabulação.
’\b’ Retorna um caractere.
’\f’ Salta uma página.
’\r’ Carriage return, posiciona o cursor
no inı́cio da linha atual.
’\a’ Alerta, faz soar a campainha do sistema.
’\0’ Null, caractere que em C termina
uma cadeia de caracteres.
"Estas s~
ao \" (aspas) dentro de cadeias."
As aspas no meio da cadeia não indicam o fim, já que elas estão precedidas
do caractere de escape.
3.5 Variáveis
Variáveis são nomes dados para posições de memória a fim de facilitar o ma-
nuseio dos dados durante a criação dos programas. Os dados podem ser de
3.5. VARIÁVEIS 69
É boa polı́tica escolher nomes que indiquem a função da variável. Por exem-
plo:
soma total nome raio
mediaNotas salarioMensal taxa imposto inicio
Em C nomes como raio, Raio e RAIO referem-se a diferentes variáveis. No
entanto, para afastar confusões, evite diferenciar nomes de variáveis por le-
tras maiúsculas e minúsculas. Normalmente, os programadores usam letras
maiúsculas para representar constantes.
Observe que em alguns nomes combinamos duas palavras para melhor indi-
car o dado armazenado na variável. Note também que o caractere espaço não
pode ser usado em nomes de variáveis. Os programadores ao longo do tempo
desenvolveram algumas regras informais para fazer esta combinação. Por exem-
plo, usa-se o caractere ’ ’ para separar as palavras que compõem o nome, como
em taxa_imposto . Outra maneira é usar letras maiúsculas para indicar quando
começa uma palavra, como em mediaNotas. Alguns programadores usam a con-
venção de não começar nomes de variáveis por letras maiúsculas. Não existem
regras formais para definir como nomes devem ser criados. O melhor é analisar
as regras que programadores mais experientes usam ou os padrões que empresas
adotam, para então escolher o que mais lhe agrada e segui-lo. Uma vez adotado
um padrão ele deve ser seguido para evitar incoerências.
int i;
unsigned i n t a , b , c ;
unsigned short i n t dia , mes , ano ;
f l o a t raio , diametro ;
double salario ;
3.6 Exercı́cios
3.1: Indique os nomes de variáveis que são válidos. Justifique os nomes inválidos.
(a) tempo (e) 2dias
(b) nota final (f) teste 1
(c) us$ (g) raio.do.circulo
(d) char (h) DiaHoje
3.2: Indique quais dos números abaixo são constantes inteiras (longas ou não)
válidas. Justifique suas respostas.
(a) 100 (e) - 234
(b) 2 345 123 (f) 0L
(c) 3.0 (g) 21
(d) -35 (h) 0xF1
(a) 025
(b) 0123
(c) 0xD
(d) 0x1D
(a) Caso um bit seja reservado para o sinal diga qual é o menor número
inteiro negativo que este computador pode armazenar?
(b) Para os números sem sinal, qual é o maior número positivo?
72 CAPÍTULO 3. TIPOS DE DADOS, CONSTANTES E VARIÁVEIS
Capı́tulo 4
4.1 Introdução
Neste capı́tulo vamos apresentar conceitos básicos de entrada e saı́da de dados
para que os exemplos e exercı́cios iniciais possam ser construı́dos. Um programa
que não fornece resultados nem pede valores para operar não deve ter grande
utilidade. A entrada de dados será feita pelo teclado e a saı́da poderá ser vista
na tela do computador. Em C, quando um programa se inicia, normalmente
três fluxos (arquivos) de dados são abertos para operações de entrada e saı́da:
um para entrada, um para saı́da e um para imprimir mensagens de erro ou di-
agnóstico. Normalmente o fluxo de entrada está conectado ao teclado, enquanto
que o fluxo de saı́da e o de mensagens de erro, para serem visualizados, estão co-
nectados ao monitor. Estas configurações podem ser alteradas de acordo com as
necessidades dos usuários e estas operações são chamadas de redirecionamento.
O fluxo de entrada é chamado de entrada padrão (standard input); o fluxo de
saı́da é chamado de saı́da padrão (standard output) e o fluxo de erros é chamado
de saı́da padrão de erros (standard error output). Estes termos são substituı́dos
pelas suas formas abreviadas: stdin, stdout e stderr.
73
74 CAPÍTULO 4. ENTRADA E SAÍDA PELO CONSOLE
Código Comentário
%c Caracter simples
%d Inteiro decimal com sinal
%i Inteiro decimal com sinal
%E Real em notação cientı́fica com E
%e Real em notação cientı́fica com e
%f Real em ponto flutuante
%G %E ou %f, o que for mais curto
%g %g ou %f, o que for mais curto
%o Inteiro em base octal
%s Cadeia Caracteres
%u Inteiro decimal sem sinal
%x Inteiro em base hexadecimal (letras minúsculas)
%X Inteiro em base hexadecimal (letras maiúsculas)
%p Endereço de memória
%% Imprime o caractere %
%[modificadores][largura][.precis~
ao][comprimento]código
largura: Caso seja usado um número inteiro, este especifica o tamanho mı́nimo
do campo onde o argumento será impresso. Na listagem 4.2 o número
especifica que 8 espaços são reservados para imprimir o resultado. Os
espaços livres serão completados com espaços em branco. Se o argumento
precisar de mais espaço que o especificado ele será escrito normalmente e
o tamanho mı́nimo é ignorado.
return 0;
}
O resultado e = 0.333
. Alo
4.4. ENTRADA - A FUNÇÃO SCANF 77
return 0;
Nos exemplos anteriores verifique que ’\n’ não é impresso. A barra inclinada
é chamada de seqüencia de escape, indicando que o próximo caractere não é para
ser impresso mas representa caracteres invisı́veis ou caracteres que não estão
representados no teclado. Esta seqüência de escape indica que o programa deve
passar a imprimir na próxima linha.
return 0;
}
Considere que este programa se chama util. Uma possı́vel interação entre
este programa e um usuário poderia ser da seguinte maneira.
$ util
Por favor, qual o seu nome?
Ze Sa
Sou um computador. Posso ajuda-lo Ze?
Diferentemente do comando scanf a função gets lê toda a cadeia até que a tecla
<enter> seja digitada. No vetor são colocados todos os códigos dos caracteres
lidos excetuando-se o da tecla <enter>, que não é armazenado sendo substituı́do
pelo código NULL. Caso a função scanf do exemplo anterior fosse substituı́da
pela gets o programa imprimiria
Posso ajuda-lo Ze Sa?
O comando que substitui o scanf é gets(nome). O protótipo da função gets
é o seguinte:
#include < stdio .h >
char * gets ( char * str );
A função gets retorna str caso nenhum erro ocorra. Caso o final do ar-
quivo seja encontrado antes de qualquer caractere ser lido, o vetor permanece
inalterado e um ponteiro nulo é retornado. Caso um erro ocorra durante a lei-
tura, o conteúdo do array fica indeterminado e novamente um ponteiro nulo é
retornado.
A função puts tem o seguinte protótipo:
#include < stdio .h >
i n t puts ( const char * str );
A função gets pode abrir porta para invasões de computadores pelo fato dela
não controlar o número de caracteres lido de stdin. Apesar do usuário definir
um tamanho máximo para o vetor que irá armazenar os caracteres a função
ignora o limite e continua lendo valores até que o usuário digite o caractere
<enter>.
Para evitar este problema recomenda-se o emprego da função fgets cujo
protótipo é
#include < stdio .h >
i n t * fgets ( const char * str , i n t tam , FILE * fluxo );
4.5. LENDO E IMPRIMINDO CARACTERES 83
4.6 Exercı́cios
4.1: Escreva um programa que declare variáveis do tipo int, char e float,
inicialize-as, e imprima os seus valores.
4.2: Escreva um programa que defina variáveis do tipo int e armazene nelas
constantes octais e hexadecimais e imprima o seu conteúdo no formato original
e em formato decimal.
4.3: Faça um programa que leia um valor inteiro no formato decimal e escreva,
na tela, este mesmo valor nas bases hexadecimal e octal.
Exemplo de Entrada e Saı́da:
Entre com o valor:
10
Hexadecimal: A
Octal: 12
4.4: Faça um programa capaz de ler um valor real e escrevê-lo com apenas uma
casa decimal.
4.5: Faça um programa que leia três palavras de até 10 letras e reescreva estas
palavras alinhadas à direita da tela.
4.6: Sabendo que os argumentos da função printf podem ser expressões (a+b,
a/b, a*b, 3*a...), e não somente argumentos, faça um programa capaz de ler
um valor inteiro e escrever seu triplo, seu quadrado, e a sua metade.
Exemplo de Entrada e Saı́da:
Valor:
6
Triplo: 18
Quadrado: 36
Meio: 3
4.7: Escreva um programa que leia 3 números reais e imprima a média aritmética
destes números.
4.8: Escreva um programa que pegue o valor de uma conta de restaurante e
imprima o valor total a ser pago, considerando que o restaurante cobra 10% de
taxa para os atendentes.
4.9: Faça um programa que peça ao usuário a quilometragem atual, a quilome-
tragem anterior, os litros consumidos e informe a taxa de consumo (quilômetros
por litro) de um automóvel.
4.10: Escreva um programa que converta uma temperatura de Farenheit para
Celsius.
4.11: Escreva um programa que, dado o perı́metro de um cı́rculo, calcule sua
área.
4.12: Faça um programa que utilize a função gets para ler duas cadeias de
tamanho até 20 e em seguia às reescreva na linha de baixo, uma ao lado da
outra e separadas por ”/-/ ”;
Capı́tulo 5
Operadores e Expressões
5.1 Introdução
O objetivo deste capı́tulo é apresentar os operadores existentes na linguagem
C e a forma correta de construir expressões que envolvam estes operadores,
constantes e variáveis.
85
86 CAPÍTULO 5. OPERADORES E EXPRESSÕES
Os sı́mbolos mostrados na Tabela 5.3 são os únicos que podem ser usados
para representar as operações acima listadas. Expressões aritméticas em C de-
vem ser escritas no formato linear para facilitar a digitação dos programas e
também porque alguns sı́mbolos usados em Matemática não existem nos tecla-
dos. O exemplo mais comum deste formato é a operação de divisão que deve
ser escrita a/b.
Parênteses têm um papel importante nas expressões e permitem que a ordem
das operações seja alterada. Expressões entre parênteses são calculadas em
primeiro lugar, portanto eles conferem o maior grau de prioridade as expressões
que eles envolvem. Podemos ter pares de parênteses envolvendo outros pares.
Dizemos que os parênteses estão aninhados. Neste caso as expressões dentro dos
parênteses mais internos são avaliadas primeiro.
Outro ponto importante são as regras de precedência que determinam que
operação deve ser executada primeiro. Na tabela os operadores estão listados
em ordem decrescente de prioridade. Para os operadores aritméticos a operação
de mais alta precedência é o - unário, vindo em seguida ++, -- com a mesma
prioridade. Os operadores de multiplicação, divisão e módulo tem a mesma
prioridade. O operador menos unário multiplica seu operador por -1. Quando
duas operações de mesmo nı́vel de prioridade têm de ser avaliadas, a operação
mais à esquerda será avaliada primeiro.
Um ponto importante que deve ser sempre levado em consideração quando
uma expressão for calculada são os tipos das variáveis, porque eles alteram
radicalmente os resultados das expressões. Por exemplo, a divisão entre ope-
randos do tipo inteiro tem como resultado um valor inteiro. Portanto, se o
resultado possuir uma parte fracionária ela será truncada. Não é possı́vel apli-
car a operação de módulo a operandos do tipo float e double. Algumas regras
de conversão simples existem e serão discutidas em detalhes mais adiante. Por
exemplo a operação 1/3 em C fornece como resultado o valor 0, enquanto que 1
% 3 é igual a 3.
A seguir mostramos alguns exemplos de expressões aritméticas escritas na
5.4. OPERADORES RELACIONAIS E LÓGICOS 87
b
1. a + b+c =⇒ a + b/(b+c)
2. b2 + c2 =⇒ b*b + c*c
x
3. a+ bc
=⇒ x/(a+b/c)
Os operadores >, >=, < e <= têm a mesma precedência e estão acima de ==
e !=. Estes operadores têm precedência menor que os aritméticos, portanto
expressões como ( i < limite - 1) e i < (limite -1) têm o mesmo signi-
ficado.
E lógico
p q p && q
0 0 0
0 1 0
1 0 0
1 1 1
OU lógico
p q p || q
0 0 0
0 1 1
1 0 1
1 1 1
Não lógico
p !p
0 1
1 0
Operador Prioridade
! 0
>, >=, <, <= 1
==, != 2
&& 3
|| 4
p q p ^ q
0 0 0
0 1 1
1 0 1
1 1 0
Observações:
return 0;
}
14 = 0000000E
7 = 00000007
-14 = FFFFFFF2
-7 = FFFFFFF9
Os resultados mostram que o número 7 após o primeiro deslocamento de 1
bit para a esquerda ficou igual a 14, portanto um 0 entrou no número. Quando
o número foi deslocado para direita 1 bit, ele retornou ao valor original. Observe
que quando o número -14 foi deslocado para a direita entrou um bit 1, que é
igual ao sinal negativo.
5.11 Exercı́cios
1. (a/b)*(c/d)
2. (a/b*c/d)
3. (a/(b*c)/d)
4. a*x*x+b*x+c
1. b2 − 4 · b · c
1
2. 1+ 1
1+ 1
1+x
a+b
3. c+d
x
4. a × c+d
1. x = 5 * 4 / 6 + 7;
2. x = 5 * 4.0 / 6 + 7;
3. x = 5 * 4 % 6 + 7;
4. x = ((4 / 2) + (3.0 * 5));
Comandos de Controle
6.1 Introdução
Este capı́tulo tem por objetivo apresentar os comandos de controle da linguagem
C. Estes comandos servem para controlar o fluxo de execução das instruções
de um programa. Estes comandos permitem que o computador tome decisões
independentemente do usuário que está rodando o programa.
97
98 CAPÍTULO 6. COMANDOS DE CONTROLE
6.3.1 Comando if
O comando if é utilizado quando for necessário escolher entre dois caminhos.
A forma geral do comando if é a seguinte:
i f ( express~
ao )
bloco_de_c o ma n do s 1 ;
else
bloco_de_c o ma n do s 2 ;
Uma construção que pode aparecer são os comandos if’s em escada, cuja
forma geral é a seguinte:
6.3. COMANDOS DE TESTE 99
i f ( express~ao )
bloco_de_c om a nd os
else i f ( express~ a o1 )
bloco_de_co m an d os 1
else i f ( express~ a o2 )
bloco_de_c om a nd o s2
...
e l s e bloco_de_c om a nd o sn
default:
seqü^
e ncia _d e _ co m a nd o s ;
}
1. A expressão é avaliada;
2. O resultado da expressão é comparado com os valores das constantes que
aparecem nos comandos case;
3. Quando o resultado da expressão for igual a uma das constantes, a e-
xecução se inicia a partir do comando associado com esta constante. A
execução continua até o fim do comando switch, ou até que um comando
break seja encontrado;
4. Caso não ocorra nenhuma coincidência os comandos associados ao co-
mando default são executados. O comando default é opcional, e se ele
não aparecer nenhum comando será executado.
1. A express~
ao1 é utilizada para inicializar a variável de controle do laço;
2. A express~
ao2 é um teste que controla o fim do laço;
104 CAPÍTULO 6. COMANDOS DE CONTROLE
i n t main ()
{
i n t numero , fat =1 , i ;
Listing 6.6: Exemplo de comando for com testes sobre outras variáveis.
#include < stdio .h >
i n t main ()
{
char c = ’ ’;
int i;
f o r ( i =0 ; (i <5) && ( c != ’* ’ ); i ++ )
{
printf ( " % c \ n " , c );
c = getchar ();
}
return 0;
}
Um outro ponto importante do for é que nem todas as expressões precisam estar
presentes. No exemplo 6.7 a variável de controle não é incrementada. A única
maneira do programa terminar é o usuário bater o número -1.
Laço infinito
Uma importante construção aparece quando colocamos como comando a ser re-
petido um outro comando for. Esta construção pode aparecer quando estamos
trabalhando com matrizes. O exemplo 6.9 mostra um programa que imprime
uma tabuada.
1. A expressão é avaliada;
Uma caracterı́stica do comando while, como pode ser visto dos passos acima,
é que o bloco de comandos pode não ser executado caso a condição seja igual a
falso logo no primeiro teste.
O trecho de abaixo imprime os 100 primeiros números usando um comando
while.
i = 1;
while ( i <= 100)
{
printf ( " Numero % d \ n " , i );
i ++;
}
1. Executa o comando;
108 CAPÍTULO 6. COMANDOS DE CONTROLE
2. Avalia a expressão;
6.6 Exercı́cios
6.5: Utilizando um laço for dentro de outro, escreva um programa que exiba
as tabuadas de multiplicação dos números de 1 à 9.
6.13: Escreva um programa que leia um numero do teclado e ache todos os seus
divisores.
Não use nenhuma constante, use apenas variáveis. Em outra linha imprima
as letras maiúsculas de A até Z (ABCD...).
0 (1*0*0)
0 (1*0*1)
0 (1*0*2)
(...)
0 (1*1*0)
1 (1*1*1)
2 (1*1*2)
···
9*9*9=729
Faça seu programa dar uma pausa a cada 20 linhas para que seja possı́vel
ver todos os números pouco a pouco. Solicite que seja pressionada alguma tecla
para ver a próxima seqüência de números.
Capı́tulo 7
Vetores e Cadeias de
Caracteres
7.1 Introdução
Vetores são usados para tratamento de conjuntos de dados que possuem as
mesmas caracterı́sticas. Uma das vantagens de usar vetores é que o conjunto
recebe um nome comum e elementos deste conjunto são referenciados através de
ı́ndices. Pelo nome vetor estaremos referenciando estruturas que podem ter mais
de uma dimensão, como por exemplo matrizes de duas dimensões. Neste capı́tulo
estaremos mostrando vetores de tamanhos fixos. Somente após apresentarmos
ponteiros iremos abordar alocação de memória para vetores.
113
114 CAPÍTULO 7. VETORES E CADEIAS DE CARACTERES
que os ı́ndices dos vetores estejam sempre dentro dos limites estabelecidos pela
declaração do vetor.
/* Geracao do conjunto */
f o r ( i = 0 ; i < DIM ; i ++) vetor [ i ] = num ++;
/* Impressao do conjunto */
f o r ( i = 0; i < DIM ; i ++)
printf ( " Elemento % d = % d \ n " , i , vetor [ i ]);
return 0;
}
i n t main ( void )
{
i n t vetor1 [ DIM ] , vetor2 [ DIM ] , i , prod =0;
return 0;
}
116 CAPÍTULO 7. VETORES E CADEIAS DE CARACTERES
do {
trocou = FALSO ;
f o r ( i =0; i < fim -1; i ++)
{
i f ( vetor [ i ] > vetor [ i +1])
{
temp = vetor [ i ];
vetor [ i ] = vetor [ i +1];
vetor [ i +1] = temp ;
trocou = VERDADE ;
}
}
fim - -;
} while ( trocou );
return 0;
}
118 CAPÍTULO 7. VETORES E CADEIAS DE CARACTERES
• char *strncat (char *dest, const char *orig, size_t n): Concatena ca-
deia orig ao final de dest, usando no máximo n caracteres de orig. O
primeiro caractere de orig substitui o caractere nulo de dest. A função
retorna o valor de dest.
• char *strcmp (const char *cad1, const char *cad2): Compara lexico-
graficamente as duas cadeias. Retorna zero se as cadeias são iguais, menor
que 0 se cad1 < cad2, maior que 0 se cad1 > cad2.
• char *strncmp (const char *cad1, const char *cad2, size_t n): Com-
para lexicograficamente até n caracteres das duas cadeias. Retorna zero
se as cadeias são iguais, menor que 0 se cad1 < cad2, maior que 0 se
cad1 > cad2.
• char *strcpy(char *dest, const char *orig): Copia cadeia orig para
dest. A cadeia destino deve ter espaço suficiente para armazenar orig.
O valor de dest é retornado.
strcat, e esta é razão das aspas, ou seja, uma cadeia de um caractere apenas.
A seguir mostramos um resultado da execução do programa 7.4.
Ze Sa
Qual caracter? a
O caractere aparece na posicao 4
i n t main ( void )
{
char c , nome [81] , sobrenome [41];
int i;
1000 m[0][0]
1004 m[0][1]
1008 m[0][2]
1012 m[1][0]
1016 m[1][1]
1020 m[1][2]
1024 m[2][0]
1028 m[2][1]
1032 m[2][2]
X
C1
M Rij = M 1ik × M 2kj (7.1)
k=1
#define L1 3
#define L2 3
#define C1 3
#define C2 3
i n t main ( void )
{
f l o a t m1 [ L1 ][ C1 ] , m2 [ L2 ][ C2 ];
f l o a t mr [ L1 ][ C2 ] , m ;
int i, j, k;
f o r ( i =0; i < L1 ; i ++ )
{
f o r ( j =0; j < C2 ; j ++)
{
printf ( " %.3 f " , mr [ i ][ j ]);
}
printf ( " \ n " );
}
return 0;
}
7.6. INICIALIZAÇÃO DE VETORES E MATRIZES 123
return 0;
}
i n t main ()
{
i n t vetor [ DIM ] = {10 , 15 , 20 , 25 , 30};
i n t vetor1 [] = {10 , 20 , 30 , 40 , 50 , 60 , -1};
i n t vetor2 [] = {3 , 6 , 9 , 12 , 15 , 18 , 21 , 24};
unsigned i n t i , tam ;
tam = s i z e o f ( vetor2 ) / s i z e o f ( i n t );
printf ( " \ nDescobrindo o tamanho do Vetor \ n " );
f o r ( i =0; i < tam ; i ++)
printf ( " Elemento % d = % d \ n " , i , vetor2 [ i ]);
return 0;
}
7.6. INICIALIZAÇÃO DE VETORES E MATRIZES 125
i n t main ( void )
{
char disciplinas [][40] = {
" disc 0: Computacao para Informatica " ,
" disc 1: Banco de Dados I " ,
" disc 2: Banco de Dados II " ,
" disc 3: Arquitetura de Computadores I "
};
int i;
return 0;
}
126 CAPÍTULO 7. VETORES E CADEIAS DE CARACTERES
7.7 Exercı́cios
7.1: Escreva um programa que leia uma linha de até 80 caracteres do teclado e
imprima quantos caracteres foram lidos.
7.2: Escreva um programa que leia uma linha de caracteres do teclado e imprima
quantas vezes um caractere, também fornecido pelo teclado, aparece nesta linha.
O programa também deve imprimir em que posições o caractere foi encontrado.
7.3: Escreva um programa que leia uma linha do teclado e em seguida um par
de caracteres. O programa deve procurar este par na linha e imprimir em que
posições o par foi encontrado. Obs. Não use funções da biblioteca de strings
do C
7.4: Escreva um programa que leia uma linha do teclado e imprima todas as
vogais encontradas no texto e o total de vezes que elas aparecem.
Obs: Tamanho máximo da linha deve ser 40 caracteres.
7.5: O imperador romano César usava um sistema simples para codificar as
mensagens que enviava aos seus generais. Neste sistema cada letra era subs-
tituı́da por três letras à frente no alfabeto. A sua missão é mais simples ainda,
escrever um programa que converta cada letra, e somente as letras, de uma
mensagem de até 80 caracteres para a letra imediatamente posterior. Note que
a letra ’z’ deve ser convertida para a letra ’a’, e a letra ’Z’ para ’A’.
7.6: Escreva um programa que leia uma frase de 80 caracteres e a imprime
retirando os espaços em branco.
7.7: Escreva um programa que leia uma linha de caracteres do teclado de
tamanho 80. A linha somente contém letras. Divida a linha em blocos de 5
letras. Dentro de cada bloco o seu programa deve trocar a primeira letra pela
letra seguinte no alfabeto, a segunda letra por duas letras adiante no alfabeto,
a terceira por três letras adiante e assim até a quinta. Os espaços em branco
devem ser retirados da frase. Considere o seguinte exemplo.
1. Frase lida:
EVA VIU A UVA
2. Retirada dos espaços em branco:
EVAVIUAUVA
3. Divisão em blocos de 5 (blocos indicados por tipos diferentes):
EVAVIUAUVA
4. Criptografia:
FYDANVCYAF
7.9: Escreva um programa que leia uma linha de caracteres do teclado e converta
o primeiro caractere de cada palavra para maiúsculas. Assuma que as palavras
são sempre separadas por um branco.
7.12: Escreva um programa que leia um conjunto de nomes para uma matriz
e imprima estes nomes em ordem alfabética. Assuma que os nomes serão lidos
somente em letras maiúsculas. Assuma também que os nomes têm no máximo
40 caracteres e serão lidos 10 nomes ao todo.
128 CAPÍTULO 7. VETORES E CADEIAS DE CARACTERES
Capı́tulo 8
Funções
8.1 Introdução
Em C, diferentemente de outras linguagens como Pascal, todas as ações ocorrem
dentro de funções. Na linguagem C não há conceito de um programa principal, o
que existe é uma função chamada main que é sempre a primeira a ser executada.
Um programa pode ser escrito apenas com a função main e mais as funções
existentes nas bibliotecas da linguagem C. No entanto o uso de funções pode
facilitar o desenvolvimento de programas de diversas maneiras.
Em primeiro lugar temos as vantagens do reuso de código desenvolvido por
outros programadores. As funções de entrada e saı́da são o exemplo mais direto
deste reuso. Em C não existem estes tipos de comandos como na maioria das
linguagens. Programas escritos em C usam funções de entrada e saı́da escritas
e testadas por outros programadores. Este reuso de código apresenta várias
vantagens. Primeiro, diminui o tempo de desenvolvimento do programas. Em
segundo lugar, como estas funções foram testadas por diversos usuários, a quan-
tidade de erros é bastante reduzida. Estes fatores contribuem para a redução
dos custos de desenvolvimento dos projetos.
Uma outra vantagem do uso de funções e a maior facilidade na divisão do
trabalho necessário para construir um aplicativo. Funções podem ser desenvol-
vidas por programadores trabalhando independentemente. Para isto basta que
alguns acordos sejam feitos entre os programadores que irão programar a função
e os que irão usá-las. Estes acordos precisam definir que parâmetros a função
irá receber, que resultados irá fornecer e que operações ela deve realizar sobre
estes parâmetros para obter os resultados necessários. Esta divisão do trabalho
concorre para acelerar o desenvolvimento dos programas e na redução dos custos
deste desenvolvimento.
A divisão de um programa em funções também permite que os testes do
sistema completo sejam feitos mais facilmente e com mais garantia de correção.
Os programadores podem testar suas funções separadamente em testes menos
complexos, já que as funções normalmente são simples e têm requisitos menos
complicados de serem avaliados. Isto permite que muitos erros do sistema com-
pleto possam ser retirados antes que ele esteja completo. Normalmente testar
um programa complexo requer testes complexos.
129
130 CAPÍTULO 8. FUNÇÕES
Uma função recebe uma lista de argumentos (nome1, nome2, ..., nomeN),
executa comandos com estes argumentos e pode retornar ou não um resultado
para a função que chamou esta função. A lista de argumentos, também cha-
mados de parâmetros, é uma lista, separada por vı́rgulas, de variáveis com seus
tipos associados. Não é possı́vel usar uma única definição de tipo para várias
variáveis. A lista de argumentos pode ser vazia, ou seja, a função não recebe
nenhum argumento. O nome da função pode ser qualquer identificador válido.
O tipo que aparece antes do nome da função especifica o tipo do resultado que
será devolvido ao final da execução da função. Caso nenhum tipo seja especifi-
cado o compilador assume que um tipo inteiro é retornado. O tipo void pode
ser usado para declarar funções que não retornam valor algum.
Há basicamente duas maneiras de terminar a execução de uma função. Nor-
malmente usa-se o comando return para retornar o resultado da função. Por-
tanto, quando o comando
return express~
ao;
for executado, o valor da express~ ao é devolvido para a função que chamou.
Quando não há valor para retornar o comando return não precisa ser usado e
a função termina quando a chave que indica o término do corpo da função é
atingido.
Os parâmetros são valores que a função recebe para realizar as tarefas para
as quais foi programada. Por exemplo, uma função que calcule a raiz quadrada
de um número do tipo float , deve declarar como parâmetro uma variável deste
tipo para receber o valor.
É importante notar que diferentemente de declarações de variáveis onde
podemos associar vários nomes de variáveis a uma declaração como em
int a, dia, mes, i;
na lista de parâmetros é necessário associar um tipo a cada variável como
no exemplo a seguir:
float media (float n1, float n2, float n3);
Neste exemplo, uma função chamada media que é do tipo float , isto é retorna
um resultado float , recebe três argumentos (n1, n2, n3) também do tipo float .
8.3. PROTÓTIPOS DE FUNÇÕES 131
• dentro de funções,
• fora de todas as funções,
132 CAPÍTULO 8. FUNÇÕES
/* Prototipo da funcao */
i n t soma ( i n t a , i n t b );
/* Funcao Principal */
i n t main ()
{
i n t a =5 , b =9;
printf ( " % d \ n " , soma (a , b ));
return 0;
}
/* Definicao da funcao */
i n t soma ( i n t a , i n t b )
{
return a + b ;
}
return 0;
}
134 CAPÍTULO 8. FUNÇÕES
As variáveis globais são definidas fora de qualquer função e são portanto dispo-
nı́veis para qualquer função. Este tipo de variável pode servir como uma canal
de comunicação entre funções, uma maneira de transferir valores entre elas. Por
exemplo, se duas funções tem de partilhar dados, mais uma não chama a outra,
uma variável global tem de ser usada.
O programa 8.4 ilustra este tipo de declaração. O resultado da execução
deste programa é o seguinte:
Funcao soma1: i = 1
Funcao sub1: i = 9
Funcao main: i = 1
Observe que a variável global i recebe o valor 0 no inı́cio da função main. A
função soma1 ao executar um comando que aumenta o valor de i em uma unidade
está aumentando a variável global. Em seguida vemos que a função sub1 define
uma variável local também chamada i e, portanto, a alteração feita por esta
função somente modifica esta variável. Finalmente, a função main imprime o
valor final da variável global.
8.6. PARÂMETROS FORMAIS 135
int i;
f l o a t Eleva ( f l o a t a , i n t b )
{
f l o a t res = 1.0;
f o r ( ; b >0; b - -) res *= a ;
return res ;
}
i n t main ()
{
f l o a t numero ;
i n t potencia ;
char linha [80];
return 0;
}
return 0;
}
i n t main ()
{
char c , linha [ DIM ];
i n t maiusculas [26] , minusculas [26];
while ( v [ i ] != ’ \0 ’)
i f ( v [ i ++] == c ) vezes ++;
return vezes ;
}
8.7. O COMANDO RETURN 139
i n t main ()
{
i n t v [ DIM ];
Le_vetor (v , DIM );
Imprime_vetor (v , DIM );
Inverte_vetor (v , DIM );
Imprime_vetor (v , DIM );
return 0;
}
8.8 Recursão
Funções em C podem ser usadas recursivamente, isto é uma função pode chamar
a si mesmo. É como se procurássemos no dicionário a definição da palavra
recursão e encontrássemos o seguinte texto:
recursão: s.f. Veja a definição em recursão
Um exemplo simples de função que pode ser escrita com chamadas recursivas
é o fatorial de um número inteiro. O fatorial de um número, sem recursão, é
definido como
n! = n ∗ (n − 1) ∗ (n − 2) ∗ · · · ∗ 2 ∗ 1
return fato ;
}
n! = n ∗ (n − 1)!
Deste modo podemos escrever uma função recursiva em que cada chamada
da função que calcula o fatorial chama a própria função fatorial. O exemplo,
mostrado a seguir, mostra como a função pode ser escrita recursivamente.
unsigned long i n t fat ( unsigned long i n t num )
{
i f ( num == 0)
return 1;
lese
return num * fat ( num -1);
}
vale 0. Se este teste não tivesse sido incluı́do na função as chamadas continu-
ariam indefinidamente com valores negativos cada vez menores sendo passados
como parâmetro.
Quando uma função chama a si mesmo recursivamente ela recebe um con-
junto novo de variáveis na pilha que é usada para transferência de valores entre
funções. É importante notar que recursão não traz obrigatoriamente economia
de memória porque os valores sendo processados tem de ser mantidos em pilhas.
Nem será mais rápido, e as vezes pode ser até mais lento porque temos o custo
de chamada as funções. As principais vantagens da recursão são códigos mais
compactos e provavelmente mais fáceis de serem lidos.
Um outro exemplo simples de função que pode ser resolvida por recursão
é xn , assumindo que n ≥ 0. Esta função pode escrita na sua forma recursiva
como
xn = x ∗ x(n−1)
que nos leva a escrever a função da maneira mostrada no exemplo 8.9. Na
função consideramos que x é do tipo float .
i f ( argc < 2)
{
printf ( " Para rodar : % s num1 num2 ... .\ n " , argv [0]);
return 1;
}
f o r ( i =1; i < argc ; i ++)
{
numero = ( unsigned long i n t ) ( atoi ( argv [ i ]));
fatorial = fat ( numero );
printf ( " O fatorial de % lu vale % lu .\ n " , numero , fatorial );
}
return 0;
}
Os nomes argc e arga são comumente usados mas o programador é livre para
escolher os nomes mais apropriados.
8.10. EXERCÍCIOS 143
8.10 Exercı́cios
Ponteiros
9.1 Introdução
Ponteiros são usados em situações em que é necessário conhecer o endereço
onde está armazenada a variável e não o seu conteúdo. Um ponteiro é uma
variável que contém um endereço de uma posição de memória e não o conteúdo
da posição. A memória de um computador pode ser vista como uma seqüência
de bytes cada um com seu próprio e único endereço. Não há dois bytes com
o mesmo endereço. O primeiro endereço é sempre 0 e o último geralmente é
uma potência de 2. Por exemplo um computador com memória igual a 512
Mbytes tem 512x1024x1024 bytes. A Figura 9.1 mostra o mapa de um trecho
de memória que contém duas variáveis inteiras (num, res) ocupando 4 bytes
cada uma e mais um ponteiro (pint), que também ocupa 4 bytes. Observar que
os endereços estão pulando de quatro em quatro bytes devido ao espaço que
cada um destas variáveis ocupa.
0
4 10 num
8 120 res
12 *pint
16
Ponteiros são importantes, por exemplo, quando se deseja que uma função
retorne mais de um valor. Neste caso uma solução é a função receber como
argumentos não os valores dos parâmetros mas sim ponteiros que apontem para
seus endereços. Assim esta função pode modificar diretamente os conteúdos
145
146 CAPÍTULO 9. PONTEIROS
destas variáveis, que após o fim da função estarão disponı́veis para a função que
chamou. Neste caso os argumentos podem funcionar como entrada e saı́da de
dados da função.
Uma outra aplicação importante de ponteiros é apontar para áreas de me-
mória que devem ser gerenciadas durante a execução do programa. Com pontei-
ros, é possı́vel reservar as posições de memória necessárias para armazenamento
destas áreas somente quando for necessário e não quando as variáveis são decla-
radas. Neste esquema o programador pode reservar o número exato de posições
que o programa requer. A Figura 9.2 ilustra como um ponteiro faz referência
para uma área de memória. Na figura a variável ponteiro pi aponta para a área
de memória que contém um vetor de 10 inteiros. Com ponteiros, o programador
precisa, no inı́cio, definir a variável ponteiro e seu tipo. Durante a execução do
programa, após descobrir o tamanho do vetor, reserva a área necessária para
guardar os dados. Observe a diferença do que ocorre quando se usa vetores de
tamanho fixo. Neste caso a definição do tamanho do vetor é dada na declaração
do vetor e é mantida até o final da execução do programa.
0
1000 pi ponteiro
4 para vetor
8
1000 120
Vetor de
10 inteiros
1036 97
996
0
4 10 num
8 120 res
12 4 *pint
16
0
4 10 num
8 120 10 res = *pint
12 4 *pint
16
Estes operadores não devem ser confundidos com os já estudados em capı́tulos
anteriores. O operador * para ponteiros não tem nada a ver com o operador
multiplicação *. O operador ponteiro * é unário e, como o operador &, tem
precedência maior que do que todos os operadores aritméticos.
30
100
30
i n t * p1 , * p2 ;
i n t i = 100;
0
4 10 v[0]
8 20 v[1]
12 30 v[2]
16 40 v[3]
p1 = &v[2];
20 50 v[4]
24 12 *p1
28 32 *p2
p2 = &i;
32 100 i
Pode parecer estranho que um ponteiro para um número inteiro, que é arma-
zenado em quatro bytes, seja incrementado por um e passe para apontar para
o próximo número inteiro. A primeira vista, para que passasse a apontar para
o próximo endereço, seria necessário aumentar o endereço em quatro. Ou seja,
sempre que um ponteiro é incrementado (decrementado) ele passa a apontar
para a posição do elemento seguinte (anterior). O compilador interpreta o co-
mando p1++ como: passe a apontar para o próximo número inteiro e, portanto,
aumenta o endereço do número de bytes correto. Este ajuste é feito de acordo
com o tipo do operando que o ponteiro está apontando. Do mesmo modo, somar
três a um ponteiro faz com que ele passe apontar para o terceiro elemento após o
atual. Portanto, um incremento em um ponteiro que aponta para um valor que
é armazenado em n bytes faz que n seja somado ao endereço. É então possı́vel
somar-se e subtrair-se inteiros de ponteiros. A operação abaixo faz com que o
ponteiro p passe a apontar para o terceiro elemento após o atual.
p = p + 3;
Também é possı́vel usar-se o seguinte comando
*(p+1)=10;
Este comando armazena o valor 10 na posição seguinte àquela apontada por
p. A operação é realizada nos seguintes passos:
i f ( c == v )
printf ( " As variáveis estao na mesma posicao . " );
else
printf ( " As variaveis nao estao na mesma posicao . " );
return 0;
}
o compilador faz é alocar um ponteiro para apontar para a memória, sem que
espaço seja reservado. O nome de um vetor é chamado de ponteiro constante
e, portanto, não pode ter o seu valor alterado. O nome de um ponteiro cons-
tante não pode aparecer em expressões no lado esquerdo do sinal de igual, ou
seja, não pode receber valores diferentes do valor inicial atribuı́do na declaração
da variável. Assim, os comandos que alteram o ponteiro list, mostrados no
exemplo 9.5, não são válidos:
Quando uma cadeia de caracteres como esta é enviada para a função, o que
é passado é o ponteiro para a cadeia. É possı́vel então carregar o endereço da
cadeia em um ponteiro do tipo char, como no exemplo 9.8. Neste programa é
contado o número de caracteres de uma cadeia. Observe o ponteiro *(s+tam++)
apontando caractere a caractere.
s = lista ;
while (*( s + tam ++) != ’ \0 ’ );
tam - -;
printf ( " O tamanho do string \"% s \" e % d caracteres .\ n " ,
lista , tam );
return 0;
}
Um outro exemplo (Programa 9.9) mostra uma função que copia um cadeia
de caracteres para outra.
Considere uma matriz chamada matriz de tamanho LIN,COL que poderia ser
declarada e ter um de seus elementos lidos da maneira mostrada no trecho de
programa listado em 9.12. Caso o programa utilizasse ponteiros ao invés de
notação de matrizes, poderı́amos usar uma solução que mapeasse a matriz que
é um objeto de duas dimensões em um vetor que tem apenas uma. Neste caso
o programa deve fazer a translação de endereços toda vez que precisar ler ou
escrever na matriz. O trecho de programa ficaria como mostrado no exemplo
9.13. A expressão matriz+(i*COL+j) calcula a posição do elemento matriz[i][j]
a partir do primeiro elemento da matriz que está no endereço inicial matriz.
No entanto, esta solução ainda não é a melhor já que o usuário necessita
escrever diretamente uma expressão para mapear o endereço bidimensional da
matriz matriz[i][j] em um endereço linear. O ideal é usar uma notação que
use somente ponteiros. Esta notação será discutida nas seções seguintes.
156 CAPÍTULO 9. PONTEIROS
i n t * matriz ;
int i, j;
i n t main ( void )
{
char * linha [ LINHAS ];
int i;
*(matriz+1) linha 1
*(matriz+2)
linha 2
*(matriz+n)
linha n
O Programa 9.15, listado a seguir, irá pedir ao usuário que digite o número
de linhas e colunas da matriz. Em seguida lerá todos os elementos da matriz e
por último irá trocar duas linhas da matriz de posição. Observe que agora foi
criado um ponteiro para ponteiro chamado de **matriz. O programa primeiro
pergunta o número de linhas da matriz para poder alocar espaço para armazenar
os ponteiros para cada uma das linhas. Em seguida é alocado espaço para
armazenar cada uma das linhas. O comando
matriz = (int **) malloc (lin * sizeof(int *));
foi usado pelo programa para reservar espaço para armazenar lin linhas de
ponteiros para ponteiros. Observe que o comando sizeof(int *) calcula o espaço
para armazenar um ponteiro na memória. Note também que o valor retornado
pela função malloc foi conformado ao tipo ponteiro para ponteiro pela operação
(int **). O interessante do programa é que a troca de linhas da matriz envolve
simplesmente a troca de dois ponteiros e não a troca de todos os elementos das
linhas. Esta solução é muito mais rápida do que trocar elemento a elemento,
especialmente para matrizes grandes.
A seguir mostramos o programa nas listagens 9.16 e 9.17 que é o exemplo
anterior modificado para utilizar funções. O propósito é mostrar como ficam as
chamadas e as definições das funções que utilizam ponteiros para ponteiros.
160 CAPÍTULO 9. PONTEIROS
i n t ** aloca_linhas ( i n t lin )
{
i n t ** m ;}
m = ( i n t **) malloc ( lin * s i z e o f ( i n t *));
i f (! m )
{
puts ( " Sem espaço para alocar memória " );
return 1;
}
return m ;
}
void aloca_colunas ( i n t ** matriz , i n t lin , i n t col )
{
int i;
f o r ( i =0; i < lin ; i ++)
{
*( matriz + i ) = ( i n t *) malloc ( col * s i z e o f ( i n t ));
i f (! *( matriz + i ) )
{
printf ( " Sem espaço para linha % d " , i );
return 1;
}
}
}
162 CAPÍTULO 9. PONTEIROS
9.9 Exercı́cios
9.2: Escreva um programa que leia uma frase de até 80 caracteres do teclado
e imprima a freqüência com que aparece cada uma das letras do alfabeto na
frase.
9.3: Escreva um programa que leia uma frase de até 80 caracteres e a imprima
em ordem reversa convertendo todos os caracteres minúsculos para maiúsculos.
9.4: Escreva um programa que leia uma matriz e a imprima. O programa deve
ler o numero de colunas e linhas do teclado. O programa deve ainda trocar duas
linhas da matriz de posição. Os números das linhas a serem trocadas devem ser
lidos do teclado.
9.5: Escreva um programa que simule uma pilha usando vetores. O programa
deve implementar as seguintes operações na pilha:
• Inserir
• Remover
• Listar
9.6: Escreva uma função que receba um ponteiro para uma cadeia de caractere
e troque todo o caracter após um branco pelo seu equivalente maiúsculo.
9.7: Escreva um programa que leia seu nome completo e pergunte quantas
letras tem o seu primeiro nome. Assuma que a letra ’a’ tem ı́ndice 0, a letra
’b’ ı́ndice 1 e assim por diante. O programa deve imprimir quantas letras iguais
a letra cujo ı́ndice é o número de letras do seu primeiro nome existem no seu
nome completo.
9.8: Escreva um programa que leia seu nome completo e pergunte quantas letras
tem o seu primeiro nome. O seu programa deve usar a função posicao que tem
o seguinte protótipo:
int posicao(char *substr, char *str);
Esta função deve verificar se a cadeia apontada por substr está presente na
cadeia apontada por str e retornar a posição em que a sub-cadeia aparece em
cadeia.
9.9: Escreva um programa que procure em uma matriz elementos que sejam
ao mesmo tempo o maior da linha e o menor coluna. As dimensões da matriz
devem ser pedidas ao usuário.
Estruturas
10.1 Introdução
Uma estrutura é um conjunto de uma ou mais variáveis, que podem ser de ti-
pos diferentes, agrupadas sob um único nome. O fato de variáveis agrupadas
em uma estrutura poderem ser referenciadas por um único nome facilita a ma-
nipulação dos dados armazenados nestas estruturas. Um exemplo poderia ser
uma estrutura que armazenasse as diversas informações sobre os alunos de uma
Universidade. Nesta estrutura estariam armazenadas, sob o mesmo nome, in-
formações do tipo: nome, registro, data de nascimento, data de ingresso, CPF,
etc. Uma estrutura pode incluir outras estruturas além de variáveis simples.
As estruturas facilitam manipular estes agrupamentos complexos de dados. Por
exemplo, considere o problema de ordenar as informações sobre os alunos da
Universidade exemplo. A ordenação pode ser efetuada como se todos os dados
que compõem a estrutura fossem uma entidade única.
165
166 CAPÍTULO 10. ESTRUTURAS
A declaração acima ainda não alocou espaço de memória já que nenhuma
variável foi realmente definida. Esta declaração é apenas um modelo de como
estruturas do tipo ALUNO devem ser construı́das. Para definir estruturas deste
tipo podemos usar a seguinte declaração.
struct ALUNO paulo, carlos, ana;
Nesta declaração três estruturas do tipo ALUNO foram criadas. Esta de-
claração alocou espaço para armazenar os dados dos três alunos. A declaração
acima é idêntica, na forma, a declaração de variáveis de um tipo pré-definido,
como por exemplo:
int a, b, c;
É possı́vel declarar ao mesmo tempo o modelo da estrutura e as variáveis do
programa. Por exemplo,
s t r u c t ALUNO
{
char nome [40];
i n t registro ;
i n t ano_entrada ;
char curso [20];
} paulo , carlos , ana ;
Agora vamos modificar a estrutura aluno de modo que ela inclua a data de
nascimento do aluno. A estrutura fica com a seguinte definição:
10.2. DEFINIÇÕES BÁSICAS 167
s t r u c t aluno
{
char nome [40];
i n t registro ;
i n t ano_entrada ;
char curso [20];
s t r u c t DATA data_nascime n to ;
};
Ao mesmo tempo que typedef tem a vantagem de tornar mais claro a fina-
lidade de cada variável ele pode trazer problemas na medida em que esconde o
real tipo da variável.
É comum o uso de typedef em conjunto com struct. Considere a definição
de uma estrutura para guardar tempos gastos em tarefas. Esta estrutura deve
guardar horas, minutos e segundos. Usando esta combinação, a definição é
usualmente feita da seguinte maneira:
s t r u c t _TEMPO
{
i n t hora , minuto , segundo ;
};
typedef s t r u c t _TEMPO TEMPO ;
...
TEMPO t1 ;
Uma forma ainda mais abreviada, junta as duas definições, ficando a de-
finição da estrutura da seguinte maneira:
typedef s t r u c t _TEMPO
{
i n t hora , minuto , segundo ;
} TEMPO ;
...
TEMPO t1 ;
168 CAPÍTULO 10. ESTRUTURAS
typedef s t r u c t _EMPREGADO
{
char nome [40];
f l o a t salario ;
} EMPREGADO ;
i n t main ()
{
EMPREGADO temp , emp1 ;
typedef s t r u c t _ALUNO {
char nome [40];
f l o a t n1 , n2 , media ;
} ALUNO ;
i n t main ( void) {
ALUNO turma [4] , temp ;
i n t jaOrdenados = 0 , foraOrdem , i ;
f o r ( i = 0; i < 4; i ++) {
gets ( turma [ i ]. nome );
scanf ( " % f " , & turma [ i ]. n1 );
do {} while ( getchar ()!= ’\ n ’ );
scanf ( " % f " , & turma [ i ]. n2 );
do {} while ( getchar ()!= ’\ n ’ );
turma [ i ]. media =( turma [ i ]. n1 + turma [ i ]. n2 )/2.0;
}
do {
foraOrdem = 0;
f o r ( i = 0; i < 4 - 1 - jaOrdenados ; i ++) {
i f ( turma [ i ]. media > turma [ i +1]. media ) {
temp = turma [ i ];
turma [ i ] = turma [ i +1];
turma [ i +1] = temp ; foraOrdem = 1;
}
}
jaOrdenados ++;
} while ( foraOrdem );
f o r ( i =0; i <4; i ++) {
printf ( " \ nDados do aluno % d \ n " , i );
printf ( " % s : %0.1 f , %0.1 f , %0.1 f \ n " ,
turma [ i ]. nome , turma [ i ]. n1 , turma [ i ]. n2 , turma [ i ]. media );
}
return 0;
}
typedef s t r u c t _CIRCULO
170 CAPÍTULO 10. ESTRUTURAS
{
f l o a t x , y , raio ;
} CIRCULO ;
f l o a t Area ( f l o a t r )
{
return 3.141516 * r * r ;
}
i n t main ( void )
{
CIRCULO c ;
c . x = c . y = c . raio = 1.0;
printf ( " % f \ n " , Area ( c . raio ));
return 0;
}
A função que recebe este parâmetro está preparada para receber uma variável
de ponto flutuante simples. Caso seja necessário passar o endereço de um dos
membros ou elementos da estrutura basta colocar o operador & antes do nome
da estrutura. Por exemplo, para trocar os valores das coordenadas x dos centros
de dois cı́rculos c1 e c2 usarı́amos chamadas da seguinte forma.
troca_x (& c1 .x , & c2 . x );
typedef s t r u c t _PONTO
{
fl oat x, y;
} PONTO ;
f l o a t comp ( PONTO p1 , PONTO p2 )
{
return sqrt ( pow ( p2 .x - p1 .x ,2)+ pow ( p2 .y - p1 .y ,2));
}
i n t main ( void)
{
PONTO p1 , p2 ;
i n t ano_entrada ;
f l o a t n1 , n2 , media ;
} * maria ;
Para alocar espaço para estruturas apontadas por ponteiros é necessário usar
o operador unário sizeof, isto porque o tamanho de uma estrutura é sempre
igual ou maior que a soma dos tamanhos dos seu componentes. Para explicar
esta fato devemos considerar como os dados são armazenados na memória dos
computadores.
Algumas arquiteturas de computadores endereçam os dados na memória por
bytes, isto é cada endereço de memória se refere a um byte. No entanto, estas
arquiteturas lêem sempre uma palavra inteira da memória. Usualmente, pala-
vras podem ser compostas de dois bytes e começam em endereços pares, como
está mostrado na figura abaixo. Sabemos que existem variáveis que ocupam
mais de um byte, por exemplo inteiros que são compostos de dois bytes.
Imagine então uma estrutura composta de um caractere (1 byte) e um
número de inteiro (2 bytes). Caso a memória do computador seja organizada
em palavras de 16 bits ou 2 bytes a estrutura acima ocuparia 3 bytes ou uma
palavra e meia. Para ler o número inteiro o programa deveria ler duas palavras.
Lembrar que se os dados fossem sempre armazenados seqüencialmente, metade
do número inteiro estaria em uma palavra e a metade restante na outra, como
está indicado na figura abaixo (parte a). Para facilitar o acesso às variáveis,
alguns compiladores armazenam as variáveis de acordo com o que está indi-
cado na figura (parte b). Observar que agora a estrutura ocupa quatro bytes.
Neste caso o acesso ao número inteiro será sempre feito em um passo e portanto
ganhou-se em tempo de acesso ao custo de gasto de memória. Este é uma troca
constante em computação.
Vimos então que embora o total de bytes dos elementos da estrutura fosse
três o compilador pode armazenar a estrutura em quatro bytes, daı́ a necessidade
de sempre usar o operador sizeof quando alocar espaço.
O programa 10.7 mostra como alocar espaço para uma variável simples e
como usar esta variável em diversos tipos de comandos.
O programa 10.8 mostra como utilizar ponteiros para vetores de estruturas e
a forma mais segura de alocar espaço para os dados. Observar as notações usadas
na função que lê os dados dos funcionários. Notar que (cadastro+i)->salario é
o valor da salário.
10.6. PONTEIROS PARA ESTRUTURAS 173
i n t main ( void)
{
ALUNO * maria ;
typedef s t r u c t _func {
char nome [40];
f l o a t salario ;
} Tfunc ;
i n t main ( void ) {
Tfunc * cadastro ;
i n t funcionarios ;
char linha [40];
10.7 Exercı́cios
le ( turma );
puts ( " Imprimindo dados lidos da turma . " );
puts ( " Digite qualquer coisa para continuar . " );
getchar ();
imprime ( turma );
ordena_medias ( turma );
puts ( " Imprimindo dados ordenados da turma . " );
puts ( " Digite qualquer coisa para continuar . " );
getchar ();
imprime ( turma );
getchar ();
}
10.4: Escrever um programa que utilize structs e ponteiro para struct e im-
prima o conteúdo das variáveis da struct.
176 CAPÍTULO 10. ESTRUTURAS
10.7: Fazer um programa que simule as operações de uma pilha push e pop,
usando structs. Um exemplo de entrada poderia ser o seguinte:
empilha C
empilha B
empilha A
desempilha A
desempilha B
desempilha C
10.8: Escreva um programa que solicite o nome e telefone de uma pessoa e grave
essas informações num vetor de uma estrutura que contem esses dados (nome e
telefone). O programa deve ter três opções apenas: uma que adiciona um novo
dado, outra que lista todos os dados atualmente armazenados na memória e
outra que sai do programa. Esse vetor de estrutura deve ter apenas 10 elementos
e fornecer uma mensagem de erro caso o usuário tente adicionar mais pessoas
que este máximo permitido.
10.11: Crie uma estrutura chamada ret^angulo, que possua duas estruturas
ponto (o ponto superior esquerdo e o ponto inferior direito). Faça um programa
que receba as informações acerca de um retângulo (as coordenadas dos dois
pontos), e informe a área, o comprimento da diagonal e o comprimento de cada
aresta
11.1 Introdução
Em C não existem instruções especiais de entrada e saı́da como em outras lin-
guagens de programação. Estas tarefas, em C são executadas por funções espe-
cialmente criadas para esta finalidade e armazenadas em bibliotecas especı́ficas.
Por esta razão todos programas em C que precisam de entrada e/ou saı́da de
dados necessitam incluir a diretiva #include<stdio.h> no inı́cio do programa,
para permitir o uso da biblioteca padrão stdio de funções de entrada e saı́da.
177
178 CAPÍTULO 11. ENTRADA E SAÍDA POR ARQUIVOS
Até agora temos trabalhado com os fluxos de dados padrão: stdin, para
entrada de dados e stdout para saı́da de dados. Ao iniciar todo programa em
C é automaticamente associado a estes dois fluxos de dados sem necessitar de
nenhuma intervenção do programador. A definição de que periféricos estarão
associados a estes fluxos depende do sistema operacional. Normalmente o fluxo
de entrada (stdin) está associado ao teclado e o fluxo de saı́da (stdout) ao
monitor.
Um fluxo binário é composto por uma seqüência de bytes lidos, sem tradução,
diretamente do dispositivo externo. Existe uma correspondência um para um
entre os dados do dispositivo e os que estão no fluxo que o programa manipula.
A Figura 11.1 ilustra estes dois tipos de fluxos. No fluxo de texto os dados são
armazenados como caracteres sem conversão para representação binária. Cada
um dos caracteres ocupa um byte. O numero 12 ocupa dois bytes e o número
113 ocupa 3. Um caractere em branco foi inserido entre cada um dos números
para separá-los, de modo que a função de entrada e saı́da possa descobrir que
são dois números inteiros (12 e 113) e não o número 12113.
fluxo de texto
fluxo binário
11.2.3 Arquivos
• remover um arquivo;
FILE *arq;
onde arq é o ponteiro que será usado para executar as operações no arquivo.
180 CAPÍTULO 11. ENTRADA E SAÍDA POR ARQUIVOS
Função Descrição
fopen() Abre um arquivo
fputc() Escreve um caractere em um arquivo
getc(), fgetc() Lê um caractere de um arquivo
fprintf() Equivalente a printf()
sscanf() Equivalente a scanf(). Lê de uma cadeia de caracteres
fscanf() Equivalente a scanf()
fseek() Posiciona o arquivo em um ponto especı́fico
rewind() Posiciona o arquivo no inı́cio
feof() Retorna verdade se chegou ao fim do arquivo
ferror() Verifica a ocorrência de um erro
fflush() Descarrega o buffer associado ao arquivo
fread() Leitura de dados no modo binário
fwrite() Escrita de dados no modo binário
“r”: Abre um arquivo para leitura, o arquivo deve existir ou um erro ocorre.
“w”: Cria um arquivo vazio para escrita, caso um arquivo com o mesmo nome
exista o seu conteúdo é apagado.
“a”: Adiciona ao final de um arquivo. O arquivo é criado caso ele não exista.
“r+”: Abre um arquivo para leitura e escrita. O arquivo deve existir ou um
erro ocorre.
11.4. INÍCIO E FIM 181
Observar que se um arquivo for aberto com permissão de escrita todo o seu
conteúdo anterior será apagado. Caso o arquivo não exista ele será criado.
O trecho de programa abaixo ilustra os passos necessários para abrir um
arquivo para escrita. Primeiro é declarado o ponteiro pa para o arquivo. Em
seguida a função fopen é chamada para associar o nome externo do programa
(arquivo.txt) no modo escrita ao ponteiro pa. Um teste para ponteiro nulo
é feito para verificar se ocorreu algum problema com a operação de abertura do
arquivo.
Lembrar que abrir, para escrita, um arquivo que já existe, implica em apagar
todo o conteúdo anterior e a preparação do arquivo para receber dados a partir
de seu ponto inicial. Se o programador deseja acrescentar dados ao final de um
arquivo já existente o modo de abertura deve ser a.
onde parq é um ponteiro de arquivo para o arquivo que deve ser fechado. Todos
os buffers internos associados com o fluxo de dados do arquivo são descarre-
gados. O conteúdo de qualquer buffer não escrito é escrito e dados não lidos
de buffers são perdidos. Este ponto é importante de ser considerado porque
em muitos sistemas operacionais uma operação de escrita em um arquivo não
ocorre imediatamente a emissão da ordem de escrita. O sistema operacional
pode executar a ordem no momento que achar mais conveniente. Um valor zero
de retorno significa que a operação foi executada com êxito, qualquer outro valor
implica em erro.
182 CAPÍTULO 11. ENTRADA E SAÍDA POR ARQUIVOS
A função feof() indica que um arquivo chegou ao seu final. A pergunta que
pode surgir é a seguinte - Se já existe o valor EOF para indicar o final de arquivo,
por que precisamos de uma função extra do tipo feof()? O problema é que EOF
é um valor inteiro e ao ler arquivos binários este valor pode ser lido como parte
do arquivo e não por ser o final do arquivo. A função feof() serve para indicar
que o final de um arquivo binário foi encontrado. Naturalmente esta função
pode ser aplicada também a arquivos texto. O protótipo da função é o seguinte:
c = getchar ();
while ( c != EOF )
{
putchar ( c );
c = getchar ();
}
return 0;
}
é importante observar que o arquivo deve estar aberto em um modo que permita
a execução das operações desejadas. Por exemplo, um arquivo aberto somente
para “escrita” e em seguida reposicionado para o inı́cio, não irá permitir outra
operação que não seja “escrita”.
11.5. LENDO E ESCREVENDO CARACTERES 183
A função lê o caractere como um unsigned char mas retorna o valor como
um inteiro, onde o byte mais significativo vale zero. O apontador do arquivo
avança um caractere e passa a apontar para o próximo caractere a ser lido.
A função devolve o código EOF ao chegar ao final do arquivo ou caso um erro
ocorra. O valor EOF também é um inteiro válido e portanto ao usar arquivos
binários é necessário que a função feof() seja utilizada para verificar o final do
arquivo. A função ferror() pode ser usada para determinar se um erro ocorreu.
Para escrever caracteres há duas funções definidas putc() e fputc(). Os
protótipos das funções são os seguintes:
int putc(int ch, FILE *parq); int fputc(int ch, FILE *parq)
onde parq é um ponteiro de arquivo para o arquivo que foi previamente aberto
por meio da função fopen() e ch é o caractere a ser escrito.
O programa 11.2 mostra como um arquivo pode ser criado para leitura e
escrita. Em seguida um conjunto de caracteres lido do teclado é escrito no ar-
quivo. O próximo passo é a leitura do arquivo que é iniciada após uma chamada
a função rewind(), fazendo com que o indicador de posição do arquivo volte a
apontar para seu inı́cio.
Uma outra alternativa mostrada em 11.3 mostra um exemplo onde o ar-
quivo é criado para escrita em seguida é fechado e reaberto para leitura ficando
automaticamente posicionado no inı́cio para a leitura.
return 0;
}
11.6. TESTANDO ERROS 185
}
c = getchar ();
while (! feof ( stdin ))
{
fputc (c , pa );
c = getchar ();
}
fclose ( pa );
printf ( " \ nTerminei de escrever , agora vou ler .\ n " );
i f (( pa = fopen ( nome , " r " )) == NULL )
{
printf ( " \ n \ nErro ao abrir o arquivo - leitura .\ n " );
exit (1);
}
c = fgetc ( pa );
while (! feof ( pa ))
{
putchar ( c );
c = fgetc ( pa );
}
fclose ( pa );
return 0;
}
i f ( pArq == NULL )
{
printf ( " Erro abrindo arquivo . " );
return 1;
}
else
{
fputc ( ’x ’ , pArq );
i f ( ferror ( pArq ))
{
printf ( " Erro escrevendo arquivo \ n " );
186 CAPÍTULO 11. ENTRADA E SAÍDA POR ARQUIVOS
fclose ( pArq );
return 1;
}
}
return 0;
}
}
rewind ( pa ); /* volta ao inicio do arquivo */
printf ( " \ nTerminei de escrever , agora vou ler .\ n \ n " );
fgets ( linha , MAX , pa );
while (! feof ( pa ))
{
puts ( linha );
fgets ( linha , MAX , pa );
}
fclose ( pa );
return 0;
}
size_t fread (void *ptr, size_t size, size_t nmemb, FILE *parq);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *parq);
A função fread lê nmemb objetos, cada um com size bytes de comprimento,
do fluxo apontado por stream e os coloca na localização apontada por ptr. A
função retorna o número de itens que foram lidos com sucesso. Caso ocorra um
erro, ou o fim do arquivo foi atingido o valor de retorno é menor do que nmemb
ou zero. Esta função não distingue entre um fim de arquivo e erro, portanto é
aconselhável o uso de feof() ou ferror() para determinar que erro ocorreu.
A função fwrite escreve nmemb elementos de dados, cada um com size bytes
de comprimento, para o fluxo apontado por stream obtendo-os da localização
apontada por ptr. fwrite retorna o número de itens que foram lidos com sucesso.
Caso ocorra um erro, ou o fim do arquivo foi atingido o valor de retorno é menor
do que nmemb ou zero. O programa 11.7 ilustra como podemos escrever e ler
dados binários de diferentes tipos em arquivos. Como um dos parâmetros da
função é o número de bytes do dado a ser lido, é recomendado o uso de sizeof.
i n t main ()
{
FILE * pa ;
char nome [40] , linha [80];
PESSOA turma [4] , back [4];
int i;
f o r ( i =0; i <4; i ++)
{
puts ( " Nome ? " );
fgets ( turma [ i ]. nome , 40 , stdin );
turma [ i ]. nome [ strlen ( turma [ i ]. nome ) -1]= ’ \0 ’;
puts ( " Ano ? " ); fgets ( linha , 80 , stdin );
sscanf ( linha , " % d " , & turma [ i ]. ano );
}
puts ( " \ nGravando \ n " );
puts ( " Qual o nome do arquivo ? " ); fgets ( nome , 40 , stdin );
nome [ strlen ( nome ) -1]= ’ \0 ’;
i f (( pa = fopen ( nome , " w + " )) == NULL )
{
puts ( " Arquivo nao pode ser aberto " );
return 1;
}
f o r ( i =0; i <4; i ++)
{
i f ( fwrite ( & turma [ i ] , s i z e o f ( PESSOA ) , 1 , pa ) != 1)
puts ( " Erro na escrita . " );
}
rewind ( pa );
f o r ( i =0; i <4; i ++)
{
i f ( fread ( & back [ i ] , s i z e o f ( PESSOA ) , 1 , pa ) != 1)
{
i f ( feof ( pa )) break ;
puts ( " Erro na leitura . " );
}
}
f o r ( i =0; i <4; i ++)
{
printf ( " Nome = % s \ n " , back [ i ]. nome );
printf ( " Ano = % d \ n \ n " , back [ i ]. ano );
}
return 0;
}
11.10. EXERCÍCIOS 191
11.10 Exercı́cios
3
ZE SA
8.5
10.0
ANTONIO SANTOS
7.5
8.5
SEBASTIAO OLIVEIRA
5.0
6.0
Escreva um programa que imprima os nomes de todos os alunos que têm a média
das duas notas menor que 7.0
11.4: Escreva um programa que leia de um arquivo, cujo nome sera fornecido
pelo usuário, um conjunto de números reais e armazena em um vetor. O tama-
nho máximo do vetor e dado pela constante TAM_MAX. A quantidade de números
no arquivo varia entre 0 e TAM_MAX. O programa ao final calcula a media dos
números lidos.
11.6: Crie uma função que receba duas strings como parâmetros, uma com um
endereço de arquivo e outra com um texto qualquer, e adicione o texto no fim
do arquivo.
11.11: Faça um programa que receba o nome de um arquivo e gere uma cópia.
1 - O
11 - a
indicando que os primeiros caracteres dos arquivos são iguais (O) bem como o
décimo primeiro (a)
194 CAPÍTULO 11. ENTRADA E SAÍDA POR ARQUIVOS
Apêndice A
Tabela ASCII
0 1 2 3 4 5 6 7 8 9
0 nul soh stx etx eot enq ack bel bs ht
1 nl vt ff cr so si dle dc1 dc2 dc3
2 dc4 nak syn etb can em sub esc fs gs
3 rs us sp ! ” # $ % & ‘
4 ( ) * + , - . / 0 1
5 2 3 4 5 6 7 8 9 : ;
6 ¡ = ¿ ? @ A B C D E
7 F G H I J K L M N O
8 P Q R S T U V W X Y
9 Z [ \ ] ^ _ ’ a b c
10 d e f g h i j k l m
11 n o p q r s t u v w
12 x y z { — } ~ del
195
196 APÊNDICE A. TABELA ASCII
Palavras Reservadas
asm: Indica que código escrito em assembly será inserido junto comandos C.
break: Comando usado para sair incondicionalmente dos comandos for, while,
switch, and do...while.
const: Modificados de dados que impede que uma variável seja modificada.
Esta palavra não existia nas primeiras versões da linguagem C e foi intro-
duzida pelo comitê ANSI C. Veja volatile.
default: É usado dentro do comando switch para aceitar qualquer valor não
definido previamente com um comando case.
else: Comando que indica um bloco de comandos alternativo que deve ser exe-
cutado quando a condição testada pelo comando if foi avaliada como
FALSA.
197
198 APÊNDICE B. PALAVRAS RESERVADAS
enum: Tipo definido pelo usuário que permite a definição de variáveis que irão
aceitar somente certos valores.
extern: Modificador de dados que indica que uma variável irá ser declarada
em outra área do programa.
goto: Comando que causa um pulo para uma posição do programa marcada
com um rótulo.
if: Comando de testes usado para mudar o fluxo do programa baseada em uma
decisão VERDADE/FALSO.
long: Tipo de dados usado para armazenar valores inteiros com precisão maior
do que o tipo int. Nos computadores modernos o tipo long tem a mesma
precisão que o tipo int e são usados 4 bytes.
short: Tipo de dados usado para armazenar valores inteiros em precisão menor
do que o tipo int. Neste tipo 2 bytes são usados para armazenar os dados.
signed: Modificador usado para indicar que uma variável pode armazenar tanto
valores positivos como negativos.
switch: Comando de desvio usado para permitir que o fluxo do programa possa
ser mudado para várias direções diferentes. Usado em conjunto com o
comando case.
typedef: Modificador usado para criar novos nomes para tipos já existentes.
unsigned: Modificador usado para significar que uma variável conterá somente
valores positivos.
void: Palavra usada para significar que ou a função não retorna nada ou que
um ponteiro deve ser considerado genérico ou ser capaz de apontar para
qualquer tipo de dados.
volatile: Modificador que significa que uma variável pode ser alterada.
while: Comando de teste que executa uma seção de código enquanto uma
condição retorna VERDADE.
201
Índice
ábaco, 21 depuração, 35
álgebra booleana, 51 Difference Engine, 22
C, 34 double, 62
DRAM, 30
abertura de arquivo, 179
algoritmo, 32, 41 EDSAC, 26
Analytical Engine, 23 EEPROM, 30
arquivo, 179 endereços de memória, 30
abrir arquivo, 180 ENIAC, 25
fechar arquivo, 181 EPROM, 30
fim de arquivo, 182 erros de compilação, 35
assembler, 33
assembly, 33 final de linha, 177
atribuição, 70 Flash memory, 30
atribuições, 52 fluxo de dados, 177
fluxo binário, 178
Babbage, 22 fluxo de texto, 177
base 2, 31 Fluxograma, 44, 45
Basic, 34 Fortran, 34
BIOS, 30
bit, 31 gcc, 37
byte, 31 George Boole, 51
hardware, 27
C++, 34
Harvard Mark I, 24
cadeia de caractere, 68
char, 61 IDE, 37
chipset, 28 int, 61
circuitos integrados, 26 Integrated Development Environment,
Cobol, 34 37
comandos de controle, 53
comandos de repetição, 54 Java, 34
compilador, 35
compiladores, 34 linguagem de alto nı́vel, 34
constante caractere, 67 linguagem de máquina, 33
constante em ponto-flutuante, 66 linguagem intermediária, 35
constante hexadecimal, 65 Linguagem Natural, 44
constante octal, 64 linguagem natural, 44
constantes, 61, 62 linguagens interpretadas, 35
link edition, 35
declaração de variáveis, 69 Lisp, 34
Delphi, 34 long, 62
202
ÍNDICE 203
memória, 29
memória cache, 29
memória principal, 29
microcomputadores, 28
microprocessador, 28
MINGW, 37
montar, 33
Moore, 19
null, 68
palavra de memória, 31
Pascal, 34
Pascalina, 22
periféricos, 28, 32
pipelining, 27
Prolog, 34
PROM, 30
pseudo-código, 44
Pseudo-linguagem, 44
pseudo-linguagem, 45
RAM, 30
registradores, 29
ROM, 30
signed, 62
sistema operacional, 36
software, 32
soroban, 21
string, 68
UCP, 27
Unidade Central de Processamento, 27
Unidade de Controle, 27
Unidade de Entrada e Saı́da, 27
Unidade Lógica e Aritmética, 27
unsigned, 62
variáveis, 61
variável, 68
VLSI, 26
void, 62
von Neumann, 26, 47
Z1, 24
Zuze, 24