You are on page 1of 105

Programao em C

Um guia para programaao em linguagem C,


que pretende servir tambmcomo uma intro-
duao Computaao. (Baseado, em parte, no
curso de Introduao Computaao [MAC-
110/113] do IME-USP.)
Este guia umtrabalho emprogresso, emcons-
tante mutao! Algumas partes ainda esto in-
completas ou faltando.
Uma cpia deste guia, em sua verso mais re-
cente, pode ser encontrada na pgina
http://fig.if.usp.br/~esdobay/
EuU.vuo S. Dov.v
edudoboyegmoii.com
Instituto de Fsica
Universidade de Sao Paulo
Versao de 6 de agosto de 2011
Sumrio
Prefcio 1
Introduo 3
1 Princpios bsicos 7
1.1 O ncleo de um programa . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2 Variaveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3 Entrada e sada de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.4 Matematica basica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3 Boas maneiras em C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2 Controle de uxo 19
2.1 Desvios condicionais: if . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.2 Repetindo operaes: laos (loops) . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Contadores e laos for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4 Condies compostas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
2.3 Repeties encaixadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.6 Variaveis booleanas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
3 Funes 37
3.1 Introduao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2 Os parametros e o valor de sada . . . . . . . . . . . . . . . . . . . . . . . 40
3.3 Usando funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.4 Trabalhando com varias funes . . . . . . . . . . . . . . . . . . . . . . . 43
3.3 Escopo de variaveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4 Mais sobre nmeros 47
4.1 Bases de numeraao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2 O armazenamento dos dados . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.3 Nmeros reais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.4 Funes matematicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.3 O tipo char . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
i
ii Sumrio
5 Ponteiros e vetores 67
3.1 Prolegomenos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.2 Ponteiros como parametros de funes . . . . . . . . . . . . . . . . . . . . 70
3.3 Cuidado com os ponteiros! . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.4 Vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
3.3 Vetores como argumentos de funes . . . . . . . . . . . . . . . . . . . . . 76
3.6 Ponteiros e vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
3.7 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.8 Mais sobre entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
6 Algoritmos 85
7 Mais ponteiros 87
7.1 Matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.2 Alocaao dinamica de memoria . . . . . . . . . . . . . . . . . . . . . . . . 88
7.3 Ponteiros duplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.4 Ponteiros para funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7.3 Escopo de variaveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
7.6 Funes recursivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8 Estruturas 95
8.1 Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.2 Listas ligadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Apndices
A Compilao 97
B Tabelas de referncia 99
Referncias Bibliogrcas 101
Prefcio
1
Introduo
Este captulo introduz alguns conceitos basicos sobre computaao, mas sem entrar na lin-
guagem C.
Oque umcomputador?
A defniao mais ampla de computador a de uma maquina que realiza uma srie de ope-
raes logicas ou matematicas sobre um conjunto de dados e devolve o resultado para o
usuario. O computador que conhecemos nao o nico exemplo disso; podemos citar di-
versos aparelhos eletronicos com essas caractersticas, de calculadoras a telefones celulares.
Um computador tem um conjunto de instrues que correspondem s operaes que
podem ser feitas com ele. Uma seqncia logica dessas instrues tal qual uma receita
de bolo o que conhecemos como programa.
Nos primeiros computadores, toda a programaao era feita diretamente no circuito do
aparelho: nao se podia modifcar o funcionamento do computador sem uma reconstruao
fsica dele ou de alguma de suas partes. Ainda existem computadores desse tipo o caso
de uma calculadora simples, por exemplo: voc nao pode criar programas e introduzir nela;
necessario mexer diretamente nas placas de circuito dela.
Ocomputador que conhecemos faz parte de umconjunto especial dentro da nossa def-
niao de computador: nele, os programas sao armazenados na memria (e nao inseridos
diretamente atravs dos circuitos), da mesma maneira que os dados, e podem ser criados
e modifcados utilizando o proprio Computador. A encarnaao mais comum desse tipo
de funcionamento um modelo conhecido como arquitetura de von Neumann. (A palavra
arquitetura usada em geral para descrever o modelo de funcionamento interno de um
computador.)
Esse modelo descreve o funcionamento geral do computador em termos da interaao
entre trs componentes:
uma unidade de processamento conhecida como CPU (unidade central de pro-
cessamento, na sigla em ingls) ou processador que responsavel por todas as
operaes matematicas e logicas. E basicamente o crebro do computador;
umespao para armazenamento (temporario) de dados, que a memoria RAM(sigla
em ingls para memria de acesso randmico);
3
4 Introduo
quaisquer dispositivos de entrada e sada, que recebem e enviam informaes de/-
para o usuario. Os exemplos mais imediatos sao o monitor (dispositivo de sada),
o teclado e o mouse (dispositivos de entrada). Tambm se encaixam aqui os dispo-
sitivos de armazenamento de dados, como o disco rgido, que tm papel tanto de
entrada quanto de sada.
Sada
Monitor
Mdias externas
Unidade de
processamento
(CPU)
Entrada
Teclado
Mouse
Mdias externas
Memria
Figura 0.1: Esquema da arquitetura de von Neumann.
A arquitetura de von Neumann um modelo, de certa forma, abstrato; na pratica, para
construir um computador, ha varios outros detalhes a se estabelecer. Diferentes escolhas
desses detalhes, devidas a interesses e propositos diferentes, levaram a diferentes arquite-
turas de processadores. Por exemplo, o funcionamento do processador que existe num
GameBoy bem diferente do funcionamento do processador de um computador pessoal.
Os computadores pessoais de hoje em dia usam, em sua maioria, processadores baseados
numa mesma arquitetura, conhecida como arquitetura Intel,' e que foi introduzida pela
primeira vez em processadores da Intel.
Linguagens de programao
Ja vimos que um programa simplesmente um conjunto de instrues ou comandos que
dizem ao computador como realizar as operaes que ele deve fazer. Mas como vm a ser
essas instrues: Oprocessador do computador so consegue compreender uma linguagem
que chamamos de linguagem de mquina; essa linguagem, inclusive, diferente para cada
arquitetura de processadores. Um programa escrito em linguagem de maquina consiste
de uma srie de bits (os famosos zeros e uns), que, para nos, parecem nao ter nenhum
signifcado. Uma instruao razoavelmente simples seria escrita assim:
10110000 00101010
Nao seria nada pratico usar essa linguagem para fazer programas com alto nvel de com-
plexidade; por isso, foram desenvolvidas diversas linguagens de programao, que servem
como intermediario entre a linguagem humana e a linguagem de maquina.
Uma linguagem de programaao deve ser, por um lado, legvel e compreensvel para
um humano, e, por outro lado, sufcientemente simples para que possa ser compreendida
Para ser mais preciso, a arquitetura Intel de 32 bits, ou, abreviadamente, IA-32. Mais tarde veremos o
que isso signica.
3
completamente e sem ambigidades por um computador. Geralmente, uma linguagem de
programaao composta de:
um conjunto de palavras-chave (geralmente em ingls, como if, Whiie, funclion),
associadas a certos comandos e recursos da linguagem;
um conjunto de smbolos (como #, ", {, etc.); e
um conjunto de regras que ditam o signifcado de todas essas coisas e como elas
podem ser combinadas e utilizadas.
No entanto, as linguagens de programaao nao sao entendidas diretamente pelo com-
putador (lembre-se, ele so entende a linguagemde maquina). Para que o computador possa
executar codigos escritos emlinguagens de programaao, existemprogramas que sao feitos
para entender essas linguagens e traduzir os codigos para a linguagemde maquina. Ha dois
tipos desses programas: os compiladores e os interpretadores. O processo de traduao do
codigo para a linguagem de maquina chama-se compilao.
Um interpretador l um programa escrito numa certa linguagem de programaao e
imediatamente executa as instrues nele contidas a traduao para a linguagem de ma-
quina feita na hora da execuao. Ja um compilador apenas traduz o programa para a
linguagem de maquina (compila o programa), deixando-o pronto para ser executado pos-
teriormente sem depender de outro programa; isso tem a vantagem de tornar a execuao do
programa mais rapida.
Os programas em linguagem C sao sempre compilados. Para aprender a usar um com-
pilador (o que voc certamente deve aprender, ja que precisara disso para poder executar
seus programas), dirija-se ao o Apndice ??.
1
Princpios bsicos
1.1 Oncleo de umprograma
Umprograma emC estruturado emfunes, que sao, basicamente, trechos de codigo que
podem ser chamados varias vezes para realizar uma certa tarefa. Todos os comandos que
o seu programa executar estarao dentro de alguma funao.
As funes, em geral, podem devolver valores o resultado de algum calculo ou con-
sulta, por exemplo e tambm podem receber parmetros por exemplo, os dados de
entrada para um calculo, ou os critrios a serem usados na busca. Nesse sentido, funes
em C sao semelhantes s funes s quais nos referimos em matematica.
Como acabamos de dizer, todos os comandos dum programa devem estar dentro de
uma funao. Em C, no entanto, existe uma funao privilegiada a funao main (prin-
cipal). Ela como se fosse o roteiro principal do programa: a execuao do programa sempre
comea por ela. Todas as outras funes (quando houver) sao chamadas, direta ou indire-
tamente, a partir da main.
Todo programa deve ter uma funao main. Outras funes podemser criadas (veremos
como um pouco mais adiante), e tambm podemos usar funes prontas ha uma srie
de funes que vem disponvel em qualquer compilador C, chamada de biblioteca padro
do C.
Vamos iniciar nosso estudo com um programa extremamente simples, que apenas im-
prime uma mensagem na tela:
#inciude <sldio.h>
inl moin(}
{
prinlf("0io moroviihoso mundo do progromoo!`n"}
relurn 0
]
Vamos ver o que acontece em cada linha:
Veja que, aqui, imprimir no tem nada a ver com a sua impressora que joga tinta no papel. Nesse livro,
sempre que usar essa palavra, entenda como uma referncia ao ato de mostrar alguma mensagem na tela.
7
8 Captulo 1. Princpios bsicos
#inciude <sldio.h>
Esta linha pede ao compilador que disponibilize para nos algumas funes de entrada
e sada de dados, que permitem que voc exiba mensagens na tela e leia dados que o
usuario digitar no teclado. Mais adiante veremos como que essa instruao funciona.
inl moin(}
E aqui que defnimos nossa funao main. As chaves { ] servem para delimitar o seu
contedo.
Se voc esta curioso, esse inl signifca que o valor que a funao devolve um nmero
inteiro, e os parnteses vazios indicam que a funao nao recebe nenhum parametro.
Nao se preocupe se isso nao fzer muito sentido; um pouco mais adiante estudaremos
funes com detalhe, e voc podera entender melhor o que tudo isso signifca. Aceitar
isso como uma formula pronta por enquanto nao lhe fara nenhum mal.
prinlf("0io moroviihoso mundo do progromoo!`n"}
Aqui printf o nome uma funao da biblioteca padrao ( possvel usa-la graas ao #in-
clude que colocamos la em cima!). Essa funao simplesmente toma a mensagem (uma
sequncia de caracteres) que lhe foi passada e mostra-a na tela.
Nessa linha, passamos como parametro a essa funao a mensagem que queremos im-
primir (usando aspas duplas, veja bem). E o que aquele `n intruso no fnal: Ele um
codigo especial que representa uma quebra de linha indicando que qualquer coisa
que for impressa depois da nossa mensagem deve ir para a linha seguinte. Se omi-
tssemos o `n, a proxima mensagem que fosse eventualmente impressa sairia grudada
nessa; isso til em varias situaes, mas nao particularmente nessa; em geral melhor
terminar a sada de um programa com uma quebra de linha.
Essa coisa entre aspas chamada de sequncia de caracteres (adivinhe por qu!), e
tambm bastante conhecida pelo seu nome em ingls, string (literalmente, cadeia
[de caracteres]). Para falar a verdade, usarei principalmente a palavra string daqui em
diante.
Note que usamos umponto-e-vrgula aqui. Da mesma maneira que emportugus usa-
mos um ponto fnal para encerrar uma frase, em C precisamos usar obrigatoriamente
um ponto-e-vrgula para encerrar um comando.
relurn 0
Essa instruao encerra a execuao do programa (por isso, deve ser sempre a ltima da
funao main). Almdisso, o nmero zero serve para indicar ao sistema que o programa
terminou com sucesso (nmeros diferentes de zero indicariam um erro); uma con-
venao da linguagem. Voc entendera melhor como isso funciona quando falarmos
detalhadamente de funes, no captulo 3.
Note novamente o ponto-e-vrgula.
Variveis 9
Um comentario adicional sobre a obrigatoriedade do ponto-e-vrgula: veja que ele nao
foi usado nem com o #inciude nem com a defniao da funao main. Neste ltimo caso,
nao se usa ponto-e-vrgula pois a defniao da funao nao propriamente um comando,
mas um bloco com varios comandos.
Ja no primeiro caso, o ponto-e-vrgula nao usado porque o #inciude um comando,
de certa maneira, especial. Por enquanto, apenas guarde isso: linhas comeadas com #
nao levam ponto-e-vrgula no fnal. Veremos mais adiante como funciona esse tipo de
comando.
E importante saber que a linguagem C diferencia maisculas e minsculas, ou seja, se
voc digitar PRTWTF ou Prinlf, ou trocar relurn por RETuRW, o programa nao ira funcionar.
1.2 Variveis
Uma das necessidades mais comuns em programaao a de guardar dados em algum lugar
da memoria. Para isso, a maioria das linguagens de programaao usa o conceito de vari-
veis, que sao simplesmente pedaos da memoria que servem para guardar um certo valor
(um nmero, por exemplo) e que tm um nome. Com isso, voc nao precisa se preocupar
em saber em que lugar da memoria voc guardara seus dados; todo o trabalho fca para o
compilador.
EmC, ha trs tipos primitivos de dados: nmeros inteiros, nmeros de ponto utuante
(um nome pomposo para os nmeros fracionarios, que tem a ver com a maneira como
os computadores trabalham com eles) e caracteres (como o, #, Z, 5 o caractere que
representa o nmero 3, e nao o nmero em si). A esses trs tipos de dados correspondem
os quatro tipos primitivos de variaveis, listados na tabela 1.1.
Na verdade, eu poderia dizer que sao apenas dois esses tipos: os nmeros inteiros e
os de ponto futuante. O que ocorre que os caracteres sao representados na memoria
como se fossem nmeros (inteiros); por exemplo, quando eu escrevo os caracteres obc em
algum lugar (em ltima instancia, isso fca em algum lugar da memoria), o que o computa-
dor guarda na memoria sao os trs nmeros 97, 98, 99. Isso esta relacionado conhecida
tabela ASCII, que nada mais que uma convenao de quais caracteres correspondem a
quais nmeros (consulte o apndice para ver uma tabela ASCII completa). Por causa disso,
o tipo de dados usado para armazenar caracteres tambm podem ser usado para guardar
nmeros, embora isso nao seja tao comum, pois ele so permite guardar nmeros pequenos.
Esses dois ou trs tipos de dados traduzem-se em quatro tipos primitivos de variaveis,
resumidos na tabela 1.1.
Tabela 1.1: Os quatro tipos primitivos de variaveis na linguagem C
Tipo Utilidade
inl Armazena um nmero inteiro
fiool Armazena um nmero de ponto futuante
doubie Como oat, mas fornece maior precisao
chor Guarda um nico caractere. Com esse tipo tambm podemos guardar
sequncias de caracteres (strings), mas isso requer um outro recurso da lin-
guagem que so veremos mais tarde.
10 Captulo 1. Princpios bsicos
Esses tipos sao chamados de primitivos porque eles podem ser modifcados e combi-
nados de certas maneiras, de modo a criar estruturas mais complexas de dados (por exem-
plo, as tais sequncias de caracteres), como veremos mais tarde. Por enquanto, usaremos
apenas os tipos inteiros; trabalharemos com nmeros de ponto futuante a partir do cap-
tulo ??.
Cada variavel que quisermos usar deve ser criada com antecedncia; para isso, pre-
cisamos dizer o nome e o tipo das variaveis. Para isso, usamos um comando no seguinte
formato:
tipo_da_variavel nome_da_variavel;
Esse tipo de comando chama-se declarao de variveis, e faz com que o computador re-
serve na memoria um espao sufciente para guardar valores do tipo que voc pediu. Veja-
mos alguns exemplos de declaraes:
inl dio
inl mes
inl ono
fiool precounilorio
fiool precololoi
Se voc tem varias variaveis do mesmo tipo, voc pode declara-las todas de uma vez
so simplesmente coloque os varios nomes separados por vrgulas. Poderamos, assim,
condensar o exemplo anterior com apenas duas linhas:
inl dio mes ono
fiool precounilorio precololoi
Mas atenao: nos nomes de variaveis voc nao pode usar acentos nem nenhum outro
tipo de caractere especial; o C so aceita os caracteres alfabticos de A a Z (minsculos e
maisculos), dgitos de 0 a 9 e o trao inferior (). Ha ainda a restriao de que o primeiro
caractere nao pode ser um nmero. Alm disso, como a linguagem sensvel diferena
entre maisculas e minsculas, a variavel vor diferente da variavel vAR (e portanto ambas
podem existir ao mesmo tempo).
Apos criar variaveis, o proximo passo aprender a usa-las. Uma das coisas mais simples
que podemos fazer guardar umvalor numa variavel: usamos umsinal de igual, escrevendo
sua esquerda o nome da variavel, e direita o valor que queremos guardar:
dio = ZZ
mes = 5
ono = Z00
Isso um comando de atribuio: ele joga o valor da direita na variavel da esquerda.
Veja que a posiao importante nesse caso; nao faria sentido escrever ZZ = dio. Voc
pode pensar no operador = como uma fecha que joga o valor da direita na variavel es-
querda: dio ZZ.
Os valores que voc joga nas variaveis nao precisam ser constantes; voc pode tambm
usar o contedo de uma outra variavel:
Entrada e sada de dados 11
inl o b
o = 5
b = o
Na verdade, voc pode atribuir a uma variavel o valor de uma expresso qualquer; por exem-
plo, expresses aritmticas envolvendo variaveis e constantes, como veremos a seguir.
A primeira atribuiao de uma variavel costuma ser chamada de inicializao. A inicia-
lizaao de uma variavel muito importante pois, quando voc declara uma variavel, o lugar
da memoria que ela ocupa apenas reservado para seu uso, sem atribuir nenhum valor pa-
drao, como 0 ou 23. Ali ha inicialmente um valor aleatorio, que foi deixado por algum
programa que ja usou aquele lugar da memoria. Por isso,
Voc nao pode tentar acessar o valor de uma variavel antes de lhe atribuir um valor.
Muitos dos problemas esquisitos que podem ocorrer em programas estao relacionados a
variaveis que nao foram inicializadas.
Devemos ter em mente que uma variavel apenas um lugar para guardar dados, assim
como uma caixa ou uma gaveta, desprovido de qualquer tcnica de magia ou adivinhaao.
O valor de uma variavel so muda quando voc o fzer explicitamente. Por exemplo:
inl o b c
o = 5
b = o
c = 5*o
o = 7
Se eu escrever isso, ao fnal desse trecho, o valor de a sera 7, mas o valor de b continuara
sendo 3 e o de c continuara sendo 23. As variaveis sao sempre independentes, nao ha como
criar vnculos do tipo c sempre vale 5a.
Tambmvale reforar que uma variavel umlugar, de certa maneira, temporrio. Quando
lhe atribumos umvalor, qualquer valor que nela houvesse anteriormente completamente
esquecido.
1.3 Entrada e sada de dados
Um programa de computador praticamente intil se nao tiver nenhum tipo de interaao
com o usuario. Por exemplo, quando aprendermos a fazer calculos, precisaremos aprender
tambm alguma maneira de mostrar os resultados ao usuario para que isso tenha alguma
utilidade. A forma mais simples de fazer isso de mostra-los na tela isso chamado de
sada de dados.
Ja fzemos um exemplo bem simples de sada de dados, que foi o programa inicial que
imprime uma string pr-fxada; mas, em geral, um programa deve ser capaz de imprimir
valores que mudam conforme a execuao do programa e/ou que dependem, por exemplo,
dos dados que o usuario forneceu. Com o que vimos, voc talvez fque tentado a escrever
isso para imprimir o valor de uma variavel inteira:
inl num
num = 17
prinlf(num}
12 Captulo 1. Princpios bsicos
Infelizmente, isso esta errado! No entanto, ainda possvel usar a funao printf para
esse proposito; so precisamos mudar um pouco a maneira de usa-la. Observe o seguinte
exemplo:
#inciude <sldio.h>
inl moin(}
{
inl dio mes ono
dio = ZZ
mes = 7
ono = Z00
prinlf("|oge dio xd/xd/xd.`n" dio mes ono}
relurn 0
]
Este exemplo mostra como podemos imprimir uma string que contenha o valor de uma
variavel: colocamos o codigo xd onde queremos que a variavel aparea; a variavel em si
especifcada direita da string. Neste exemplo, ja colocamos trs variaveis de uma vez (com
o codigo repetido emtrs lugares diferentes), e o computador as substitui na ordememque
elas aparecem apos a mensagem que voc escreveu. Ha uma ilustraao disso na fgura 1.1.
Figura 1.1: Ilustraao do funcionamento dos guardadores de lugar da funao printf.
O codigo xd um guardador de lugar para as variaveis que se seguem. Mais especif-
camente, o computador l a string, trechinho por trechinho; quando encontra um codigo
desses, ele pega a primeira variavel que encontrar depois da string; a cada novo codigo
encontrado, ela pega a proxima variavel da lista, at que todas se esgotem.
Na verdade, o codigo xd so serve se a variavel que voc quer imprimir um inteiro; se
for umnmero de ponto futuante, por exemplo, usamos outro codigo. Na verdade, existem
diversos desses codigos, que podemser utilizados para fazer algumas alteraes no formato
da sada do programa.
E bom acostumar-se com a nomenclatura: dentro dos parnteses que seguem o nome
da funao printf, cada um dos 4 itens separados por vrgulas um parmetro ou argu-
mento. Em outras palavras, quando imprimimos uma mensagem na tela com a funao
printf, o esqueleto da mensagem o primeiro parametro, e os demais parametros devem
corresponder s expresses que serao inseridas dentro do esqueleto (nos guardadores de
lugar).
Entrada e sada de dados 13
Nao menos importante que a sada a entrada de dados, ouseja, o ato de ler informaes
fornecidas pelo usuario. Novamente, uma das maneiras mais simples de fazer isso ler
dados que o usuario digita pelo teclado. Para isso, usamos uma funao parente da printf, a
funao scanf. O que ela faz passar o controle para o teclado, esperar que o usuario digite
algo e termine com a tecla Enter, interpretar o que o usuario escreveu e salvar em uma ou
mais variaveis.
Para cada dado que voc quiser ler, voc deve fornecer funao scanf duas informaes:
o tipo de dado (inteiro, nmero de ponto futuante, etc.) e o lugar onde o dado deve ser
armazenado (uma variavel). Um exemplo simples:
#inciude <sldio.h>
inl moin(}
{
inl idode
prinlf("uoi o suo idode? "}
sconf("xd" &idode}
relurn 0
]
Veja que aqui apareceu novamente o codigo xd, que signifca o mesmo que na funao
printf, mas funciona ao contrario: ele l uminteiro do teclado e guarda seu valor na proxima
variavel da lista de parametros. Voc tambmpoderia ler varios valores e guardar emvarias
variaveis num comando so:
#inciude <sldio.h>
inl moin(}
{
inl dio mes ono
prinlf("uoi o suo dolo de noscimenlo (no formolo dd mm oooo}? "}

sconf("xd xd xd" &dio &mes &ono}


relurn 0
]
Mas aqui ainda tem uma coisa esquisita: tem um E comercial (&) antes do nome de
cada variavel. Pra que isso serve: Ele uma maneira de dizer que estamos nos referindo ao
lugar em que esta a variavel (um lugar na memoria), e nao ao seu valor. Isso necessario
justamente para que a funao possa colocar o valor lido no lugar que queremos veja que
nao estamos atribuindo explicitamente nenhum valor s nossas variaveis. (Veremos como
isso funciona no Captulo ??, Ponteiros.) Voc nao pode deixar esse E comercial de lado; se
voc o omitir, o valor lido nao ira para a variavel que voc pediu, e seu programa podera
terminar com um erro do tipo Falha de segmentao.
14 Captulo 1. Princpios bsicos
1.4 Matemtica bsica
A palavra computador vem do latim computus, que signifca calculo, conta. Era jus-
tamente esse o objetivo do computador na sua primeira concepao: fazer contas. Entao,
nada mais razoavel que dedicar um tempo para aprender a fazer contas em C.
As operaes matematicas basicas nao sao nenhum mistrio em C. A linguagem pos-
sui os mesmos operadores matematicos com os quais estamos acostumados (com algumas
adaptaes que foram outrora necessarias), de acordo com a seguinte tabela:
Operador Signicado
+ adiao
- subtraao
* multiplicaao
/ divisao
x resto da divisao inteira
Os operadores em C funcionam da maneira usual para realizar uma operaao com
dois nmeros, basta colocar o operador entre eles. Esses nmeros podem ser tanto cons-
tantes numricas quanto variaveis (ou qualquer coisa que em C for considerada uma ex-
pressao). Formalmente, os nmeros ou as expresses s quais se aplica um operador sao
chamados de operandos.
Podemos tambm montar expresses aritmticas mais complexas, e nelas as operaes
seguem tambm a ordem usual: multiplicaes e divises` sao realizadas antes de adies
e subtraes, a menos que voc use parnteses para alterar a ordem. Observe que para esse
fmvoc no pode usar os colchetes e chaves; emcompensaao, voc pode usar varios nveis
de parnteses, como em 3 * (5 * (7 - 1}}.
O operador - (o sinal de menos) tambm pode ser usado para obter o valor de uma
variavel com o sinal trocado. Ele tambm usado como na matematica: -num corresponde
ao valor de num com o sinal trocado. Note que esse valor nao muda o valor da variavel,
apenas obtm uma copia dele com o sinal trocado. Para alterar o valor da variavel,
necessario dizer explicitamente que voc quer guardar o valor com sinal trocado de volta
na variavel.
Veja alguns exemplos de uso dos operadores aritmticos, com os resultados anotados
ao lado:
x = 14 / 7 /* Z */
y = 1Z x 7 /* 5 (reslo do diviso} */
x = 1Z + 5 * 7 /* 47 */
x = (1Z + 5} * 7 /* 119 */
x = ((1Z + 5} * 7 + Z} * 3 /* 363 */
fol = 1 * Z * 3 * 4 * 5 /* 1Z0 */
x = y + Z*z /* usondo os voiores oluoimenle
guordodos em oulros vorioveis
*/
Aqui est includo tambm o uso do operador x, j que o resto tambm resultado de uma operao de
diviso.
Matemtica bsica 13
x = -x /* lroco o sinoi de x e
guordo de voilo em x */
x = x + 1 /* oumenlo em umo unidode o voior
de x guordondo de voilo em x
*/
Note (pelos dois ltimos exemplos) que podemos usar a mesma variavel nos dois lados
da operaao de atribuiao. Isso nao tem nenhuma ambigidade para o computador; ele
sabe reconhecer que, no lado direito, voc esta fazendo algum calculo que envolve o valor
da variavel, e que o lado esquerdo representa o lugar onde voc quer guardar o resultado
desse calculo. O computador primeiro faz o calculo com o valor atual da variavel, e depois
guarda de volta na variavel.
Voc pode usar expresses aritmticas diretamente na funao printf: os parametros nao
precisam ser nomes de variaveis. Por exemplo:
inl x
prinlf("0igile um nmero inleiro. "}
sconf("xd" &x}
prinlf("0 dobro de xd xd.`n"
"0 quodrodo de xd xd.`n" x Z*x x x*x}
Na funao scanf, logico que o lugar de armazenamento tem de ser, obrigatoriamente,
uma variavel; nao faz sentido nenhumescrever algo como &(Z*x}, pois a expressao Z*x nao
representa um lugar bem defnido onde um valor pode ser guardado.
1.4.1 Contas cominteiros
Oexemplo a seguir converte uma temperatura (inteira) dada na escala Celsius para a escala
Fahrenheit. Para quemnao se lembra, a relaao entre dois valores T
C
(emCelsius) e T
F
(em
Fahrenheit) correspondentes mesma temperatura
T
F
32 =
9
5
T
C
.
#inciude <sldio.h>
inl moin(}
{
inl ceisius fohrenheil
prinlf("0igile o lemperoluro de hoge. "}
sconf("xd" &ceisius}
fohrenheil = 9 * ceisius / 5 + 3Z
prinlf("|oge eslo fozendo xd grous Fohrenheil!`n" fohrenheil}
relurn 0
]
16 Captulo 1. Princpios bsicos
Voc, que conhece a propriedade comutativa da multiplicaao, deve imaginar que a
conta principal do programa poderia ser escrita de varias maneiras:
fohrenheil = 9 * ceisius / 5 + 3Z
fohrenheil = ceisius * 9 / 5 + 3Z
fohrenheil = ceisius / 5 * 9 + 3Z
fohrenheil = 9 / 5 * ceisius + 3Z
Em teoria, isso estaria certo. Agora tente executar o programa com as quatro variaes,
testando algumas temperaturas diferentes. Voc deve reparar que as duas primeiras vari-
aes dao os valores mais proximos do valor correto, enquanto que a terceira da um erro
maior e a quarta da um erro realmente grosseiro. Por que isso ocorre: Para o computa-
dor, faz sentido que uma operaao entre inteiros deve dar um resultado inteiro; por isso,
quando voc divide dois inteiros, voc obtm apenas a parte inteira do resultado. Dessa
maneira, quando fazemos no programa a divisao por 3, obtemos a versao arredondada do
valor que esperaramos obter, por exemplo, numa calculadora.
Os resultados foram diferentemente incorretos porque os arredondamentos sao reali-
zados em diferentes etapas. Sabendo que o C calcula expresses matematicas sempre da
esquerda para a direita, podemos prever, por exemplo, que, na ltima variaao da nossa
conta, o primeiro calculo realizado 9 / 5, que da 1 (com resto 4). Assim, a temperatura
em graus Celsius simplesmente somada com 32, o que da um erro muito grosseiro. Na
terceira conta, o problema na divisao da temperatura por 3 qualquer temperatura que
nao seja mltipla de 3 sofrera um arredondamento para baixo, de modo que 29

C seriam
convertidos para 77

F, que na verdade correspondem a 23

C.
Por isso, neste caso, a melhor opao a usar a primeira ou a segunda, ja que o arre-
dondamento na divisao feito sobre um nmero maior e ocorre numa das ltimas etapas,
de modo que o erro nao se propaga mais para outras contas ou melhor, ele so se pro-
paga para a adiao, na qual nao ha perigo de haver outros erros, pois a adiao de inteiros
sempre exata.
Mais adiante veremos como trabalhar com nmeros de ponto futuante, o que resol-
vera o problema das divises mas tambm traz alguns outros problemas, como tambm
vamos estudar.
1.5 Boas maneiras emC
1.5.1 Comentrios
Talvez voc tenha reparado que num certo ponto usei os smbolos /* e */ para anotar
resultados de contas. Eles tambm sao parte da linguagem; eles sao usados para fazer co-
mentrios no meio do codigo. Voc pode escrever o que quiser entre esses dois smbolos,
pois tudo que ali estiver sera ignorado pelo compilador. A nica coisa que voc nao pode
fazer colocar um comentario dentro do outro:
/* um comenlorio /* islo um erro */ denlro do oulro */
Existe tambm uma outra maneira de fazer comentarios, que foi copiada da linguagem
C++: usam-se duas barras //, mas o comentario so vale at o fnal da linha. Essa segunda
Boas maneiras em C 17
maneira so foi fxada no padrao mais recente da linguagem(C99); se seu compilador estiver
operando no modo ANSI, ele nao gostara desse segundo tipo de comentario.
/* Comenlorio esliio C
* ===================
* Comeo com borro e oslerisco
* lermino com oslerisco e borro.
*/
// Comenlorio esliio C++
// =====================
// Comeo com duos borros
// voie openos ol o finoi do iinho.
Tenho algumas palavrinhas a proferir sobre o uso dos comentarios, mas postergarei
esse assunto para o proximo captulo, quando tivermos visto um pouco mais de teoria.
1.5.2 Estrutura e estilo
Em C, praticamente todos os espaos, tabulaes e quebras de linha suprfuos de agora
em diante, sempre que eu falar de espaos, entenda que tabulaes e quebras de linha
tambm estao includas sao ignorados pelo compilador (exceto, claro, em uma string).
Os programas abaixo sao, perante as regras da linguagem, absolutamente equivalentes:
#inciude <sldio.h>
inl moin(}{prinlf("0io moroviihoso mundo do progromoo!`n"}relurn
0]
#inciude <sldio.h>
inl moin(}
{
prinlf("0io moroviihoso mundo do progromoo!`n"}
relurn 0
]
#inciude <sldio.h>
inl moin(
}{
prinlf(
"0io moroviihoso mundo do progromoo!`n"
Em ingls, h a expresso whitespace, que indica qualquer sequncia de espaos, tabulaes e quebras
de linha. No conheo nenhuma expresso equivalente em portugus, ento teremos de conviver com essa
ambiguidade.
18 Captulo 1. Princpios bsicos
}
relurn 0
]
Mas qual deles mais facil de ler: O segundo, nao : Embora o estilo especfco que
voc ira usar possa variar de acordo com sua preferncia, ha alguns princpios gerais que
voc deve procurar seguir ao escrever um programa:
Escreva uma instruao (um comando) por linha. Quando for necessario, divida a
instruao em mais de uma linha para melhorar a legibilidade (voc pode quebrar a
linha em qualquer lugar onde poderia colocar um espao). Em algumas situaes
tambm pode ser aceitavel juntar duas instrues em uma linha so, mas nao abuse
disso.
Sempre que voc tiver um bloco de comandos (algo delimitado por chaves), o con-
tedo do bloco deve estar mais afastado da margem que o exterior do bloco. E isso
que chamamos de indentao; ela torna muito mais facil a visualizaao da estrutura
do codigo e de onde os blocos se encaixam. Voc tambm deve deixar com o mesmo
recuo as linhas que pertencem ao mesmo bloco, senao acaba aparecendo uma falsa
noao de hierarquia (ou uma verdadeira noao de baguna).
2
Controle de uxo
Com o pouco que aprendemos at agora, so possvel construir programas lineares, ou
seja, que so sabem fazer uma determinada sequncia de operaes, quaisquer que sejam os
dados fornecidos (ou outras condies). Na maioria dos programas, isso nao sufciente;
preciso que o programa saiba fazer decises. Acapacidade do computador de fazer decises
conhecida como controle de uxo.
2.1 Desvios condicionais: if
Uma das estruturas mais simples de controle de fuxo a construao if (se), que verifca se
uma certa condiao satisfeita, e executa um conjunto de comandos em caso afrmativo.
Em C, isso se escreve assim:
if (condio}
{
/* comondos oqui */
]
O funcionamento dessa estrutura simples. Quando a execuao do programa atinge o
if, a condiao avaliada, e ha duas sadas possveis:
se a condiao for satisfeita, o programa executa os comandos colocados dentro do
par de chaves, e depois continua a executar os comandos fora do par de chaves;
se a condiao nao for satisfeita, o programa simplesmente pula o que esta entre chaves
e continua a executar os comandos apos as chaves.
Para que isso tenha alguma utilidade, precisamos aprender como sao escritas as tais
condies. Dentre as condies mais simples que podemos escrever estao as comparaes
entre nmeros. Elas sao escritas com os operadores a seguir, denominados operadores re-
lacionais:
19
20 Captulo 2. Controle de uxo
Tabela 2.1: Operadores relacionais, para comparaao entre nmeros.
Operador Signicado
< menor que
<= menor que ou igual a
> maior que
>= maior que ou igual a
== igual a (cuidado! sao DOIS iguais!)
!= diferente de
Do mesmo jeito que ocorre para os operadores matematicos, nao ha nenhum grande
mistrio no uso dos operadores relacionais. Por exemplo, a condiao a menor do que b
escrita como o < b; a igualdade entre a e b pode ser verifcada pela expressao o == b, com
DOIS iguais. Porm, uma condiao composta como x esta entre a e b (matematicamente,
a < x < b) no pode ser escrita como o < x < b; a expressao at sintaticamente valida,
mas seu resultado completamente diferente do que voc esperaria. O jeito certo de fazer
essa comparaao sera esclarecido daqui a pouco.
if (o == b}
{
prinlf("0s nmeros so iguois!`n"}
]
Existe uma abreviaao muito til que pode ser usada coma estrutura if (e tambmpode
ser usada com outros tipos de estruturas que veremos a seguir). Quando houver apenas
um comando dentro do bloco delimitado pelas chaves, voc pode omitir as chaves. Assim,
poderamos escrever o trecho acima de forma um pouco mais compacta:
if (o == b}
prinlf("0s nmeros so iguois!`n"}
Reforo que voc so pode fazer essa abreviaao quando dentro do bloco houver apenas
um comando. Se voc fzer isso quando houver mais de um comando, mesmo que estejam
todos na mesma linha ou coma mesma indentaao, todos os comandos a partir do segundo
serao dados como externos ao bloco. Por exemplo, observe o codigo a seguir:
if (o < 0}
prinlf("0 voior de A no pode ser negolivo!`n"}
o = 0 /* indenloo engonoso! */
A terceira linha foi indentada de modo que parea fazer parte do if; na verdade ela esta
fora dele, e sera executada em qualquer caso, seja a negativo ou nao. Esse codigo equiva-
lente a este outro a seguir; este sim da a correta impressao da organizaao do programa.
if (o < b} {
prinlf("A menor que B!`n"}
]
o = b
Desvios condicionais: if 21
Outro erro comum confundir o operador de comparaao de igualdade, ==, com o
operador de atribuiao, =. O codigo a seguir gramaticalmente correto, mas nao faz o
que ele aparenta fazer:
if (o = b}
prinlf("A iguoi o B!`n"}
O que esta acontecendo aqui: Copiamos o valor de b para a variavel a; o comando de
atribuiao o = b por si mesmo uma expressao, cujo valor igual ao valor que foi atribudo.
Esse valor , entao, interpretado como se fosse o valor de uma condiao cobriremos
isso mais adiante, na seao 2.6; mas veja que, independentemente de quem ele , esse valor
condicional nao tema ver coma relaao entre a e b, pois ele so depende do valor da variavel
b! Portanto, a conclusao de que a igual a b a partir desse if esta errada.
Esse um dos varios erros que podem nao ser detectados pelo compilador, pois o co-
digo, ainda que semanticamente incorreto, sintaticamente valido; por causa disso, voc
conseguira compilar e executar seu programa, mas alguma parte dele exibira algum com-
portamento inesperado. Portanto, muito cuidado ao fazer comparaes de igualdade.
2.1.1 Condies alternativas: else
Suponha que voc quer ler um nmero do teclado e imprimir uma mensagem dizendo se o
nmero par ou mpar. Para verifcar se par, podemos simplesmente ver se ele divsivel
por 2, ou seja, ver se o resto da divisao por 2 zero. Isso poderia ser feito assim:
#inciude <sldio.h>
inl moin(}
{
inl num
prinlf("0igile um nmero. "}
sconf("xd" &num}
if (num x Z == 0}
prinlf("0 nmero por.`n"}
if (num x Z != 0}
prinlf("0 nmero mpor.`n"}
relurn 0
]
Mas pense bem: sendo o nmero digitado inteiro, ou ele par ou mpar; entao, se ja
verifcamos que ele nao par, nao precisamos fazer outra conta para ver que ele mpar.
Esse tipo de padrao associar um procedimento ao caso em que a condiao satisfeita, e
outro procedimento ao caso emque ela falsa extremamente comumemprogramaao.
Por isso, a cada condiao voc pode associar tambm um outro bloco de comandos, a ser
executado caso ela seja falsa. Para isso, voc usa a palavra-chave else (que signifca seno),
da seguinte maneira:
if (condio} {
22 Captulo 2. Controle de uxo
/* SE o condio for solisfeilo execulo esles comondos */
]
eise {
/* SEWA0 execulo esles oulros comondos */
]
(Continua existindo a abreviaao do um-comando: se houver apenas um comando dentro
do else, voc pode tirar as chaves.)
Assim, nosso programa acima pode ser reescrito da seguinte maneira, que tambm
deixa bem mais claro o fato de que as duas situaes sao opostas, complementares:
#inciude <sldio.h>
inl moin(}
{
inl num
prinlf("0igile um nmero. "}
sconf("xd" &num}
if (num x Z == 0}
prinlf("0 nmero por.`n"}
eise
prinlf("0 nmero mpor.`n"}
relurn 0
]
2.2 Repetindo operaes: laos (loops)
O computador til para automatizar tarefas; por exemplo, tarefas repetitivas que seriam
muito trabalhosas se realizadas de outra maneira. Por isso, interessante criar, numa lin-
guagem de programaao, uma estrutura que permita a execuao repetida de uma tarefa ou
de varias tarefas parecidas.
Em C, existem trs tipos de estruturas de repetiao, mas o princpio de funcionamento
de todas o mesmo: repetir um certo conjunto de comandos at que uma certa condiao
de parada seja satisfeita. Esse tipo de estrutura costuma ser chamado de lao ou, equiva-
lentemente em ingls, loop.
O tipo de lao mais simples o while (enquanto). O funcionamento dele simples
uma condiao avaliada; se ela for falsa, continua a execuao do resto do programa; se
ela for verdadeira, um bloco de comandos executado, e volta-se ao comeo do processo
(avaliando a condiao novamente). Em outras palavras, o bloco de comandos executado
enquanto a condio for satisfeita. Isso representado no esquema da fgura 2.1.
A sintaxe desse lao, bem parecida com a do if, a seguinte:
Whiie (condio}
{
/* comondos oqui */
]
Repetindo operaes: laos (loops) 23
Cada execuao do bloco de comandos chamada de iterao nao so para o while,
mas tambm para os outros laos que veremos posteriormente. Assim, uma observaao
importante a fazer que, se a condiao ja for falsa quando o while for atingido, nenhuma
iteraao sera executada o bloco de comandos sera ignorado.
Figura 2.1: Esquema de execuao da estrutura while.
Vamos criar um exemplo bem basico: um programa que imprime na tela, em ordem
crescente, os nmeros inteiros de 1 a 10. Como fazer isso usando while:
Uma idia usar uma variavel que tem o valor inicial 1, e aumentar esse valor de 1
em 1 at que chegue a 10 (aqui entra o while!), imprimindo o valor atual antes de realizar
cada incremento. Ficou confuso: Vamos ver um esquema disso, usando no lugar do C um
esboo em uma linguagem de programaao simplifcada e genrica:
num1
repita enquanto num10:
imprima num
numnum+ 1
Para ter certeza de que fcou tudo claro, vamos simular a execuao desse pseudocodigo.
Veja que inicialmente a variavel num vale 1. Como 1 10, entramos no bloco do repita;
assim, imprimimos o nmero 1 e a variavel passa a valer 2. Voltamos para a avaliaao da
condiao e obtemos novamente um resultado positivo, 2 10; o programa imprime o
nmero 2 e a variavel recebe o valor 3. Isso continua at que o valor de num seja 9; esse
nmero impresso e o valor 10 colocado na variavel. A ocorre a ltima iteraao do lao:
imprimimos o nmero 10, e num passa a valer 11. Com isso, a condiao do lao deixa de
ser verdadeira, pois ela sera avaliada como 11 10.
Pseudocdigo o nome dado a esse tipo de linguagem de programaao simplifcada
e genrica, e geralmente usado para enfatizar o algoritmo de uma certa operaao, sem
depender das peculiaridades das diversas linguagens.
Algoritmo, por sua vez, a seqncia de instrues usada para completar uma certa
tarefa computacional. Essa defniao muito parecida com a de programa; de fato, um
programa pode ser considerado uma grande e complexa associaao de algoritmos para
as diversas tarefas que ele temde realizar. Emgeral, o algoritmo para realizar uma tarefa
nao nico; pode haver varios, alguns mais efcientes ou praticos que outros.
Agora vamos passar isso de volta para o C. A traduao quase literal, excluindo-se o
cabealho e o rodap do programa:
24 Captulo 2. Controle de uxo
#inciude <sldio.h>
inl moin(}
{
inl num
num = 1
Whiie (num <= 10} {
prinlf("xd`n" num}
num = num + 1
]
relurn 0
]
Esse tipo de codigo muito importante em programaao, e ilustra um padrao conhe-
cido como contador uma variavel usada em um lao, cujo valor varia em uma unidade
a cada iteraao. Esse padrao usado em diversas situaes que envolvam a repetiao de
varias operaes iguais ou bem parecidas, que possam ser diferenciadas apenas pelo valor
do contador.
Exivccio i.1 Faa um programa que l um nmero n do teclado e, em seguida, imprime
todos os nmeros inteiros de 1 a n junto com seus quadrados.
Vamos construir umexemplo mais sofsticado: calcular o fatorial de umnmero inteiro
nao-negativo fornecido pelo usuario.
Denio. Dado um nmero natural n, defnimos como fatorial de n, e denotamos
n!, o produto de todos os nmeros naturais menores que ou iguais a n. Observe que
1! = 1. Defnimos tambm 0! = 1.
Por exemplo, 5! = 1 2 3 4 5 = 120.
Como podemos usar o while nessa tarefa: Suponhamos primeiro que n maior que
1. Nessa situaao, devemos fazer varias multiplicaes, cada vez com fatores diferentes
afnal, a multiplicaao com n fatores se defne indutivamente a partir da multiplicaao
elementar de dois fatores. Se realizarmos as operaes da esquerda pela direita, faremos as
seguintes contas:
1 2
(1 2) 3
.
.
.
(1 2 (n 1)) n
Tendo isso emvista, podemos montar umalgoritmo no qual, emcada passo, o resultado
do passo anterior (a) multiplicado pelo valor atual de um contador (b), que vai de 2 at n:
a 1
b 2
Repetindo operaes: laos (loops) 23
repita enquanto b n:
a a * b
b b + 1
Ao fnal do algoritmo, a variavel a contera o fatorial do nmero n, conforme desejava-
mos. Agora pensemos nos casos n = 1 e n = 0. Se n = 1, a condiao do lao (2 1) sera,
ja de incio, falsa, e o valor inicial de a garantira o resultado correto, 1! = 1. Caso n = 0, a
condiao tambm sera falsa de incio, e assim tambm teremos o resultado 0! = 1. Agora
vamos escrever tudo isso em C:
/* Coicuio o folorioi de um nmero inleiro no-negolivo. */
#inciude <sldio.h>
inl moin(}
{
inl n o b
prinlf("0igile um nmero. "}
sconf("xd" &n}
o = 1
b = Z
Whiie (b <= n} {
o = o * b
b = b + 1
]
prinlf("xd! = xd`n" n o}
relurn 0
]
Exivccio i.i Elabore um algoritmo (e construa um programa) que calcule o fatorial de
um nmero n multiplicando os fatores da direita para esquerda (ou seja, comeando de n e
indo at 1). Quais problemas voc encontra?
Exivccio i. Faa um programa que l do teclado dois nmeros, a > 0 e b 0, e
imprime o valor da potncia a
b
.
Vamos agora ver outro tipo de programa que vale a pena ser ressaltado. Buscaremos
resolver o seguinte problema: ler do teclado uma sequncia de nmeros, cujo tamanho
nao inicialmente conhecido, mas que sabemos ser terminada por um zero (ou seja, os
demais nmeros da sequncia sao todos nao-nulos), e somar os nmeros lidos. Que tipo
de algoritmo podemos usar para atacar esse problema:
Obviamente, se nao sabemos a priori quantos elementos a sequncia tem, nao podemos
armazenar todos os elementos para depois somar todos; precisamos acumular as somas
parciais a cada nmero lido. Utilizaremos para isso uma variavel chamada soma, que devera
ser inicializada com o valor zero, e qual se somara cada nmero lido.
A idia central ler nmeros do teclado indefnidamente at que o nmero lido seja
zero. Sempre que o nmero for diferente de zero, devemos inclu-lo na soma e continuar
26 Captulo 2. Controle de uxo
lendo nmeros. Declarando uma variavel numpara armazenar os nmeros lidos, podemos
proceder assim:
Whiie (num != 0} {
somo = somo + num
prinlf("0igile oulro nmero ou 0 poro lerminor. "}
sconf("xd" &num}
]
Mas preciso prestar atenao a um detalhe: num devera ser lido pela primeira vez antes
desse lao caso contrario, a variavel nao estaria inicializada!. Feitas essas observaes,
nosso programa fcara com essa cara:
#inciude <sldio.h>
inl moin(}
{
inl num somo
somo = 0
prinlf("0igile um nmero ou 0 poro lerminor. "}
sconf("xd" &num}
Whiie (num != 0} {
somo = somo + num
prinlf("0igile um nmero ou 0 poro lerminor. "}
sconf("xd" &num}
]
prinlf("Somo = xd`n" somo}
relurn 0
]
Vamos agora adicionar mais uma tarefa ao nosso problema: encontrar o elemento ma-
ximo da sequncia digitada pelo usuario. Para ver como faremos isso, imagine que voc
dispe de papel e caneta, e que algum esta ditando para voc a tal sequncia de nmeros.
Como voc realizaria essa tarefa: Voc anota o primeiro nmero e, caso oua posterior-
mente um nmero maior que ele, anota-o logo abaixo; se ouvir outro nmero ainda maior,
anota-o abaixo do anterior; e assim por diante nao necessario tomar nota de todos os
nmeros. No fnal, o ltimo nmero anotado sera o maior nmero da sequncia.
Um computador poderia fazer isso exatamente da mesma maneira. Em vez de anotar
os nmeros, ele os guardaria em variaveis; alm disso, ele nao precisaria manter as ano-
taes anteriores; basta substituir o valor da variavel. Assim, nosso programa completo
poderia ser escrito assim:
#inciude <sldio.h>
Contadores e laos for 27
inl moin(}
{
inl somo /* somos porciois */
num /* ilimo nmero iido */
mox /* condidolos o moximo */
somo = 0
prinlf("0igile um nmero ou 0 poro lerminor. "}
sconf("xd" &num}
mox = num
Whiie (num != 0} {
somo = somo + num
if (num > mox} /* poro procuror o moximo */
mox = num
prinlf("0igile um nmero ou 0 poro lerminor. "}
sconf("xd" &num} /* i o proximo nmero */
]
prinlf("Somo = xd`n" somo}
prinlf("Moximo = xd`n" mox}
relurn 0
]
2.3 Contadores e laos for
Se voc olhar com atenao para os exemplos do fatorial e do programa que conta de 1 a
10, que utilizam uma variavel-contador, vera que os dois programas tm estruturas bem
parecidas: primeiro era defnido o valor inicial do contador; depois, escrevia-se um lao
que defnia o valor maximo desse contador; no fnal desse lao, o valor do contador era
aumentado. Esquematicamente, tnhamos
inicializao do contador
Whiie (controle do valor mximo}
{
comondos
incremento do contador
]
Esse tipo de situaao uma das mais frequentes aplicaes dos laos; por isso, essa
estrutura comum foi abreviada em outro tipo de lao: a estrutura for. Com ela, a escrita
desses laos fca mais compacta e de mais facil visualizaao:
for (inicializao controle incremento}
{
comondos
28 Captulo 2. Controle de uxo
]
Com isso, podemos reescrever os exemplos anteriores da seguinte maneira:
/* Tmprime lodos os nmeros inleiros de 1 ol 10
* gunlo com seus quodrodos */
#inciude <sldio.h>
inl moin(}
{
inl num
for (num = 1 num <= 10 num = num + 1}
prinlf("xd`n" num}
relurn 0
]
/* Coicuio o folorioi de um nmero inleiro no-negolivo. */
#inciude <sldio.h>
inl moin(}
{
inl n o b
prinlf("0igile um nmero. "}
sconf("xd" &n}
o = 1
for (b = Z b <= n b = b + 1}
o = o * b
prinlf("xd! = xd`n" n o}
relurn 0
]
O funcionamento da estrutura for pode ser mais detalhado como a seguir (voc pode
ver isso de maneira mais esquematica na fgura 2.2):
1. A variavel do contador inicializada com um valor especifcado; isso ocorre apenas
uma vez, antes do incio do lao. A inicializaao costuma ser uma instruao do tipo
i = 1.
2. Acondiao de controle especifca que condiao a variavel do contador deve satisfazer
para que os comandos sejam executados; em outras palavras, quando essa condiao
deixar de valer, o lao ira parar de se repetir. Por exemplo, uma condiao do tipo
i <= n diz que o lao sera interrompido assimque o contador i fcar maior que n. Se
voc quiser que seucontador decresa at umvalor mnimo, voc usara uma condiao
do tipo i >= n.
3. Sempre que a condiao de controle for satisfeita, executada uma iteraao do bloco
de comandos fornecido, de cabo a rabo. Mesmo que a condiao de controle deixe
de ser satisfeita no meio do bloco, o lao so sera abandonado quando chegarmos ao
fnal do bloco. Essa ltima observaao vale tanto para o for quanto para o while.
Contadores e laos for 29
Figura 2.2: Esquema de execuao da estrutura for.
4. Ao fnal de cada iteraao, executa-se a instruao de incremento ela pode ser qual-
quer instruao que altere o valor do contador, como i = i + 1 ou i = i - Z. Ape-
sar do nome incremento, voc pode variar o valor do contador da maneira que de-
sejar diminuindo, por exemplo.
Apesar de termos vinculado nossa discussao do for a uma variavel-contador, o for
uma estrutura bastante fexvel. No entanto, isso nao signifca que voc deve sair usando o
for a torto e a direito e esquecer o while; deve existir uma boa razao para existirem os dois
tipos de estrutura. E essa razao semantica: voc deve usar o for quando seu lao tiver um
contador ou algo com funcionamento semelhante; nos outros casos, voc deve, em geral,
ater-se ao while.
2.3.1 Abreviaes
Atribuies que envolvemo valor original da variavel que esta sendo alterada (como a nossa
ja conhecida i = i + 1) sao muito comuns, ja que sao usadas o tempo todo nos laos com
contadores; por isso, foram criadas algumas abreviaes bastante teis. Quando repre-
senta um dos cinco operadores aritmticos, podemos abreviar o = o b para o = b.
Assim, temos:
Abreviao Signicado
vor += expr vor = vor + expr
vor -= expr vor = vor - expr
vor *= expr vor = vor * expr
vor /= expr vor = vor / expr
vor x= expr vor = vor x expr
Existe ainda outro tipo de abreviaao quia o mais usado em C que serve para
aumentar ou diminuir emuma unidade o valor de uma variavel. Sao os operadores ++ e --,
que podem ser usados tanto antes quanto depois do nome das variaveis a serem alteradas.
Ou seja, para aumentar o valor de uma variavel, podemos escrever
vor++
30 Captulo 2. Controle de uxo
++vor /* equivoiem o. vor = vor + 1 */
e, para diminuir, usamos
vor--
--vor /* equivoiem o. vor = vor - 1 */
No contexto atual, o mais comum usar os operadores depois dos nomes das variaveis.
Porm, mais tarde veremos que existe uma diferena importante entre as duas possibilida-
des de posiao.
Dessa maneira, a cara de um lao com contador passa a fcar parecida com isso (aqui
foi reescrito o lao do ltimo exemplo):
for (b = Z b <= n b++}
o *= b
Exerccios
2.4. Crie um programa que l um nmero natural n do teclado e imprime todos os divi-
sores desse nmero. Ao fnal, imprima tambm a soma dos divisores encontrados.
2.3. Aproveite o programa do exerccio anterior para verifcar se um nmero n dado
primo.
2.6. Crie um programa que calcula o fatorial duplo de um nmero natural n (lido do
teclado), que defnido como a seguir:
n!! =
{
n(n 2) 4 2, se n par;
n(n 2) 3 1, se n mpar.
2.7. Faa umprograma que l dois nmeros inteiros a e b, sendo a > 0 e b 0, e calcula
a potncia a
b
. Veja se seu algoritmo funciona para o caso b = 0; tente elabora-lo de
modo que nao seja preciso um teste especial para esse caso.
2.8. Faa umprograma que l uma sequncia de nmeros naturais do teclado (terminada
por zero) e imprime a quantidade de nmeros pares e mpares da sequncia. Imprima
tambmo maior e o menor mpar e o maior e o menor par encontrados na sequncia.
2.9. Variao sobre o mesmo tema: Mesmo problema que o anterior; mas agora o tamanho
n da sequncia conhecido previamente. Antes de tudo voc deve ler n, e depois ler
os n nmeros.
2.10. Faa um programa que l do teclado uma sequncia de nmeros inteiros nao-nulos
(terminada por zero) e os soma e subtrai alternadamente por exemplo, para a
sequncia [5, 7, 1, 4, 9], seu programa devera calcular 5 7 + 1 (4) + 9.
2.11. [Problema] Crie um programa que recebe um nmero n e a seguir l uma sequncia
de nalgarismos de 0 a 9; voc devera calcular e imprimir o nmero que corresponde
queles algarismos, na mesma ordem (nao vale imprimir dgito por dgito! O obje-
tivo construir o nmero a partir da sua sequncia de dgitos). Por exemplo, para a
sequncia [5, 4, 2, 0, 7, 0], seu programa devera imprimir o nmero 342070.
Condies compostas 31
2.12. [Idem] Crie um programa que l um nmero natural n e imprime a soma de seus
dgitos (na base decimal).
2.4 Condies compostas
Em muitos casos, voc precisara verifcar condies mais complexas do que as simples ex-
presses com operadores relacionais, como x > 10 ou cod == 15. Havera muitas vezes
em que voc precisara fazer alguma coisa somente quando duas (ou mais) condies forem
satisfeitas ao mesmo tempo; em outras, voc precisara fazer algo quando qualquer uma,
dentre varias condies, for satisfeita.
Por exemplo, suponha que voc queira que o usuario digite nmeros entre 0 e 10 e que
voc precise verifcar se os nmeros realmente estao entre 0 e 10. Ja falamos anteriormente
que, em C, isso no se escreve como 0 <= x <= 10 esse codigo valido, mas nao faz o
que gostaramos. Mas podemos contornar isso usando ifs encadeados, pois (0 x 10)
equivalente a ((x 0) e (x 10)):
if (x >= 0} {
if (x <= 10}
prinlf("0k!`n"}
eise
prinlf("0 nmero preciso eslor enlre 0 e 10!`n"}
]
eise
prinlf("0 nmero preciso eslor enlre 0 e 10!`n"}
No entanto, esse mtodo tem duas desvantagens: (1) ele nao deixa tao evidente o fato
de que queremos que as duas condies sejam satisfeitas ao mesmo tempo; e (2) tivemos
mais trabalho para verifcar quando as condies no sao satisfeitas foi necessario usar
dois blocos else, e acabamos usando o mesmo codigo nos dois.
Quando precisamos de critrios compostos como esse dentro de um lao, a situaao
torna-se ainda mais complicada; com o que aprendemos at agora, isso simplesmente
impossvel de se implementar mesmo com outros recursos da linguagem, a soluao nao
seria nem um pouco pratica ou clara. Por isso, precisamos introduzir dois operadores que
permitem a criaao de condies compostas:
&& e 11 ou
O operador &&, chamado de E lgico, serve para verifcar se duas condies sao satis-
feitas simultaneamente. O operador 11, o OU lgico, verifca se, dentre duas condies,
pelo menos uma satisfeita. (Os nomes desses operadores serao grafados em maisculas
para evitar confuses no texto.)
32 Captulo 2. Controle de uxo
Note que, coloquialmente, quando usamos a palavra ou para unir duas oraes, geral-
mente imaginamos que apenas uma delas verdadeira (por exemplo, Independncia
ou morte cada uma das possibilidades exclui o acontecimento da outra). Nesse caso,
dizemos que trata-se de um ou exclusivo. Em outras palavras, esse ou signifca que,
dentre as duas sentenas, uma e apenas uma verdadeira.
Ja em computaao, quando dizemos A ou B, geralmente queremos dizer que, das duas
sentenas (condies), pelo menos uma verdadeira. Esse conhecido como ou inclu-
sivo. Daqui para a frente, a menos que indiquemos o contrario, usaremos sempre o ou
inclusivo.
Com esses operadores, podemos reescrever aquele exemplo assim:
if (x >= 0 && x <= 10}
prinlf("0k!`n"}
eise
prinlf("0 nmero preciso eslor enlre 0 e 10!`n"}
Bem mais claro, nao: Veja que tambm podemos inverter as coisas para escrever nosso
exemplo usando um operador OU se um nmero nao satisfaz a condiao de estar entre
0 e 10, entao necessariamente ele ou menor que zero ou maior que 10:
if (x < 0 11 x > 10}
prinlf("0 nmero preciso eslor enlre 0 e 10!`n"}
eise
prinlf("0k!`n"}
2.5 Repeties encaixadas
Uma grande classe de problemas computacionais comuns requer, para sua soluao, o uso
de repeties encaixadas. Isso, conceitualmente, nao nenhuma novidade; trata-se sim-
plesmente de colocar um lao dentro do outro: para cada iteraao do lao externo, o lao
interno sera executado tantas vezes quanto for necessario para que ele termine.
Podemos encaixar os laos da maneira que quisermos, ou seja, qualquer mistura de for
e while permitida. O nico cuidado que devemos ter o de nao misturar as variaveis
de controle dos varios laos por exemplo, se encaixamos dois laos for, devemos dar
nomes diferentes aos contadores (por exemplo, i para o lao externo e j para o interno).
No entanto, os contadores nao precisam ser completamente independentes: os limites dos
contadores dos laos internos podem depender da posiao atual no lao principal.
Vamos estudar um exemplo. Veremos um programa que encontra os primeiros N n-
meros primos, onde N um nmero dado. Para isso, passamos por todos os nmeros na-
turais (enquanto nao tivermos selecionado os N primeiros primos) e verifcamos se cada
um deles primo. Nao difcil testar se um nmero n primo: devemos verifcar se ele
tem um divisor nao-trivial, isto , um que nao seja igual a 1 ou n. Assim, verifcamos en-
tre todos os candidatos (a princpio, {2, 3, . . . , n 1}, mas essa lista ainda pode ser bem
refnada) se ha algum divisor de n. Se nao acharmos nenhum, porque n primo.
Agora vejamos como escrever a soluao para nosso problema. Se voc fez o exerccio 2.3
da pagina 30, ja tem o problema quase inteiro resolvido. (Se nao fez, volte e tente resolver!)
Vamos ver uma possvel implementaao da verifcaao de primalidade de um nmero:
Repeties encaixadas 33
inl divisores /* conlo o nmero de divisores */
k /* condidolo o divisor */
n /* nmero o ser leslodo */
divisores = 0
/* leslo lodos os condidolos */
for (k = Z k < n k++}
if (n x k == 0}
divisores++
if (divisores == 0} {
/* no ochomos nenhum divisor denlre os possveis
condidolos enlo primo */
]
Essa uma das implementaes mais inefcientes possveis, pois varios dos candidatos
da lista {2, 3, . . . , n 1} podem ser sumariamente descartados. Por exemplo, nenhum
nmero maior que
n
2
pode ser divisor de n, pois isso signifcaria que n tambm tem um
divisor entre 1 e 2. Ainda mais, se so precisamos verifcar se n primo (e nao achar todos
os seus divisores), basta verifcar os possveis divisores d

n (pois, se d for realmente
divisor, n/d

ntambmsera divisor). Mas, no momento, para o proposito atual, vamos


nos contentar com a maneira inefciente.
Agora tudo o que precisamos fazer usar esse codigo varias vezes (usando um lao)
para encontrar N nmeros primos:
inl W /* quonlidode de primos o procuror */
i /* primos go enconlrodos */
n /* condidolo oluoi */
divisores /* conlo o nmero de divisores */
for (n = Z i = 0 i < W n++} {
divisores = 0
/* leslo lodos os condidolos o divisor */
for (k = Z k < n k++}
if (n x k == 0}
divisores++
if (divisores == 0} {
/* no ochomos nenhum divisor denlre os possveis
condidolos enlo primo */
prinlf("xd`n" n}
i++
]
]
Observe que cada iteraao do lao for externo verifca se um nmero n primo, e so
aumenta o contador i em caso afrmativo. Veja tambm que o limite do contador k, no for
34 Captulo 2. Controle de uxo
interno, depende do contador n do lao externo.
Mais um detalhe que foi usado no programa mas ainda nao havia sido mencionado:
na inicializaao e no incremento de um lao for, possvel escrever comandos compostos.
Podemos separar varias atribuies por vrgulas (lembre-se que o ponto-e-vrgula separa a
inicializaao da condiao e esta do incremento), como foi feito no comando de inicializaao
do lao externo:
for (n = Z i = 0 i < W n++} {
Da mesma maneira, poderamos criar um incremento composto, como i++ g++.
2.6 Variveis booleanas
As estruturas if, while e for dependem da avaliaao de expresses condicionais para exe-
cutarem seu trabalho. Para que essas expresses realmente faam sentido como condies,
elas so podem assumir dois valores: verdadeiro ou falso (que indicam se o bloco correspon-
dente sera ou nao executado). Variaveis que so indicam esses dois estados sao chamadas
booleanas ou lgicas.
Em C, nao ha nenhum tipo primitivo especifcamente para lidar com variaveis boole-
anas; por isso, convenciona-se que qualquer valor diferente de zero interpretado como
verdadeiro, e o zero interpretado como falso. Ou seja, qualquer variavel ou expressao,
independente de seu tipo, pode ter um valor booleano que ou verdadeiro ou falso.
Os operadores que devolvem um valor logico usam os valores 0 e 1 (alm de serem a
escolha mais simples, esses dois valores sao sempre representaveis em qualquer tipo num-
rico) para representar os resultados falso e verdadeiro, respectivamente; assim, quando
fazemos alguma comparaao do tipo o < 10 ou x == y, o valor da expressao comparativa
0 ou 1. No entanto, em geral nao devemos nos preocupar com essa representaao, pois
essas expresses ja tm um valor logico bem defnido.
A partir desse conhecimento, podemos analisar o que acontece em algumas das situa-
es capciosas que mencionamos anteriormente. Primeiro temos as comparaes compos-
tas do tipo o < b < c; quando escrevemos em C exatamente dessa maneira, o que ocorre
que a expressao avaliada da esquerda para a direita, dois operandos por vez. Assim, pri-
meiramente avaliada a expressao o < b, cujo resultado podera ser 0 ou 1 chamemo-no
genericamente de R. Entao avaliada a expressao R < c, que nada tem a ver com a relaao
entre b e c.
Olhemos agora para as comparaes erroneas que usam o operador de atribuiao em
vez do operador de comparaao de igualdade, como if (o = b}. Ovalor de uma expressao
como o = b corresponde ao valor que foi atribudo, ou seja, b (ou o apos a atribuiao).
Assim, o codigo if (o = b} equivalente a
o = b
if (b}
No entanto, como a variavel b esta num contexto logico, seu valor sera interpretado como
um valor booleano, ou seja, falso se b == 0 e verdadeiro se b != 0. Assim, fnalmente, a
comparaao if (o = b} na verdade equivalente a
Nota para o futuro: na verdade, s podem ser interpretados como booleanos os tipos escalares, ou seja,
os tipos numricos (inteiro e ponto utuante), alm dos ponteiros, que s veremos no captulo 5.
Variveis booleanas 33
o = b
if (b != 0}
que, claramente, nao tem nada a ver com a igualdade (prvia) entre a e b.
Rivis.uo .1i .qUi 03/02/2011
3
Funes
3.1 Introduo
Em C, uma funo um pedao de codigo, dentro de um programa maior, que realiza
uma certa tarefa com uma certa independncia do resto do programa. Funes podem ser
executadas varias vezes, e uma grande vantagem disso a reutilizao de cdigo: em vez
de repetir varias vezes o codigo para executar certa tarefa, podemos simplesmente chamar
varias vezes a funao que executa essa tarefa. Alm de economizar linhas de codigo, isso
permite que voc mude facilmente o codigo associado a essa tarefa se nao fosse pelas
funes, voc teria de buscar em seu programa por todos os locais em que voc executou
essa tarefa e alterar o codigo em cada um. Mais ainda, ao organizarmos o codigo em varias
funes, podemos focar cada parte do codigo emuma so tarefa, deixando o programa mais
claro e limpo.
Em C, uma funao deve ter as seguintes caractersticas:
Um nome pela qual ela possa ser chamada. Os nomes possveis seguem as mesmas
restries que os nomes de variaveis: devem comear com uma letra ou com um
sublinhado (), e podem conter qualquer combinaao desses e dos algarismos 09.
Lembre-se de que ha distinao entre maisculas e minsculas.
Valores de entrada ou parmetros sao os valores sobre os quais a funao deve ope-
rar. Os parametros das funes (tambm chamados de argumentos) atuam de ma-
neira analoga s variveis das funes matematicas.
Tambm possvel criar funes sem argumentos por exemplo, se voc quiser
criar uma funao que calcula o valor (aproximado, claro) do nmero , nao precisa
de nenhum parametro (a princpio; voc poderia introduzir parametros se quisesse
por exemplo, a precisao desejada , mas eles nao sao necessarios se voc quer
executar a operaao de uma maneira pr-determinada).
Um valor de sada, que corresponde ao resultado da funao para o conjunto dos
parametros de entrada fornecidos. Tambm possvel criar funes que nao devol-
vem nenhum valor de sada. Por exemplo, uma funao que simplesmente exibe uma
mensagem na tela nao precisa devolver nenhum valor embora a funao tenha um
resultado, ele mostrado na tela, e nao devolvido internamente para o programa.
37
38 Captulo 3. Funes
Burocraticamente, ao se defnir uma funao, precisamos sempre especifcar todas essas
caractersticas: o nome da funao, a quantidade de parametros e o tipo de cada um, alm
do tipo do valor de sada (caso haja valor de sada). E, claro, voc deve defnir o que a
funao vai fazer.
Para defnir uma funao, usamos a seguinte estrutura:
tipo_da_sada nomedofuno (parmetros}
{
contedo da funo
]
Essa defniao deve ser colocada no nvel superior do arquivo, ou seja, nao deve estar
dentro de outra funao como o main. Todas as funes dentro de um arquivo devem fcar
no mesmo nvel, cada uma apos o fnal da anterior. Na verdade, enquanto eu nao falar
de uma outra coisa (ainda neste captulo), as demais funes devem fcar antes da funao
main.
O tipo do valor de sada pode ser qualquer um dos tipos usados para variaveis (por
enquanto, voc so conhece o int). No caso em que nao ha valor de sada, voc deve usar no
lugar do tipo a palavra void (vazio, em ingls). Ela nao um tipo de variavel; ela apenas
indica a ausncia de um valor. (Muitos falam do tipo void, mas isso apenas um abuso
de linguagem.)
A defniao dos parametros semelhante declaraao de variaveis. Cada parametro
deve ter um nome (seguindo, novamente, as mesmas restries validas para os nomes de
variaveis) e um tipo. Para especifcar esses parametros, voc deve usar o formato
tipo_1 nome_1, tipo_2 nome_2, , tipo_n nome_n
Note que, nesse caso, nao existe nenhum tipo de abreviaao para varios parametros do
mesmo tipo (como ocorria na declaraao de variaveis). No caso de nao haver parametros,
voc deve usar a palavra void sozinha dentro dos parnteses:
tipo_da_sada nome_da_funo (void)
Atenao: segundo muitos textos, em caso de ausncia de parametros bastaria deixar os pa-
rnteses vazios, sem nada no meio. Segundo o padrao da linguagem, nesse caso o com-
pilador apenas entenderia que no h informao sobre os parametros; mesmo assim, isso
costuma ser aceito pela maioria dos compiladores.
O conjunto dessas trs defnies do nome, do tipo de sada e da lista de parametros
da funao chamado de cabealho da funao. Vamos construir alguns exemplos de
cabealhos:
Uma funao que calcula a soma dos divisores de um nmero inteiro n. Como en-
trada, teremos obviamente o nmero n, que sera uma variavel do tipo int. Como
sada, teremos outro valor do tipo int, que correspondera a soma dos divisores de n.
Com isso, o cabealho fca
inl somodivisores(inl n}
Introduo 39
Uma funao que recebe dois nmeros inteiros, a e b, e devolve o valor da potncia
a
b
. Novamente, todos os valores envolvidos sao do tipo int, e nosso cabealho vem
a ser
inl polencio(inl o inl b}
Voc esta criando uma funao que recebe um ms e um ano e imprime na tela o
calendario desse ms. Nesse caso, nao ha nenhum valor de sada (os dados sao en-
viados diretamente para a tela, com a funao printf), o que indica que usaremos a
palavra void no lugar do tipo da sada; mas ha dois parametros do tipo int, entao o
cabealho fca assim:
void imprimecoiendorio(inl mes inl ono}
Suponha agora que voc quer fazer uma funao que l uminteiro do teclado (usando
a funao scanf como intermediaria). Para isso, voc nao precisa de nenhum parame-
tro de entrada; voc simplesmente devolvera o nmero lido.
inl ieinleiro(void}
Agora, o prato principal: como escrever o contedo da funao: Isso o menor dos
mistrios tudo que voc precisaria saber sobre isso ja foi feito na funao main, que uma
funao (quase) como outra qualquer. Basta colocar um par de chaves apos o cabealho e
colocar no meio das chaves tudo o que voc souber fazer em C: declarar variaveis, fazer
contas, chamar as funes scanf e printf, usar laos e controles, etc.
Antes de poder criar exemplos concretos, precisamos ver um pouco mais de teoria.
int funcao(int x, int y)
x y
Figura 3.1: Representaao ilustrativa de uma funao.
40 Captulo 3. Funes
3.2 Os parmetros e o valor de sada
Ja vimos como defnir as entradas e sadas de uma funao; agora precisamos saber lidar
com elas: como acessar os parametros de entrada e como devolver o valor de sada. A
primeira tarefa muito simples: uma vez defnidos os parametros no cabealho da funao,
voc pode acessa-los como se fossem variaveis normais. Por exemplo, se quisssemos criar
uma funao que recebe dois inteiros e imprime sua soma na tela, poderamos escrever:
void imprimesomo(inl o inl b}
{
inl somo
somo = o + b
prinlf("xd`n" somo}
]
Veja que nesse caso a funao nao tem nenhum resultado a devolver para o programa,
entao usamos a palavra void para o tipo de sada. Lembre-se de que nao necessario criar
uma variavel intermediaria para fazer essa conta. Poderamos ter escrito apenas
void imprimesomo(inl o inl b}
{
prinlf("xd`n" o + b}
]
Para devolver o valor de sada, usamos a instruao return seguida do valor de sada (ter-
minando com um ponto-e-vrgula). O valor pode ser qualquer expressao que seja legtima
de se colocar no lado direito de uma atribuiao: o valor de uma variavel, uma constante
numrica, uma expressao aritmtica, etc. Por exemplo:
relurn 0
relurn x*x
relurn y + 1
Vamos ver um exemplo mais concreto. A funao a seguir devolve para o programa a
soma dos dois nmeros recebidos como parametros:
inl somo(inl o inl b}
{
relurn o + b
]
E importante ressaltar que a instruao return tambm encerra a execuo da funo,
ou seja, voc so pode executa-la quando nao houver mais nada a fazer dentro da funao.
Se voc colocar uma instruao return no meio da funao, ela devolvera o valor indicado e
ignorara todo o resto da funao.
Vale tambm salientar que uma funao void no pode devolver nenhum valor. Seria
um erro escrever algo do tipo relurn 0 numa funao void da mesma maneira, errado
usar uma instruao return sem valor numa funao que nao void. No entanto, voc pode
usar a instruao return (sem nenhum valor) para terminar uma funao void no meio. Por
exemplo:
Usando funes 41
void imprimenumero(inl n}
{
if (n < 0} {
prinlf("Wo quero imprimir nmeros negolivos!`n"}
relurn
]
prinlf("xd`n" n}
]
Vamos criar umexemplo mais elaborado: uma funao que calcula a potncia a
b
, dados
dois inteiros a e b. (Para deixar o codigo mais claro, chamamos a de base e b de expoente.)
Veja que o codigo da funao essencialmente o mesmo que havamos criado antes.
inl polencio(inl bose inl expoenle}
{
inl pol i
pol = 1
for (i = 1 i <= expoenle i++}
pol *= bose
relurn pol
]
3.3 Usando funes
Ja sabemos como criar funes; mas como fazemos para usa-las: Tendo emmaos o nome da
funao e a lista de valores que desejamos mandar como parametros de entrada, a formula
para chamar uma funao simples, e nao depende de a funao ter ou nao umvalor de sada:
nome_da_funo (parmetro_1, parmetro_2, )
Caso a funao nao tenha nenhum parametro, simplesmente deixe os parnteses sozi-
nhos sem nada no meio:
nome_da_funo ()
Esse tipo de comando uma chamada de funo; ele simplesmente faz comque o com-
putador pule para a funao chamada, execute-a por inteiro e depois volte para o mesmo
ponto de onde saiu.
Se a funao tiver um valor de sada, provavelmente vamos querer aproveita-lo nesse
caso, basta colocar essa chamada de funao no meio de uma expressao qualquer; por exem-
plo, podemos guarda-lo numa variavel, coloca-lo no meio de uma expressao aritmtica ou
mesmo manda-lo como parametro para outra funao. Um exemplo, utilizando duas fun-
es que ja escrevemos acima:
inl o b c
o = polencio(Z 3}
42 Captulo 3. Funes
b = somo(o }
c = polencio(3 somo(b o} + b}
Se nao houver valor de sada, simplesmente colocamos a chamada de funao com o
tradicional ponto-e-vrgula no fnal. Por exemplo:
inl o b
o = polencio(Z 3}
b = somo(o }
imprimesomo(o b + polencio(o b}}
Vamos ver um exemplo de programa completo usando funes. O funcionamento dele
sera bemsimples: leremos umpar de inteiros do teclado e calcularemos o primeiro elevado
ao segundo (o segundo deve ser positivo!), usando a funao potencia ja escrita anterior-
mente:
#inciude <sldio.h>
inl polencio(inl bose inl expoenle}
{
inl pol i
pol = 1
for (i = 1 i <= expoenle i++}
pol *= bose
relurn pol
]
inl moin(}
{
inl bose expoenle
prinlf("0igile o bose. "}
sconf("xd" &bose}
prinlf("0igile o expoenle (inleiro posilivo}. "}
sconf("xd" &expoenle}
prinlf("Resuilodo. xd`n" polencio(bose expoenle}}
relurn 0
]
Veja que a funao potencia foi colocada antes da main, como ja observamos que seria
necessario. Se voc trocasse a ordem das funes, receberia uma mensagem de erro do
compilador; veremos a seguir por que isso ocorre e uma outra maneira de defnir as funes
que, de certa maneira, elimina essa limitaao.
Trabalhando com vrias funes 43
3.4 Trabalhando comvrias funes
A linguagem C bastante rgida quanto aos tipos dos objetos (variaveis e funes) usados
nos programas: toda variavel deve ser declarada antes de ser utilizada, para que o compu-
tador saiba como organizar a memoria para o acesso a essa variavel. Da mesma maneira,
para que o computador saiba como organizar o transito dos valores de entrada e sada,
toda funo deve ser declarada antes de ser chamada.
A princpio, isso nao um grande problema, pois, quando fazemos a defniao de uma
funao (isto , escrevemos seu cabealho e logo em seguida seu contedo), a declaraao
feita implicitamente. No entanto, essa declaraao so vale para o que vier depois dela;
entao uma funao nao pode ser usada dentro de outra funao que vem antes dela. Por isso
que eu disse que todas as funes deveriam ser defnidas antes da main; se chamamos a
funao potencia dentro da funao main e a defniao de potencia so vem depois de main,
o compilador nao tem nenhuma informaao sobre quem potencia, entao nao tem como
chamar essa funao corretamente.
Por esse motivo, uma funao pode ser declarada explicitamente antes de ser propria-
mente defnida. A declaraao explcita de uma funao bastante simples: basta reescrever
o cabealho da funao, seguido de um ponto-e-vrgula:
tipo_da_sada nome_da_funo (parmetros);
Por exemplo, para declarar a funao potencia que criamos anteriormente, escrevera-
mos simplesmente
inl polencio(inl bose inl expoenle}
Ja vimos que nao estritamente necessario declarar explicitamente as funes emtodos
os casos: em varias situaes possvel contornar o problema com uma simples reordena-
ao das funes. No entanto, com programas maiores e mais complexos, sempre uma
boa idia declarar todas as funes no comeo do arquivo, e voc encorajado a sempre
faz-lo isso nos permite uma melhor organizaao do codigo, sem precisar nos prender
ordem de dependncia das funes; alm disso, a lista de declaraes pode servir como
um pequeno ndice das funes que foram defnidas num certo arquivo.
3.5 Escopo de variveis
Uma caracterstica bastante til (e importante) da linguagem C que as funes sao to-
talmente independentes quanto s variaveis declaradas dentro delas. Se voc declara uma
variavel dentro de uma funao essas sao chamadas de variveis locais , ela so existe
dentro da propria funao; quando a funao termina sua execuao, as variaveis locais sao, de
certa maneira, perdidas. Nenhuma operaao feita com variaveis locais podera afetar outras
funes. Ainda mais, voc pode declarar variaveis locais com o mesmo nome em funes
diferentes, sem o menor problema; cada funao sabera qual a sua variavel e nao mexera
nas variaveis das outras funes.
Ebomressaltar o papel dos parametros de funes nessa historia: eles funcionamexata-
mente como as variaveis locais, coma diferena de que seus valores sao atribudos de forma
implcita. Cada vez que voc chama uma funao com um certo conjunto de parametros,
os valores desses parametros sao copiados para a funao chamada. Assim, voc tambm
44 Captulo 3. Funes
pode modifcar os valores dos parametros de uma funao, e nada mudara na funao que a
chamou.
Um exemplo classico para ilustrar esse comportamento uma tentativa de criar uma
funao que troca o valor de duas variaveis:
#inciude <sldio.h>
void lroco(inl o inl b}
{
inl lemp
prinlf("funo lroco - onles. o = xd b = xd`n" o b}
lemp = o
o = b
b = lemp
prinlf("funo lroco - depois. o = xd b = xd`n" o b}
]
inl moin(}
{
inl x y
x = 10
y = 5
prinlf("moin - onles. x = xd y = xd`n" x y}
lroco(x y}
prinlf("moin - depois. x = xd y = xd`n" x y}
relurn 0
]
Se voc executar esse exemplo, vera que o valor das variaveis x e y dentro da funao
main nao se altera. A sada desse programa sera:
moin - onles. x = 10 y = 5
funo lroco - onles. o = 10 b = 5
funo lroco - depois. o = 5 b = 10
moin - depois. x = 10 y = 5
Voc deve entender por que isso acontece: quando trocamos as variaveis a e b, estamos
nada mais que trocando de lugar as cpias das variaveis originais x e y. Quando a funao
troca encerrada, essas copias sao inutilizadas e a funao main volta a ser executada. Nao
fzemos nada explicitamente com as variaveis da funao main; nao ha motivo para que elas
mudem.
Existe, sim, uma maneira de criar uma funao que troque o valor de duas variaveis
ela envolve o uso de ponteiros, conforme veremos no Captulo 3.
As variaveis locais podem ser declaradas nao apenas dentro de funes, mas dentro
de laos ou estruturas de controle (if, while, for, etc.). Isso quer dizer que podemos criar
variaveis que so existem dentro de um bloco if (por exemplo), e nao podem ser acessadas
fora dele, mesmo dentro da mesma funao. O contexto em que uma variavel existe e pode
ser acessada denominado escopo. Entao dizemos que o escopo de uma certa variavel
um certo bloco ou uma certa funao.
Escopo de variveis 43
Mesmo podendo declarar variaveis dentro de qualquer bloco (bloco como se costuma
designar genericamente qualquer estrutura de comandos delimitada por { ], seja uma
funao ou uma estrutura de controle), continua valendo a restriao de que as declaraes
de variaveis devem vir no comeo do bloco correspondente, nao podendo haver nenhum
outro tipo de comando antes dessas declaraes.
4
Mais sobre nmeros
4.1 Bases de numerao
Ao criar uma maquina que faz calculos, nada mais crucial que a escolha da maneira de
representaao dos nmeros ou do formato em que eles serao armazenados. Para nos, o
sistema decimal parece ser o mais natural contamos com os 10 dedos das maos (uma
possvel explicaao para o uso da base 10 e nao de outra); usamos as potncias de 10 (dez,
cem, mil) como referncia ao falar os nmeros por extenso.
No entanto, os componentes de um computador funcionam com base em correntes
eltricas, e com eles muito mais simples implementar um sistema de numeraao binria
(ou base 2), no qual existem apenas dois dgitos (0 e 1), correspondendo, por exemplo, a
um nvel baixo ou nulo (0) e a um nvel alto (1) de corrente ou voltagem.
Se tomarmos um instante para compreender como a nossa usual base 10 funciona, sera
mais facil entender como a base 2 funciona. Tomemos um nmero; por exemplo, 41 302.
Ele pode ser tambm escrito como
41502 = 40000 + 1000 + 500 + 2
= 4 10000 + 1 1000 + 5 100 + 0 100 + 2 1
= 4 10
4
+ 1 10
3
+ 5 10
2
+ 0 10
1
+ 2 10
0
Veja, entao, que cada algarismo funciona como um multiplicador de uma potncia de
10. Os expoentes sao contados a partir da direita, comeando pelo zero. Assim, o primeiro
algarismo(a partir da direita) multiplicado por 10
0
, o segundopor 10
1
, e assimpor diante.
Perceba a regrinha implcita do sistema: o valor de cada algarismo sempre menor que a
base (o maior algarismo, 9, menor que a base, 10) e maior ou igual a zero. Isso necessario
para que cada nmero tenha uma nica representaao nessa base.
Para lembrar como sao as regras para se contar, pense em um contador analogico (por
exemplo, o odometro dos carros mais antigos, ou os medidores de consumo de agua e
energia eltrica nas casas), daqueles que mostram cada algarismo em uma rodinha. Cada
rodinha marca um algarismo de 0 a 9 e corresponde a uma ordem de grandeza (1, 10, 100,
). Sempre que uma das rodinhas da uma volta completa (passando do 9 para o 0), a rodi-
nha sua esquerda aumenta seu dgito. Se esse dgito ja era 9, o mesmo processo repetido
sucessivamente para as outras rodinhas da esquerda.
47
48 Captulo 4. Mais sobre nmeros
Sabendo isso, podemos, por analogia, proceder base 2. As regras sao, pois, as seguin-
tes:
Como cada algarismo deve ter umvalor menor que o da base e maior e igual a zero, so
podemos ter os algarismos 0 e 1, como voc provavelmente ja sabia. Por isso, contar
em binario bastante simples pense num contador binario, no qual cada rodinha
so tem duas posies: 0 e 1. Quando uma rodinha passa do 1 para o 0, a proxima
rodinha (a da esquerda) se movimenta: se estiver no 0, passa para o 1; se estiver no
1, passa para o 0, propagando o movimento para a esquerda.
Os nmeros de um a dez escrevem-se assim em binario:
1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010
Os algarismos binarios, como ja disse, costumam corresponder ao estado de um
componente quanto passagem de corrente. Esses algarismos sao geralmente cha-
mados de bits abreviaao de binary digit.
O valor do nmero obtido multiplicando-se cada algarismo por uma potncia de
2 (e nao mais de 10) de acordo com sua posiao. Assim, o nmero 1101101 poderia
ser traduzido para o sistema decimal da seguinte maneira:
(1101101)
2
= 1 2
6
+ 1 2
5
+ 0 2
4
+ 1 2
3
+ 1 2
2
+ 0 2
1
+ 1 2
0
= (109)
10
Veja que usamos a notaao ( )
2
ou ( )
10
para especifcar a base em que estamos
escrevendo. (Quando nao houver indicaao, fcara entendido que o nmero esta na
base decimal.)
4.1.1 Uma base genrica
Podemos generalizar esse raciocnio para qualquer base de numeraao b; outras bases co-
mumente usadas em computaao sao a octal (b = 8) e hexadecimal (b = 16). Se um
nmero representado como uma sequncia de n+1 dgitos, a
n
a
n1
a
1
a
0
, seu valor
dado por
a
n
b
n
+ a
n1
b
n1
+ + a
1
b + a
0
=
n

k=0
a
k
b
k
Para que a representaao de umnmero seja nica, necessario que os valores de todos
os seus algarismos sejam menores do que b, ou seja, no maximo b 1. Caso contrario, o
nmero b
2
, por exemplo, poderia ser representado pelas duas sequncias (b, 0) e (1, 0, 0).
Os valores dos dgitos tambm nao podem ser negativos senao surgiria mais uma am-
biguidade na representaao.
E importante nesse ponto nao confundir os valores dos dgitos com as suas representa-
es (que costumam ser os algarismos arabicos sempre que possvel). Aqui, os a
j
simbo-
lizam os valores dos dgitos (como objetos matematicos abstratos), independentemente de
sua representaao. Quando a base nao ultrapassa 10, os valores confundem-se com suas
representaes (usamos os algarismos de 0 a b1 para representar os valores de 0 a b1).
Quando a base maior que 10, precisamos de outra convenao para a base hexadecimal,
por exemplo, os dgitos com valores de 10 a 13 sao representados pelas seis primeiras letras
do alfabeto, enquanto os dgitos at 9 continuam sendo representados da maneira usual.
O armazenamento dos dados 49
Aformula acima nos permite, dados os valores dos dgitos de umnmero, achar o valor
do nmero. Podemos tambm querer realizar o processo inverso: dado um nmero, achar
os valores dos seus dgitos na representaao em base b. Olhando para a formula acima,
podemos ver que, ao dividir (com resto) o valor do nmero por b, obtemos como resto o
valor de a
0
; o quociente o novo nmero
a
n
b
n1
+ a
n1
b
n2
+ + a
1
E, ao dividi-lo por b, obteremos como resto o proximo dgito, a
1
(que pode muito bem ser
zero). Nao difcil concluir que, se realizarmos n + 1 divises por b, os restos serao, na
ordem, os dgitos
(a
0
, a
1
, a
2
, . . . , a
n1
, a
n
).
Ou seja, dividindo repetidamente por b, obteremos os dgitos da direita para a esquerda.
Note que, na ltima divisao, obtemos a
n
como resto e zero como quociente.
Para ilustrar esse algoritmo, vamos fazer uma funao que recebe um nmero inteiro e
imprime seus dgitos na representaao binaria.
void imprimebinorio(inl numero}
{
Whiie (numero > 0} {
prinlf("xd" numero x Z}
numero /= Z
]
prinlf("`n"}
]
Notemos, primeiro, que essa funao fornece os dgitos na ordem inversa (conforme foi
observado acima); por exemplo, o nmero 12 = (1100)
2
seria impresso como 0011.
Veja tambmque, emvez de fxar o nmero de divises, estabelecemos como critrio de
parada o anulamento do quociente. Com isso, nao precisamos saber de antemao quantos
dgitos binarios tem o nmero; vimos que o quociente sera zero quando so sobrar o dgito
mais signifcativo.
4.2 Oarmazenamento dos dados
Ao armazenar um nmero na memoria do computador, precisamos estabelecer um tama-
nho padronizado (uma espcie de nmero de algarismos) que um nmero ocupara. Por
que isso: Imagine que a memoria do computador uma enorme aglomeraao de rodinhas
numeradas. Se nao tivesse umtamanho padrao, como saberamos onde comea e onde ter-
mina cada nmero: Precisaramos de algum outro nmero para saber onde cada nmero
comearia, e essa historia nao terminaria nunca.
Num computador, a menor unidade possvel de informaao o bit; mas a menor uni-
dade de informaao que pode ser de fato acessada o byte, que, nos microcomputadores
modernos, equivale a 8 bits. No entanto, um byte sozinho nao serve para muita coisa; n-
meros inteiros costumam ser armazenados em espaos de 1, 2, 4 ou 8 bytes (ou seja, 8, 16,
32 ou 64 bits, respectivamente).
Apesar de o tamanho de um inteiro ser padronizado, ainda possvel que existam in-
teiros de mais de um tamanho. Por exemplo, em C existem pelo menos trs tipos inteiros,
30 Captulo 4. Mais sobre nmeros
de tamanhos diferentes. O que ocorre que cada processador trabalha naturalmente com
um certo tamanho de inteiro (usualmente, 32 ou 64 bits) todos os seus espaos inter-
nos de armazenamento (eles se chamam registradores) tm esse mesmo tamanho. Quando
queremos usar inteiros de tamanhos diferentes, o processador simplesmente trabalha com
pedaos desses tais registradores ou com varios registradores juntos.
Agora voltemos um pouco ao C. Ja conhecemos os dois tipos basicos de inteiros: inl e,
embora nao o tenhamos usado ainda, chor (como vimos no comeo do primeiro captulo,
os caracteres sao armazenados como se fossem inteiros). O tipo chor tem um tamanho
nico: um byte ou seja, 8 bits. O tipo inl geralmente tem um tamanho padrao de 32
bits, mas ele tambm possui alguns subtipos com tamanhos diferentes. Sao eles:
shorl inl, um inteiro curto, que costuma ter 16 bits;
iong inl, um inteiro longo, que costuma ter 32 bits (nao ha nada de errado aqui;
o tipo inl realmente costuma ser igual ao iong).
iong iong inl, que geralmente tem 64 bits. (Somente no padrao C99.)
Nota: No padrao da linguagem C, os tamanhos desses tipos de dados sao defnidos de uma ma-
neira mais formal (e mais vaga), devido grande diferena entre os varios tipos de sistemas em
que o C pode ser usado. Eu os defni de uma maneira mais pratica, segundo os tamanhos que
esses tipos tmna maioria dos computadores pessoais de hoje emdia; uma defniao mais correta
pode ser encontrada na especifcaao da linguagem.
Todos esses subtipos podem (e costumam) ser abreviados, tirando deles a palavra inl.
Assim, shorl inl costuma ser escrito como simplesmente shorl (e assim por diante).
Quao grandes sao esses tamanhos: Que valores cabemnuminteiro de 32 ou 16 bits (ou
de qualquer outro nmero n): Para isso, podemos pensar no menor e no maior nmero
que pode ser representado com n bits. O resultado analogo ao caso decimal: se temos k
algarismos, o maior nmero que podemos representar uma sequncia de k algarismos 9,
que igual a 10
k
1. Da mesma maneira, com n bits, o maior nmero representavel (em
binario) 2
n
1, que corresponde a uma sequncia de n algarismos 1.
Assim, com32 bits, por exemplo, podemos representar todos os nmeros de 0 a 2
32
1,
que vale 4 294 967 295. Na tabela a seguir voc pode ver as capacidades dos tamanhos de
inteiros mais comuns.
Tabela 4.1: Os tamanhos mais comuns de inteiros nos microcomputadores atuais, e os maiores
nmeros armazenaveis com tais tamanhos.
Tipo Bits Bytes Maior nmero
chor 8 1 255
shorl 16 2 65 535
inl, iong 32 4 4 294 967 295
iong iong 64 8 18 446 744 073 709 551 615
Voc tambm pode chegar a esses nmeros pensando em termos das nossas somas

a
k
b
k
, com todos
os a
k
= b1. Lembre da frmula de soma de uma progresso geomtrica e verique que, de fato, o resultado
o mesmo.
O armazenamento dos dados 31
4.2.1 Nmeros negativos
Talvez no meio disso tudo voc tenha se perguntado se existe alguma maneira de escrever
nmeros negativos no sistema binario. Numa escrita humana, o natural seria simples-
mente escrever o sinal de menos para os nmeros negativos. Nos computadores, a codi-
fcaao mais comum de nmeros negativos tem como princpio reservar um dos bits do
nmero para o sinal assim, um nmero de 32 bits fca com 31 bits para guardar o mo-
dulo do nmero e 1 bit para guardar o sinal. Geralmente usado o bit mais signifcativo
(o que estiver mais esquerda na nossa representaao usual), e ele vale 1 para nmeros ne-
gativos ou 0 para nmeros positivos. Sabendo isso, talvez voc poderia imaginar que, por
exemplo, se o nmero 14 armazenado em 8 bits como 0000 1110, o nmero 14 seria
armazenado como 1000 1110. Nao bem assim.
O que acontece que essa representaao nao muito efciente quando o computador
vai fazer calculos. Existe uma outra representaao, conhecida como representaao do com-
plemento de 2, que permite que nmeros negativos sejam operados exatamente da mesma
maneira que os nmeros positivos, isto , sem que o computador precise verifcar se um
nmero negativo para saber como fazer a conta.
Complemento de 2 nao o melhor nome (seria mais adequado complemento de 2
n
);
mas o mtodo consiste em guardar cada nmero negativo k como se fosse o nmero
positivo 2
n
k, sendo n o nmero de bits reservado para o inteiro. Por exemplo, ao
trabalhar com 8 bits, o nmero 14 seria guardado como se fosse 256 14 = 242 (em
binario, isso seria 1111 0010).
Agora, se olharmos para o nmero 128, veremos que ele o seu proprio complementar
quando usamos n = 8 bits: 256 128 = 128. Mas lembre-se que a representaao binaria
de 128 1000 0000 como o bit de sinal 1, faz muito mais sentido convencionar que
essa sera a representaao do nmero negativo 128. Entao, nessa representaao, o 128
nao existe (precisaramos usar mais que 8 bits); os nmeros positivos param no 127. Os
nmeros negativos vao at o 128.
Generalizando essas concluses, num espao de n bits possvel guardar, com sinal,
todos os nmeros inteiros de 2
n1
at 2
n1
1. (Veja como isso equivalente ao nosso
intervalo de 128 a 127 para a representaao de 8 bits.)
Umalgoritmo pratico para calcular o complemento de 2 de umnmero, ja na represen-
taao binaria, o seguinte: preencha com zeros esquerda para completar a quantidade de
bits que esta sendo usada; entao inverta todos os bits da representaao (trocar os 0 por 1 e
vice-versa) e some 1. Lembre-se de inverter tambm os zeros esquerda que voc incluiu.

Em C possvel armazenar tanto nmeros negativos quanto positivos, como ja men-


cionamos no comeo. Mais ainda, ao declarar uma variavel voc pode escolher se quer
guardar nmeros negativos e positivos (com sinal) ou so positivos (sem sinal) como
acabamos de ver, isso faz diferena no intervalo de nmeros que pode ser guardado. As-
sim, uma variavel de 8 bits pode suportar nmeros de 0 a 233 ou nmeros de 128 a 127.
Para indicar isso, voc pode modifcar os tipos numricos inteiros (chor e inl) com mais
dois adjetivos: signed (com sinal) e unsigned (sem sinal). Quando voc nao diz nada, o
computador assume que voc quer guardar os nmeros com sinal (signed).
Se voc quiser vericar por que os dois mtodos so equivalentes, pense na soma de um nmero com o
que tem todos os seus bits invertidos.
32 Captulo 4. Mais sobre nmeros
Tabela 4.2: As faixas de nmeros armazenaveis nos tipos inteiros com sinal.
Tipo Regio
chor (8 bits) 128 a 127
shorl (16 bits) 32 768 a 32 767
inl, iong (32 bits) 2 147 483 648 a 2 147 483 647
iong iong (64 bits) 9 223 372 036 854 775 808 a 9 223 372 036 854 775 807
4.3 Nmeros reais
E possvel fazer muita coisa so comnmeros inteiros, mas emmuitas situaes somos obri-
gados a usar nmeros fracionarios ou reais (nmeros com vrgula). Em C, os nmeros
reais estao encarnados em dois tipos de dados: oat e double. Os dois funcionam mais
ou menos do mesmo jeito; a diferena entre eles a precisao de cada um dizemos que
o oat tem precisao simples e o double tem precisao dupla. Na maioria das linguagens de
programaao (inclusive C), esses tipos de nmeros sao conhecidos como nmeros de ponto
utuante mais adiante veremos o porqu desse nome.
Falamos em precisao porque, assim como no caso dos inteiros, necessario impor um
tamanho para cada nmero, o que limita a representaao dos nmeros veremos mais
adiante como exatamente isso se da.
Antes de entrar na parte mais teorica, vamos ver como se usamos tais nmeros de ponto
futuante. A declaraao de variaveis funciona da mesma maneira que para os inteiros:
doubie x y
fiool z W
Para escrever nmeros fracionarios em C, usamos o ponto (.) para separar a parte in-
teira da fracionaria por exemplo, 3.14159 ou -0.001. Podemos tambm usar uma esp-
cie de notaao cientfca para trabalhar com nmeros muito grandes ou muito pequenos
escrevemos o valor principal do nmero da maneira que acabei de descrever, e usamos a
letra e ou E para indicar a potncia de 10 pela qual o nmero deve ser multiplicado. Alguns
exemplos:
3.14159e-7
(
3,14159 10
7
)
1.Z34E+Z6
(
1,234 10
26
)
4.56e5
(
4,56 10
5
)
Veja que nao faz diferena se o caractere E maisculo ou minsculo, nem se colocamos o
sinal de + nos expoentes positivos.
Tao importante quanto saber escrever esses nmeros saber imprimi-los na tela ou l-
los do teclado. Usando a funao printf, usamos o codigo xf (da mesma maneira que o nosso
conhecido xd), tanto para o double quanto para o oat, como no seguinte exemplo:
#inciude <sldio.h>
inl moin(}
{
doubie x = 5.0
fiool y = 0.1
inl z = Z7
Nmeros reais 33
prinlf("x = xf`n" x}
prinlf("y = xf z = xd`n" y z}
relurn 0
]
Para ler nmeros reais do teclado, precisamos dizer se a variavel onde vamos guardar
do tipo double ou oat. No primeiro caso, usamos o codigo xif; no segundo, apenas xf
(como no printf)
#inciude <sldio.h>
inl moin(}
{
doubie x z
fiool y
prinlf("0igile dois nmeros de ponlo fiuluonle. "}
sconf("xif xf" &x &y}
z = x + y
prinlf("xf`n" z}
relurn 0
]
Veja que podemos fazer contas entre nmeros de ponto futuante da mesma maneira
que fazamos com os inteiros; podemos at misturar doubles com oats. Mais adiante
faremos algumas observaes sobre isso.
4.3.1 Mais sobre printf
Quando trabalhamos com nmeros de ponto futuante, pode ser til exibir nmeros em
notaao cientfca. Para isso, podemos trocar nosso codigo xf por xe ou xE: o resultado
sera impresso com a notaao que estabelecemos anteriormente, usando um E maisculo
ou minsculo dependendo do que for especifcado. Por exemplo,
prinlf("xe xE`n" 6.0 0.05}
/* resuilodo. 6.000000e+00 5.000000E-0Z */
Mas o printf tambm tem uma opao muito inteligente, que a xg ela escolhe o
melhor formato entre xf e xe de acordo coma magnitude do nmero. Nmeros inteiros nao
muito grandes sao impressos sem casas decimais (e sem ponto); nmeros muito grandes
ou muito pequenos sao impressos com notaao cientfca. Tambm podemos usar um C
maisculo se quisermos que o E da notaao cientfca saia maisculo. Por exemplo:
prinlf("xg xg xC`n" 6.0 0.00005 4000000.0}
/* resuilodo. 6 5e-05 4E+06 */
34 Captulo 4. Mais sobre nmeros
Outra coisa que podemos mudar o nmero de casas decimais exibidas apos o ponto. O
padrao para o formato xf de 6 casas; isso pode ser demais emvarios casos por exemplo,
se voc for imprimir preos de produtos. Para que o nmero de casas decimais exibidas seja
n, trocamos o codigo xf por
.nf
Para imprimirmos um nmero com 2 casas, por exemplo, podemos escrever assim:
prinlf("Preo = x.Zf`n" Z6.5}
/* resuilodo. Z6.50 */
Se colocarmos n = 0, o ponto decimal tambm sera apagado. Veja que os nmeros
serao sempre arredondados conforme necessario. Por exemplo:
prinlf("x.Zf x.0f`n" Z6.575 Z6.575}
/* resuilodo. Z6.5 Z7 */
4.3.2 Arepresentao
Falamos um pouco acima que tanto o oat quanto o double sao tipos de ponto utuante.
Isso diz respeito representaao dos nmeros reais na memoria do computador trata-se
de uma espcie de notaao cientfca. Nessa representaao, um nmero tem duas partes: a
mantissa, que sao os algarismos signifcativos, e o expoente, que indica a posiao da vrgula
decimal em relaao aos algarismos signifcativos.
Pensando na base decimal, um nmero como 0,000395 teria a representaao 3,95
10
4
, cuja mantissa 3,95 e cujo expoente 4 (indica que a vrgula deve ser deslocada
4 dgitos para a esquerda). Na notaao cientfca, estabelecemos que a mantissa deve ser
maior que ou igual a 1, mas nao pode ter mais de um dgito esquerda da vrgula por
exemplo, a mantissa pode ser 1,00 ou 9,99, mas nao 15,07 ou 0,03. Assim, estamos de fato
dividindo a informaao do nmero em duas partes: o expoente da a ordem de grandeza
do nmero, e a mantissa da o valor real dentro dessa ordem de grandeza.
Nem todo nmero tem uma representaao decimal fnita em outras palavras, nem
todo nmero tem uma mantissa fnita. Por exemplo, a fraao 5/3 pode ser expandida in-
fnitamente como 1,6666 . . .; se quisermos expressa-la com um nmero fnito de dgitos
(como necessario em um computador), devemos parar em algum dgito e arredondar
conforme necessario por exemplo, 1,6667, se precisarmos trabalhar com 3 dgitos.
Assim, para colocar um nmero nessa representaao, necessario primeiro normalizar
a mantissa para que fque entre 1 (inclusive) e 10, e depois arredonda-la para um nmero
de dgitos pr-estabelecido.
A representaao usual de ponto futuante no computador praticamente igual a essa;
antes de ver como ela realmente funciona, vamos ver como funciona a representaao dos
nmeros fracionarios na base binaria.
O que a representaao decimal de um nmero: Considere um nmero que pode ser
escrito como a
n
a
n1
a
1
a
0
,b
1
b
2
(veja que a parte inteira, esquerda da vrgula,
tem um nmero fnito de dgitos; a parte fracionaria, direita da vrgula, pode ter infnitos
dgitos). Ja vimos como funciona a representaao da parte inteira; falta analisarmos a parte
fracionaria.
Nmeros reais 33
Vejamos primeiro o caso de um nmero cuja parte fracionaria fnita e tem m dgitos
nao vamos nos importar com a parte inteira; suponha que zero. Esse nmero pode ser
escrito como 0,b
1
b
2
b
m
. Entao, se multiplicarmos esse nmero por 10
m
, ele voltara
a ser inteiro, e sera escrito como b
1
b
2
b
m
. Ja sabemos como funciona a representaao
inteira de um nmero! Esse nmero vale
b
1
10
m1
+ b
2
10
m2
+ + b
m1
10
1
+ b
m
10
0
=
m

k=1
b
k
10
mk
Como esse o nmero original multiplicado por 10
m
, o nmero original vale
b
1
10
1
+ b
2
10
2
+ + b
m1
10
(m1)
+ b
m
10
m
=
m

k=1
b
k
10
k
Nao difcil estender essas contas para os casos em que a parte fracionaria nao fnita
(ouemque a parte inteira nao zero), mas seria muito tedioso reproduzi-las aqui; deixa-las-
ei como exerccio se voc quiser pensar um pouco, e direi apenas que aquela representaao
(possivelmente infnita) a
n
a
n1
a
1
a
0
,b
1
b
2
corresponde ao nmero
n

k=0
a
k
10
k
+

k=1
b
k
10
k
Como no caso dos inteiros, claro que todos os dgitos a
k
e b
k
so podem assumir os
valores {0, 1, . . . , 9}, para que a representaao de cada nmero seja nica. (Na verdade isso
nao sufciente para que a representaao seja nica; precisamos, para isso, exigir que haja
um nmero innito de dgitos diferentes de 9, evitando assim as representaes do tipo
0,999999 . . . 1.)
Veja que essa representaao uma extensao da representaao dos inteiros; poderamos
trocar os bs por as com ndices negativos (b
k
= a
k
) e estender a somatoria para os
ndices negativos:
n

k=
a
k
10
k
Com isso, a transiao para a base binaria esta muito bem encaminhada. O que mu-
dara na base binaria que os dgitos sao multiplicados por potncias de 2 (o dgito a
k

multiplicado por 2
k
), e so podem assumir os valores 0 e 1.
Por exemplo, o nmero 3/8 = 1/8 +1/4 = 2
2
+2
3
podera ser representado como
(0,011)
2
. E o nmero 1/10, que aparentemente muito simples: Na base 2, ele nao tem
uma representaao fnita! E facil ver o porqu: se ele tivesse representaao fnita, bastaria
multiplicar por uma potncia de 2 para torna-lo inteiro; sabemos que isso nao verdade
(nenhuma potncia de 2 divisvel por 10!).
Como descobrir a representaao binaria de 1/10: Vou falar sobre dois jeitos de fazer
isso. O primeiro envolve alguns truquezinhos algbricos. Vamos escrever
1
10
=
1
2

1
5
=
1
2

3
15
=
3
2

1
2
4
1
=
3
2
2
4

1
1 2
4
36 Captulo 4. Mais sobre nmeros
Agora a ltima fraao a soma da srie geomtrica de razao 2
4
. Podemos entao escrever
1
10
= 3 2
5

(
1 + 2
4
+ 2
8
+
)
= (1 + 2) 2
5

(
1 + 2
4
+ 2
8
+
)
=
(
2
4
+ 2
5
)

(
1 + 2
4
+ 2
8
+
)
1
10
=
(
2
4
+ 2
8
+ 2
12
+
)
+
(
2
5
+ 2
9
+ 2
13
+
)
Assim, identifcando esse resultado com a formula da representaao de um nmero, te-
mos b
k
= 1 se k = 4, 8, 12, . . . ou se k = 5, 9, 13, . . . , e b
k
= 0 caso contrario. Ou seja,
os dgitos nas posies 4, 5, 8, 9, 12, 13, . . . sao 1, e os outros sao 0. Logo, a representaao
binaria de 1/10
1
10
= (0,0 0011 0011 0011 . . .)
2
A partir dessas contas, podemos ver alguns padres interessantes (em analogia com a
base 10). Umdeles que multiplicar e dividir por 2 tem, na base binaria, o mesmo efeito que
tinhamas multiplicaes/divises por 10 na base decimal. Ou seja, ao multiplicar por 2 um
nmero, a vrgula vai uma posiao para a direita na representaao binaria (ou acrescenta-se
um zero caso o nmero seja inteiro).
Outro padrao que fraes do tipo
a
2
n
1
, em que a < 2
n
1 um inteiro, sao otimas
geradoras de dzimas periodicas (binarias): podemos escrev-las como
a
2
n
1
=
a
2
n
1
1 2
n
=
a
2
n
(
1 + 2
n
+ 2
2n
+
)
Como a representaao de a tem no maximo n dgitos, e a srie geomtrica direita uma
dzima de perodo n, a representaao da nossa fraao sera uma dzima cujo perodo a
representaao de a (com zeros esquerda para completar os n dgitos se necessario).
O outro mtodo de achar a representaao decimal de uma fraao voc ja conhece:
o algoritmo da divisao. Para aplica-lo ao caso das fraes, basta escrever o denominador
e o numerador em binario e fazer o processo de divisao, lembrando que as regras do jogo
mudam um pouco por exemplo, voc so pode multiplicar o divisor por 0 ou 1. Nao vou
ensinar os detalhes aqui nao o proposito deste livro. Mas basicamente este o algoritmo
usado pelo computador para converter nmeros fracionarios para a representaao binaria;
assim que ele pode controlar quantos dgitos serao mantidos na representaao basta
parar o algoritmo na casa desejada.
Fizemos uma longa digressao sobre representaao binaria de nmeros. Para que isso
serviu: Agora poderemos entender melhor como funciona a representaao de ponto fu-
tuante. Como dissemos acima, na representaao de ponto futuante um nmero tem duas
partes: a mantissa e o expoente. Para a mantissa M devemos impor uma condiao de nor-
malizaao como no caso da notaao cientfca decimal: devemos ter 1 M < 2 para que
esquerda da vrgula haja ume apenas umdgito (nao-nulo). Assim, uma fraao como 21/4
seria normalizada da seguinte maneira:
21
4
=
21
16
4 =
(10101)
2
2
4
2
2
= (1,0101)
2
2
2
Devemos considerar que a mantissa deve ser representada com um tamanho fxo (e
fnito) de dgitos. No caso da precisao simples do oat, esse nmero costuma ser de 24
Nmeros reais 37
dgitos. Assim, muitas fraes precisam ser arredondadas tanto as que tm representa-
es infnitas quanto as que tm representaes fnitas porm muito longas. Por exemplo,
a fraao 1/10 (representaao infnita) seria arredondada para
(1,100 1100 1100 1100 1100 1101)
2
2
4
= 0,100000001490116119384765625
Agora vamos chegar mais perto da representaao que realmente usada pelo compu-
tador. Um nmero de ponto futuante representado com a seguinte estrutura:
.. ..
sinal
31
63
..
expoente
30 23
62 52
..
mantissa
22 0
51 0
Figura 4.1: Esboo da estrutura de representaao de ponto futuante. Emcada parte foramindicados
os nmeros dos bits utilizados nas representaes de precisao simples e dupla, respectivamente.
Eu ainda nao havia falado do sinal: a representaao de ponto futuante usa um bit para
o sinal, assim como na representaao de inteiros. Mas, ao contrario desta, em ponto fu-
tuante nao se usa nada parecido com o complemento de 2; a nica diferena entre as
representaes de dois nmeros de mesma magnitude e sinais opostos o bit de sinal.
Oexpoente (relacionado a uma potncia de 2, nao de 10!) representado (quase) como
um nmero inteiro comum, e por isso ha uma limitaao nos valores que ele pode assumir.
Na precisao simples, sao reservados 8 bits para o expoente, o que permite at 236 valores
possveis nao sao exatamente de 128 a 127; na verdade, os dois extremos sao reservados
para situaes especiais; os expoentes representaveis sao de 127 at 126. Na precisao
dupla, usam-se 11 bits, e a gama de expoentes muito maior: de 1023 at 1022.
Sobre a mantissa nao resta muito a falar; os bits da mantissa sao armazenados sequen-
cialmente, como na representaao binaria que construmos acima. Em precisao simples,
sao reservados 23 bits para a mantissa. O leitor atento notara que eu falei em 24 dgitos
alguns paragrafos atras. De fato, a representaao binaria so reserva 23 dgitos. Mas, como
a mantissa esta sempre entre 1 e 2, esquerda da vrgula temos sempre um algarismo 1
sozinho; entao, convenciona-se que esse algarismo nao sera escrito (o que nos deixa com
1 bit a mais!), e assim temos uma mantissa de 24 dgitos em um espao de apenas 23 bits.
Na precisao dupla, sao reservados 32 bits (que na verdade representam 33).
Na verdade, ha varios outros detalhes sobre a representaao de ponto futuante; poderia
gastar mais algumas paginas com essa descriao, mas nao vem ao caso. Meu objetivo era
dar uma idia geral do funcionamento dessa representaao.
At agora, so explorei a parte teorica dessa representaao. Precisamos conhecer tam-
bm as caractersticas praticas da representaao de ponto futuante. Por exemplo, em ter-
mos da representaao decimal, como se traduzem os limites de representaao do expoente
e da mantissa: Nao vou fazer contas aqui, vou apenas mostrar os resultados praticos. Des-
creverei primeiro que resultados sao esses:
Devido aos limites de expoentes, existe um nmero maximo e um nmero mnimo
que podem ser representados com uma dada precisao. O nmero maximo corres-
ponde ao maior expoente possvel coma maior mantissa possvel; o nmero mnimo,
analogamente, corresponde ao menor expoente possvel com a menor mantissa pos-
svel. Na tabela, sao apresentadas as faixas aproximadas.
38 Captulo 4. Mais sobre nmeros
Os mesmos limites se aplicam para os modulos dos nmeros negativos, ja que n-
meros negativos e positivos sao tratados de maneira simtrica na representaao de
ponto futuante.
Como varios nmeros precisamser arredondados para seremrepresentados, a repre-
sentaao tem um certo nmero de algarismos signifcativos de precisao quantas
casas decimais estao corretas na representaao de um nmero. Como os nmeros
nao sao representados em base 10, a quantidade de casas corretas pode variar de n-
mero para nmero; os valores apresentados na tabela seguir correspondem quan-
tidade mnima.
Tabela 4.3: Comparaao dos diferentes tipos de nmero de ponto futuante em C
Tipo Expoente Mantissa Intervalo Preciso
oat (32 bits) 8 bits 23 bits 10
45
10
38
6 decimais
double (64 bits) 11 bits 32 bits 10
324
10
308
13 decimais
long double (80 bits) 13 bits 64 bits 10
4950
10
4932
19 decimais
Nota: A representaao de ponto futuante pode nao ser a mesma em todos os computadores. As
informaes aqui descritas estao de acordo com a representaao conhecida como IEEE 754, que
a mais usada nos computadores pessoais hoje em dia.
O tipo long double ainda nao tinha sido mencionado. Ele um tipo que, em geral, tem
precisao maior que o double (embora certos compiladores nao sigam essa norma), mas seu
tamanho nao tao padronizado como os outros dois tipos (precisao simples e dupla). Alm
de 80 bits, possvel encontrar tamanhos de 96 ou 128 bits.
De maneira similar ao double, necessario utilizar o codigo xLf em vez de apenas xf
quando queremos ler comscanf uma variavel longdouble. Nesse caso tambm necessario
utilizar esse mesmo codigo para o printf (em contraste com o double que continua usando
o codigo xf).
4.3.3 Problemas e limitaes
Como ja vimos, os tipos de ponto futuante nao conseguemrepresentar comexatidao todos
os nmeros fracionarios. Devido aos arredondamentos, acontecem alguns problemas que,
segundo a matematica que conhecemos, sao inesperados. Contas que, matematicamente,
dao o mesmo resultado, podem ter resultados diferentes. At uma simples troca de ordem
das operaes pode alterar o resultado.
Vamos ilustrar isso com um exemplo. Pediremos ao usuario que digite os coefcientes
reais de uma equaao quadratica
ax
2
+ bx + c = 0,
e analisaremos os trs tipos de soluao que podem ocorrer dependendo do valor de =
b
2
4ac (ainda nao vimos como calcular raiz quadrada, entao vamos apenas analisar os
casos):
Se > 0, a equaao tem duas razes reais distintas.
Nmeros reais 39
Se = 0, a equaao tem uma raiz real dupla.
Se < 0, a equaao nao tem razes reais.
O programa fcaria assim:
#inciude <sldio.h>
inl moin(}
{
fiool o b c deilo
prinlf("0 os coeficienles do equoo ox^Z + bx + c = 0. "}
sconf("xf xf xf" &o &b &c}
deilo = b*b - 4*o*c
prinlf("0eilo = xg`n" deilo}
if (deilo > 0}
prinlf("A equoo lem duos rozes reois dislinlos.`n"}
eise if (deilo == 0}
prinlf("A equoo lem umo roiz reoi dupio.`n"}
eise
prinlf("A equoo no lem rozes reois.`n"}
relurn 0
]
Esse programa, apesar de estar logicamente correto, apresenta umproblema. Considere
as equaes que tm uma raiz dupla , ou seja, equaes do tipo a(x )
2
: para elas,
exatamente igual a 0. No entanto, se voc testar o programa para varias dessas equaes,
descobrira um comportamento estranho: para muitos valores de , o valor de calculado
pelo programa nao igual a zero. Por exemplo, para valores nao inteiros e proximos de 1,
obtemos um da ordem de 10
7
.
Por que isso acontece: O problema sao os arredondamentos que ja vimos serem neces-
sarios para representar a maioria dos nmeros; por conta deles, nossa conta nao da exata-
mente zero no fnal.
Neste momento, considere isso como umalerta evite fazer comparaes de igualdade
com nmeros de ponto futuante; um pouco mais adiante, veremos o que possvel fazer
para contornar (ou conviver melhor com) essas limitaes.
4.3.4 Ponto utuante vs. inteiros
As operaes entre nmeros de ponto futuante funcionambasicamente da mesma maneira
que operaes entre inteiros; voc pode at realizar operaes mistas entre nmeros in-
teiros e de ponto futuante o inteiro convertido automaticamente para ponto futuante
e a operaao realizada como se os dois nmeros fossem de ponto futuante. Em geral,
sempre que voc fornece um inteiro num lugar onde era esperado um nmero de ponto
futuante, o inteiro convertido para ponto futuante.
Por outro lado, a coerncia matematica da linguagem exige que o resultado de uma
operaao entre dois inteiros seja um inteiro. Agora lembremos que a divisao entre dois
60 Captulo 4. Mais sobre nmeros
nmeros inteiros nem sempre exata, e da surgem os nmeros racionais a divisao entre
dois nmeros inteiros deve ser, emgeral, umnmero racional. Como resolver esse confito:
Em C, a primeira regra (fechamento das operaes entre inteiros) a que prevalece:
quando dividimos dois inteiros, obtemos apenas o quociente da divisao inteira entre eles.
Para obter o resultado racional da divisao entre dois inteiros, precisamos converter umdeles
em ponto futuante.
Suponha que voc deseja imprimir a expansao decimal da fraao 1/7. Uma possvel
primeira tentativa seria a seguinte:
fiool x
x = 1/7
prinlf("1/7 = xf`n" x}
Para sua frustraao, voc obteria o nmero 0.000000 como resultado. Claro, se voc fez
uma divisao entre inteiros, o resultado foi a divisao inteira entre eles; ao guardar o resultado
numa variavel de ponto futuante, apenas o resultado convertido quem decide que tipo
de operaao sera realizada sao os operandos em si.
Para fazer o que queramos, pelo menos um dos operandos devera ser de ponto futu-
ante. Ou seja, devemos escolher uma das alternativas:
x = 1.0 / 7
x = 1 / 7.0
x = 1.0 / 7.0
Mas e se quisssemos calcular a divisao entre dois nmeros que nao conhecemos a
priori: Devemos usar a
4.3.5 Converso explcita de tipos (casting)
Muitas converses de tipo sao feitas automaticamente em C, mas em alguns casos, como
na motivaao que antecede esta seao, preciso fazer uma conversao forada. Esse tipo
de converso explcita tambm conhecido pelo nome tcnico (em ingls) de casting. Ela
feita da seguinte maneira:
(novo_tipo) variavel_ou_valor
Por exemplo, para calcular a razao (fracionaria) entre dois inteiros fornecidos pelo
usuario, poderamos converter um deles para o tipo oat e realizar a divisao:
inl o b
fiool x
/* (...} ieiluro dos nmeros */
x = (fiool}o / b
prinlf("xf`n" x}
Note, porm, que o operador de conversao de tipos so atua sobre a variavel que vem
imediatamente depois dele por exemplo, se quisssemos converter uma expressao in-
teira, precisaramos de parnteses:
x = (fiool}(o / b} / c
Funes matemticas 61
Nesse caso, seria realizada a divisao inteira entre a e b, e depois seu quociente seria dividido
por c, numa divisao racional.
4.4 Funes matemticas
Emdiversas areas, as quatro operaes basicas nao cobremtodas as contas que precisamser
feitas em um programa. E muito comum, mesmo que nao estejamos lidando diretamente
com matematica, precisar das funes trigonomtricas, raiz quadrada, exponencial, entre
outras. Todas essas funes de uso comum constam no padrao da linguagem C e fazem
parte da biblioteca matematica padrao. Para usa-las em um programa, precisamos de mais
uma instruao no comeo do arquivo de codigo:
#inciude <molh.h>
Ao compilar com o GCC, tambm necessario incluir a biblioteca matematica na linha
de comando da compilaao; isso feito com a opao -im. (Na versao do MinGW para
Windows, devido diferena de organizaao do sistema, isso nao necessario.)
Uma lista completa das funes disponveis nesse arquivo pode ser facilmente encon-
trada nas referncias sobre a biblioteca padrao; na tabela 4.4 sao listadas apenas as princi-
pais. A maioria das funes aqui listadas recebe apenas um parametro, exatamente como
esperado; casos excepcionais serao indicados.
Tabela 4.4: Principais funes matematicas da biblioteca padrao
Funo Signicado
sin, cos, lon Funes trigonomtricas: seno, cosseno e tangente. Os angulos
sao sempre expressos em radianos.
osin, ocos, olon Funes trigonomtricas inversas. osin e olon devolvem um an-
gulo no intervalo
[

2
,

2
]
; ocos devolve umangulo no intervalo
[0, ].
sinh, cosh, lonh Funes hiperbolicas (seno, cosseno e tangente)
sqrl Raiz quadrada (square root)
exp Funao exponencial (e
x
)
iog Logaritmo natural, base e (ln)
iog10 Logaritmo na base 10
obs, fobs Modulo (valor absoluto) de um nmero. Use obs para inteiros e
fobs para nmeros de ponto futuante.
poW(x y} Potenciaao: x
y
(x e y podem ser nmeros de ponto futuante)
4.4.1 Clculo de sries
Varias das funes implementadas na biblioteca matematica podemser calculadas por meio
de expanses em srie (somatorias infnitas); por exemplo, a funao exponencial:
e
x
=

k=0
x
k
k!
= 1 + x +
x
2
2
+
x
3
3!
+
x
4
4!
+
62 Captulo 4. Mais sobre nmeros
E claro que, em um computador, impossvel calcular todos os termos dessa srie para
chegar ao resultado; apos um certo ponto, devido convergncia das sries, o valor de cada
termo muito pequeno, de modo que, frente precisao do computador, soma-los nao faz
mais diferena. Dessa maneira, devemos somar um nmero fnito Nde termos dessa srie.
Mas como escolher N: Seguem alguns dos critrios possveis:
Escolher umN fxo. Esse critrio nao muito bom, porque a precisao at o N-simo
termo costuma depender do argumento x; em varios casos, para valores grandes de
x necessario somar um nmero maior de parcelas, enquanto, para x bem pequeno,
poucas parcelas ja dao uma aproximaao muito boa.
Limitar a magnitude das parcelas a serem somadas interromper a soma quando o
modulo da parcela for menor que umdado (por exemplo, = 10
9
). Esse critrio
melhor que o anterior, mas nao leva emconta a magnitude do resultado; por exemplo,
se o valor da funao for da ordem de 10
9
, uma parcela de 10
9
realmente nao faz
diferena; se o valor da funao for da ordemde 10
6
, a mesma parcela de 10
9
ainda
signifcativa.
Podemos modifcar um pouco esse critrio levando isso em conta: em vez de limitar
o tamanho de cada parcela, limitaremos o tamanho da parcela dividido pelo valor
acumulado at ento. Se dividirmos pela soma do passo anterior, que nao inclui essa
parcela, obtemos a quantidade conhecida como variao relativa se S
n
indica a
soma das primeiras n parcelas, a (n + 1)-sima parcela corresponde a S
n+1
S
n
,
e entao o critrio de limitaao na variaao relativa traduz-se matematicamente em

S
n+1
S
n
S
n

< ,
parando no passo n+1 se essa condiao for satisfeita. Esse critrio bastante usado
em diversos mtodos numricos.
Devido precisao do computador, em algum momento as parcelas fcarao tao pe-
quenas que absolutamente nao irao mais alterar a soma por exemplo, se estamos
trabalhando com 3 algarismos signifcativos, somar 0,0001 no nmero 100 nao faz
a menor diferena. Assim, podemos comparar a soma de cada passo com a soma
anterior, e interromper o processo quando as somas forem iguais.
No entanto, necessario tomar cuidado compossveis otimizaes do compilador
como voc somou uma parcela nao-nula, logicamente a soma deve ter-se alterado;
portanto, com certas opes de otimizaao ativadas, o compilador adianta a res-
posta da comparaao entre as duas somas e diz que elas sao diferentes, sem verifcar
se a variaao realizada estava realmente dentro da precisao do ponto futuante. O
resultado disso um lao infnito.
4.4.2 Denindo constantes
A linguagem C permite que voc crie apelidos para constantes que voc usa no seu pro-
grama. Isso ajuda muito a manter o codigo organizado, pois evita que o codigo fque cheio
de nmeros anonimos difceis de entender. Alm disso, quando utilizamos uma cons-
tante pelo nome, ganhamos uma grande fexibilidade: se precisamos alterar o valor dela,
O tipo char 63
podemos alterar simplesmente a sua defniao, e a alteraao refetir-se-a emtodos os lugares
onde a constante foi utilizada.
Para defnir constantes, utilizamos o comando #define, da seguinte maneira:
#dene CONSTANTE valor
As restries sobre os nomes das constantes sao as mesmas que para nomes de variaveis e
funes. Para utilizar essas constantes,
Um exemplo de aplicaao disso: suponha que voc criou um programa que mostra o
cardapio de uma pizzaria, recebe um pedido e calcula o valor total. Agora a pizzaria quer
que voc refaa o programa pois os preos foramreajustados. Se voc tiver escrito os preos
diretamente no meio do codigo do programa, voc tera bastante trabalho procurando os
preos e modifcando em diversos lugares. No entanto, se voc defnir constantes como
PREC0MARC|ERTTA e PREC0P0RTuCuESA e usa-las no codigo, voc so precisara alterar a
defniao das constantes.
#inciude <sldio.h>
#define PREC0MuSSARELA 15.00
#define PREC0MARC|ERTTA 16.00
#define PREC0P0RTuCuESA 1.50
void imprimecordopio(}
{
prinlf (
"Mussoreio x5.Zf`n"
"Morgherilo x5.Zf`n"
"Porlugueso x5.Zf`n"
PREC0MuSSARELA PREC0MARC|ERTTA PREC0P0RTuCuESA}
]
/* 0eixorei como exerccio poro voc pensor o porle de regislror um
* pedido. Wo verdode por enquonlo voc pode se preocupor openos em
* coicuior o preo loloi.
* Sugeslo. use um lerminodor poro poder sober quondo o pedido ocobo.
*/
4.5 Otipo char
Voc ja conheceu trs dos quatro tipos fundamentais da linguagem C. Falta apenas um, o
tipo char que ja mencionamos, mas nao tivemos a oportunidade de usar. Como o nome
sugere, ele designado para guardar caracteres (como o Y ^ { e # x). No entanto, ele
apenas mais um tipo inteiro, com um intervalo de valores permitidos mais modesto que
o de um inteiro comum. Isso ocorre assim porque, para um computador, um caractere
apenas um nmero; caracteres sao codifcados como nmeros de acordo com uma tabela
de correspondncia por exemplo, a letra A maiscula armazenada como 63, o cifrao S
como 36, etc. Esses exemplos sao os codigos da conhecida tabela ASCII, que a base do
padrao atual para a codifcaao de caracteres.
64 Captulo 4. Mais sobre nmeros
Uma variavel do tipo char pode conter um caractere (apenas um!), e caracteres sao
representados entre aspas simples (nao confunda comas aspas duplas como as que voc usa
nas funes printf e scanf). Alguns exemplos: o, Z, e. Um exemplo de variavel do
tipo chor seria:
chor opcoo
opcoo = b
Ha alguns caracteres que nao sao representados literalmente, mas por meio de codigos
especiais. Um deles ja lhe foi apresentado, a saber, o caractere de quebra de linha, repre-
sentado pela sequncia `n. Alguns outros exemplos sao apresentados na tabela 4.3, junto
com os codigos numricos correspondentes na tabela ASCII.
Tabela 4.5: Algumas das sequncias utilizadas para representar caracteres especiais.
Sequncia ASCII Signicado
`l 9 Tabulaao. Avana o cursor para posies pr-defnidas ao longo
da linha; usualmente, essas posies sao defnidas de 8 em 8 ca-
racteres a partir da primeira posiao da linha.
`n 10 Quebra de linha. Esse caractere comumente conhecido pela si-
gla NL, new line, ou por LF, line feed.
`r 13 Retorno de carro (CR, carriage return): retorna o cursor para o
incio da margem esquerda. (Ver observaao adiante.)
`" 34 Aspa dupla.
` 39 Aspa simples.
`` 92 Barra invertida.
`nnn Permite especifcar qualquer caractere pelo seu codigo em base
octal (de 000 a 377). Cada n um dgito de 0 a 7.
`xnn Idem, em base hexadecimal (de 00 a FF). Cada n um dgito de
0 a 9 ou uma letra de A a F (maiscula ou minscula).
Os caracteres CR e LF (alm de outros que nao foram indicados aqui) sao uma herana
da poca das maquinas de escrever. Para comear a escrever texto na linha seguinte, eram
necessarias duas aes: retroceder o carro de impressao para o comeo da margem (CR)
e alimentar mais uma linha de papel (LF). Em sistemas Windows, o fnal de uma linha de
texto usualmente indicado pela sequncia CR + LF, ao invs de simplesmente LF, como
de praxe nos sistemas Unix (enquadram-se aqui o Linux e o Mac OS X). Nos sistemas Mac
antigos usava-se apenas CR, sem LF.
Veja que os caracteres " ` precisam de uma representaao alternativa: as duas aspas
porque podemos querer representa-las como caracteres sem que sejam interpretadas como
o fnal da sequncia de caracteres; a barra invertida porque ela propria usada em outras
sequncias de caracteres especiais.
4.5.1 Entrada e sada
Para ler e imprimir variaveis do tipo char, voc pode usar o codigo de formato xc nas fun-
es printf e scanf. Por exemplo:
chor opcoo
O tipo char 63
sconf("xc" &opcoo}
prinlf("voc escoiheu o opo (xc}!`n" opcoo}
inl opcoo
opcoo = gelchor(}
pulchor(o}
E importante notar que, tanto com a funao scanf quanto com a getchar, os caracteres
digitados nao sao recebidos pelo programa em tempo real. Cada vez que voc digita um
caractere, ele armazenado temporariamente numa regiao da memoria chamada buer
de teclado, e so apos o fnal da linha que o contedo do buer liberado para o nosso
programa, atravs dessas funes.
5
Ponteiros e vetores
5.1 Prolegmenos
Num computador, cada variavel guardada em uma certa posiao da memoria. Essas po-
sies de memoria sao numeradas, de modo que cada uma tem um endereo numrico
como se cada uma fosse uma casa em uma rua.
Em C, um ponteiro (tambm chamado de apontador) uma variavel que guarda uma
referncia a outra variavel seu valor o endereo de uma outra variavel. Se o valor de
um ponteiro p o endereo de uma variavel X, entao dizemos que p aponta para X. Veja
uma ilustraao disso na fgura 3.1. Se um ponteiro aponta para uma variavel, voc pode
acessar essa variavel (ler ou alterar seu valor) atravs do ponteiro logo veremos como.
Figura 5.1: Esquema do funcionamentode umponteiro. Oponteirop contmo endereo da variavel
n (1032), ou seja, p aponta para n.
Isso pode parecer apenas uma maneira de complicar as coisas, mas na realidade tem
diversas utilidades, das quais citamos algumas:
Quando precisamos transmitir uma grande quantidade de dados a outra parte do
programa, podemos passar apenas umponteiro para esses dados emvez de fazer uma
copia dos dados e transmitir a copia. Isso economiza tempo o processo de duplicar
os dados gasta tempo de processamento e, obviamente, espao na memoria.
Uma funao em C so pode devolver um valor com a instruao return. No entanto,
se uma funao recebe como parametros ponteiros para outras variaveis, voc podera
gravar valores nessas variaveis, e com isso uma funao pode gerar varios valores de
sada.
67
68 Captulo 5. Ponteiros e vetores
O conceito de ponteiros pode ser estendido para funes possvel passar um
ponteiro para uma funo como parametro. Por exemplo, podemos criar uma funao
chamada ochoroiz com um algoritmo numrico que acha razes de uma funao
matematica f; a funao f poderia ser passada (na forma de ponteiro) como parametro
da funao ochoroiz.
5.1.1 Declarao de ponteiros
Em C, para declarar uma variavel que funciona como ponteiro, colocamos um asterisco (*)
antes do seu nome. Umponteiro so pode apontar para umtipo de variavel, ja que a maneira
de armazenar o valor de uma variavel na memoria depende do seu tipo. Por exemplo, um
ponteiro que aponta para uma variavel inteira nao pode ser usado para apontar para uma
variavel de ponto futuante. Assim, um ponteiro tambm precisa de um tipo, que deve ser
igual ao tipo de variavel para a qual ele ira apontar.
Por exemplo, se queremos criar umponteiro p que ira apontar para uma variavel inteira,
declaramo-no da seguinte maneira:
inl *p
Essa instruao apenas declara um ponteiro, sem aponta-lo para nenhuma variavel.
Se quisermos declarar varios ponteiros com uma nica instruao, devemos colocar o
asterisco em cada um deles. Se voc escrever o asterisco apenas no primeiro nome, apenas
a primeira variavel sera um ponteiro!
inl *p1 *pZ *p3 /* ok os lrs so ponleiros */
doubie *p4 p5 p6 /* probiemo! so p4 sero um ponleiro */
Para fazer um ponteiro apontar para uma variavel, devemos atribuir-lhe como valor o
endereo de outra variavel, e nao um nmero comum. Para isso, usamos o operador &
(E comercial), que fornece o endereo de uma variavel aqui ele sera conhecido como o
operador endereo-de. Se temos uma variavel var, seu endereo representado por &vor.
Por exemplo:
inl n
inl *p
p = &n
Aqui criamos uma variavel inteira chamada n, e em seguida criamos um ponteiro p, que
apontado para a variavel n.
5.1.2 Acesso indireto por ponteiros
Para acessar a variavel que apontada por um ponteiro, usamos o operador * (o mesmo
asterisco usado na declaraao), chamado operador de indireo ou operador de desreferen-
ciao. Esse operador faz a volta do processo que leva da variavel ao ponteiro (a refe-
renciao da variavel); por isso o chamamos de operador de desreferenciaao. O nome
indireao usado simplesmente porque isso um acesso indireto variavel.
Esse um termo difcil de se traduzir. Em ingls, diz-se dereference, que seria uma combinao do prexo
de- (equivalente, nesse caso, ao nosso des-) e da palavra que signica referncia, no sentido de desfazer
uma referncia. Muitas pessoas tentam traduzir isso como de-referncia, mas essa palavra no consta nos
dicionrios. (Tudo bem, desreferenciao tambm no, mas eu achei melhor.)
Prolegmenos 69
Nomenclaturas parte, se p um ponteiro, podemos acessar a variavel para a qual ele
aponta com*p. Essa expressao pode ser usada tanto para ler o contedo da variavel quando
para altera-lo.
var p
&var
*p

Figura 5.2: Ilustraao da relaao entre ponteiros e variaveis.


Por exemplo,
inl n *p /* vego que podemos decioror os dois */
p = &n /* num mesmo comondo */
*p = 5
prinlf("n = xd`n" n} /* imprime 5 */
n = 10
prinlf("*p = xd`n" *p} /* imprime 10 */
Vemos, entao, que acessar umponteiro para uma variavel , de certa forma, equivalente
a acessar a variavel apontada. Podemos tambm mudar a variavel para a qual um ponteiro
aponta, e a partir da as operaes com o ponteiro so afetarao a variavel nova:
inl o b *p
p = &o
*p = 5
prinlf("o = xd`n" o} /* imprime 5 */
p = &b
*p = 10
prinlf("o = xd`n" o} /* oindo imprime 5 */
prinlf("b = xd`n" b} /* imprime 10 */
Desreferenciao ou multiplicao? O leitor atento deve ter-se perguntado se o fato de o
asterisco dos ponteiros ser o mesmo asterisco da multiplicaao nao gera nenhumproblema.
Ede fato nao ha nenhumproblema; podemos inclusive multiplicar os valores apontados por
dois ponteiros, por exemplo:
c = *p1 * *pZ
70 Captulo 5. Ponteiros e vetores
Esse codigo teria o efeito de obter os valores apontados por p1 e p2, multiplica-los e guardar
o resultado em c, como poderamos esperar.
Isso ocorre porque os operadores de desreferenciaao sao sempre interpretados antes
dos de multiplicaao. Apos a interpretaao das expresses *p1 e *pZ, o que sobra sao os dois
valores apontados pelos ponteiros, com um asterisco entre eles; isso so pode signifcar uma
multiplicaao se o asterisco do meio fosse atuar como operador de desreferenciaao, a
expressao fcaria invalida, alm de ele estar agindo sobre algo que ja nao seria um ponteiro!
Apesar do codigo mostrado acima ser valido e inambguo (para um computador),
recomendavel que voc use parnteses quando tiver de fazer esse tipo de coisa isso deixa
o codigo bem mais legvel:
c = (*p1} * (*pZ}
5.2 Ponteiros como parmetros de funes
Uma grande utilidade dos ponteiros a possibilidade de se modifcar as variaveis que foram
passadas como parametros para uma funao. Anteriormente vimos um exemplo de uma
funao que tenta (semsucesso) trocar o valor de duas variaveis; semusar ponteiros, a tarefa
era impossvel, pois os valores dos parametros de uma funao sao sempre copiados, de modo
que ela nao tem acesso s variaveis originais.
Agora que conhecemos ponteiros, a tarefa fca facil. Se queremos que uma funao mo-
difque uma variavel, basta passar a ela um ponteiro para a variavel, em vez de passar o
valor da variavel (que o comportamento padrao). Para de fato modifcar a variavel den-
tro da funao, devemos usar o operador de indireao para trocar os valores apontados pelos
ponteiros (senao, mudaramos apenas o lugar para o qual o ponteiro aponta, o que nao nos
interessa).
Vamos comear com um exemplo bem simples, que apenas dobra o valor de uma va-
riavel. No cabealho da funao, o ponteiro especifcado da mesma maneira que nas de-
claraes de variavel com um asterisco entre o tipo e o nome do parametro.
void dobrovoriovei(inl *vor}
{
*vor = (*vor} * Z
]
Para chamar essa funao, usamos o operador & para passar o endereo de uma variavel.
Veja que voc nao pode passar um nmero (uma constante) diretamente para essa funao,
pois ele nao tem um endereo! Outro detalhe que deve ser levado em conta que a variavel
apontada deve ser inicializada antes de chamarmos a funao, ja que a funao se baseia no
valor que havia na variavel.
inl moin(}
{
inl num
num = 10
dobrovoriovei(&num}
prinlf("xd`n" num} /* Z0 */
Ponteiros como parmetros de funes 71
relurn 0
]
Com esse exemplo em mente, nao devemos ter difculdades para montar uma funao
que de fato troca o valor de duas variaveis (essa funao, na pratica, nao incrivelmente til,
mas boa para ilustrar esse conceito):
void lroco(inl *o inl *b}
{
inl lemp = *o
*o = *b
*b = lemp
]
Naturalmente, tambm sera necessario alterar a chamada funao troca, ja que agora
precisamos passar os endereos das variaveis e nao mais os seus valores. Para chama-la,
escreveremos algo como o seguinte:
inl m n
m = 10
n = Z0
lroco(&m &n}
Em computaao, comum usar os nomes chamada por valor e chamada por referncia;
eles indicam se a funao chamada recebe apenas uma copia do valor da variavel, ou se
recebe uma referncia (um ponteiro) para a variavel. Em C, as chamadas de funes sao
intrinsecamente por valor, mas podemos usar ponteiros para obter o comportamento das
chamadas por referncia.
5.2.1 Usando ponteiros para devolver valores
Em muitos casos, uma funao pode ter mais de uma sada. Como uma funao so pode
devolver um nico valor atravs da instruao return, estamos um pouco limitados. No
entanto, se passarmos a uma funao o endereo de uma variavel, a funao pode gravar di-
retamente sua sada naquela variavel.
Vamos rever o exemplo da equaao de segundo grau ax
2
+bx+c = 0, comcoefcientes
reais. Sabemos que, de acordo com o sinal do discriminante = b
2
4ac, pode haver
duas, uma ou nenhuma raiz real. Vamos escrever uma funao que distingue entre esses
trs casos, como ja fzemos na pagina 38, e calcula as razes reais da equaao quando for
o caso. A funao devera devolver o nmero de razes reais (distintas), e gravar as razes
encontradas (quando for o caso) em variaveis fornecidas pelo usuario. Veja nossa soluao:
inl equocooquodrolico (doubie o doubie b doubie c doubie *roiz1
doubie *roizZ}
{
doubie deilo
deilo = b*b - 4*o*c
if (deilo < 0} {
72 Captulo 5. Ponteiros e vetores
relurn 0
]
eise if (deilo == 0} {
*roiz1 = -b/(Z*o}
relurn 1
]
eise {
*roiz1 = (-b - sqrl(deilo}} / (Z*o}
*roizZ = (-b + sqrl(deilo}} / (Z*o}
relurn Z
]
]
5.3 Cuidado comos ponteiros!
Um grande poder exige uma grande responsabilidade. Os ponteiros sao uma ferramenta
muito poderosa da linguagem C, mas devem ser usados com muita cautela. Se um pon-
teiro nao estiver apontando para o lugar que voc espera, coisas terrveis podem ocorrer.
E comum que apaream bugs ou erros esquisitos no seu programa simplesmente porque
voc esta acessando um ponteiro invalido. Dizemos que um ponteiro invlido, em geral,
quando ele aponta para uma posiao de memoria que nao uma variavel do seu programa.
Isso costuma ocorrer em situaes como as seguintes:
Voc nao inicializou o ponteiro (isto , nao atribuiu nenhum valor a ele), de modo
que nao se tem idia do lugar da memoria para o qual ele aponta.
Voc apontou o ponteiro para umendereo arbitrario, que nao pertence ao seu pro-
grama. Por exemplo, se voc tentar atribuir o valor 300 ao seu ponteiro, ele apontara
para a posiao 300 da memoria do seu computador nao temos idia do que pode
haver nesse lugar.
Seu ponteiro caiu num lugar invalido da memoria, apesar de ter sido inicializado
corretamente. Isso ocorre mais frequentemente quando voc esta variando atravs
de um lao (for, while) a posiao apontada pelo ponteiro (veremos isso mais adiante)
e nao para na hora certa alguma hora, seu ponteiro acaba apontando para um
lugar que nao deveria.
Por exemplo, voc no deve fazer nada parecido com isso:
inl *p
*p = 5 // erro lerrvei!!
Oque ocorreu aqui que voc criou umponteiro mas nao defniu para onde ele aponta.
Seu valor inicial sera aleatorio, e portanto, ao tentar atribuir um valor variavel apontada
pelo ponteiro, voc estara acessando uma posiao aleatoria da memoria. Repito: no faa
isso!
Geralmente, quando voc tentar acessar ponteiros invalidos, o programa ira dar um
erro durante a execuao e fechar. No Linux, voc vera algo do tipo Segmentation fault
ou Falha de segmentaao (isso quer dizer que voc acessou um segmento de memoria
Vetores 73
invalido; tem a ver com a organizaao interna da memoria). No Windows, voc devera ver
uma das famosas e genricas mensagens Este programa executou uma operaao ilegal e
sera fechado.
As vezes tambm pode acontecer de o programa continuar funcionando mesmo com
um erro desses; os sintomas serao mais sutis, como valores inesperados nas suas variaveis,
e a que esses erros sao mais difceis de rastrear. Portanto, olho vivo ao usar ponteiros!
5.4 Vetores
Vetores sao uma maneira pratica de guardar um grande conjunto de variaveis relacionadas
por exemplo, sequncias de nmeros. EmC, umvetor uma srie de variaveis indexadas
que podemser acessadas por meio de umndice inteiro, por exemplo velor_4]. Ha uma
pequena restriao: um vetor so guarda variaveis do mesmo tipo ou seja, voc pode fazer
um vetor de inteiros e um vetor de nmeros de ponto futuante, mas nao um vetor que
contenha ambos.
Ha varios termos diferentes para se referir aos vetores. E muito comum encontrar array, que
o termo original em ingls; tambm vemos matrizes, termo que prefro reservar apenas aos
vetores multidimensionais, como veremos mais tarde. O termo lista tambm possvel, mas
pouco usado em C (ele pode ser usado em outro contexto semelhante, sendo mais comum em
outras linguagens).
Essas variaveis sao todas guardadas sequencialmente (sem buracos) na memoria e, em
um vetor de n elementos, sao identifcadas por ndices de 0 a n 1 (veja a fgura 3.3). Em
C, podemos nos referir ao elemento de ndice i de um vetor V pela expressao v_i].
...
V[0]
. .
V[1]
.

. .
V[n 1]
Figura 5.3: Ilustraao do modo de armazenamento de um vetor com n elementos.
Para usar um vetor, precisamos primeiro declara-lo, como era feito para qualquer va-
riavel normal. A declaraao de um vetor feita da seguinte maneira:
tipo_de_dados nome_vetor[tamanho];
O compilador entende essa frase como: reserve na memria um espao para tamanho
variveis do tipo tipo_de_dados, e chame esse espao de nome_vetor. Veja dois exemplos
desse tipo de declaraao:
inl sequencio_40]
doubie nolos_100]
E importante ressaltar que o compilador apenas reserva o espao de memoria pedido,
sem colocar nenhum valor especial nele. Isso signifca que o vetor contera inicialmente
uma seleao aleatoria de valores (que sobraramda execuao de algumprograma que usou
aquele espao), exatamente como ocorria para as variaveis comuns.
Voc deve prestar atenao a alguns detalhes do funcionamento dos vetores em C:
74 Captulo 5. Ponteiros e vetores
Os elementos de um vetor sao numerados a partir de zero. Dessa maneira, num
vetor V que tem 3 elementos, os elementos sao: V[0], V[1], V[2], V[3] e V[4]. Nao
se confunda com a declaraao! Um tal vetor seria declarado com uma instruao do
tipo inl v_5], mas o elemento v_5] nao existiria!
E realmente necessario tomar bastante cuidado com a numeraao dos elementos. Se
voc tentar acessar umelemento invalido, como V[5] ou V[100] (para este caso com
3 elementos), o compilador naoo avisara disso, e erros estranhos comearaoa ocorrer
no seu programa o mesmo tipo de erro que pode ocorrer com os ponteiros.
O tamanho deve ser um valor constante (nao pode depender de valores de varia-
veis). Ou seja, voc nao pode perguntar ao usuario o tamanho desejado, guardar
numa variavel n e depois declarar um vetor do tipo inl v_n]. Em outras palavras, o
tamanho do vetor deve ser umvalor que possa ser estabelecido na hora da compilao
do programa.
Por isso, quando for necessario ler uma quantidade de dados que so estipulada
na execuao do programa, a princpio teremos de estabelecer um teto no nmero de
dados a seremlidos, usando umvetor de tamanho fxo. Caso o teto nao seja atingido,
algumas entradas do vetor fcarao sem ser utilizadas.
E possvel, sim, criar vetores cujo tamanho so conhecido a posteriori; no padrao
C99 possvel fazer declaraes do tipo inl v_n] com algumas restries. Ha outro
recurso, de certa maneira mais fexvel, que permite criar vetores cujo tamanho so
conhecido na execuao do programa: a alocao dinmica de memria, que sera vista
no Captulo 7.
Almde constante, o tamanho dos vetores imutvel, ou seja, se eu declarei umvetor
de 3 entradas, eu nao posso aumenta-lo para que caibam 10 entradas. Se eu quero
que caibam10 entradas, eu preciso reservar espao para 10 entradas logo no comeo.
(Novamente, a alocaao dinamica de memoria salva a patria nesse aspecto; no cap-
tulo 7, voc vera o que possvel fazer quanto a isso.)
Podemos acessar os elementos de um vetor, em geral, tratando-os como se fossem va-
riaveis normais. O operador de endereo tambm pode ser usado com os elementos indi-
viduais do vetor. Por exemplo,
inl iislo_3]
sconf("xd" &iislo_0]}
iislo_1] = 37
iislo_Z] = iislo_1] - 3*iislo_0]
prinlf("xd xd xd`n" iislo_0] iislo_1] iislo_Z]}
Dito isso, vamos ver algumas aplicaes simples do uso de vetores.
Eximvio ,.1. Vamos fazer um programa que l uma lista de n nmeros (n 100) e os
imprime na ordeminversa (emrelaao ordememque foramlidos). Para isso, necessario
Isso no apenas uma extravagncia do C; muitas linguagens funcionam dessa maneira, que a mais
natural para um computador logo mais veremos por qu.
Vetores 73
armazenar cada umdos nmeros lidos antes de comear a imprimi-los isso nao possvel
(a nao ser com um codigo extremamente pedestre e propenso a erros) com o que tnhamos
aprendido antes.
#inciude <sldio.h>
inl moin(}
{
inl iislo_100]
inl i n
prinlf("0igile o nmero de eiemenlos (no moximo 100}. "}
sconf("xd" &n}
if (n > 100} {
prinlf("n no pode ser moior que 100! So ierei os "
"100 primeiros nmeros.`n"}
n = 100
]
prinlf("0igile o seguir os xd eiemenlos.`n" n}
/* ieiluro dos eiemenlos */
for (i = 0 i < n i++}
sconf("xd" &iislo_i]}
/* impresso dos eiemenlos no ordem inverso */
for (i = n-1 i >= 0 i--}
prinlf("xd " iislo_i]}
prinlf("`n"}
relurn 0
]
Umaspecto que ainda nao reforamos foi a validaao da entrada do usuario. Voc pode
pedir encarecidamente que o usuario digite um nmero at 100, mas nada garante que o
usuario nao sera desonesto ou distrado e digite um nmero fora dos limites pedidos. Nes-
sas horas, voc no deve conar no usurio, e deve verifcar se o valor digitado valido, para
evitar que aconteam coisas mas em seu programa nesse caso, se o usuario digitasse um
nmero maior que 100 e nao fzssemos essa verifcaao, acabaramos acessando posies
invalidas do vetor (acima do ndice 99).
5.4.1 Inicializao de vetores
Em algumas situaes voc precisara usar vetores cujo contedo seja determinado inici-
almente por voc, e nao lido do teclado ou de algum arquivo. Obviamente seria muito
cansativo ter de inicializar elemento por elemento, como a seguir:
inl iislo_100]
iislo_0] = 9
76 Captulo 5. Ponteiros e vetores
iislo_1] = 35
.
.
.
iislo_99] = -1
Felizmente, o C permite que voc inicialize os valores de um vetor junto com a decla-
raao (assim como de uma variavel escalar comum). Isso feito da seguinte maneira:
tipo_de_dados nome_vetor[tamanho] = { lista de valores ;
Na realidade, voc nao precisa escrever o tamanho explicitamente se especifcar todos os
elementos o compilador simplesmente contara quantos elementos voc digitou ( neces-
sario, no entanto, manter os colchetes, para que o compilador saiba que isso um vetor).
Assim, o nosso arduo exemplo poderia ser escrito da seguinte maneira (vamos reduzir um
pouco o tamanho do vetor, so para nao precisarmos digitar tantos elementos):
inl iislo_] = {9 35 -17 9 -4 Z9 -Z 10 -1]
Note que isso so possvel na hora da declaraao. Nao possvel, fora desse contexto,
defnir de uma so vez todos os elementos do vetor, como a seguir ( necessario atribuir
elemento por elemento):
/* islo eslo errodo! */
iislo = {7 4Z 0 -1 3 110 57 -43 -11]
Tambm possvel declarar um vetor de um certo tamanho mas so inicializar parte
dele (desde que seja a parte do comeo). Por exemplo, suponha que queremos espao para
uma sequncia de 100 inteiros, mas que vamos comear inicializando apenas os 10 primei-
ros. Nesse caso, escrevemos explicitamente o tamanho do vetor, mas so especifcamos os
elementos desejados:
inl iislo_100] = {9 35 -17 9 -4 Z9 -Z 10 -1 47]
Nesse caso, os elementos que nao foram especifcados serao automaticamente inicializados
com o valor 0.
5.5 Vetores como argumentos de funes
Nao ha, a princpio, problema nenhum em passar vetores como argumentos para uma fun-
ao. O que acontece geralmente que vetores podem ser bem grandes, e portanto nao se-
ria muito pratico copiar todos os valores para a funao; por isso, vetores sao naturalmente
passados por referncia para as funes ou seja, quando voc passa um vetor para uma
funao, ela na verdade recebe apenas o endereo dos dados; quando sua funao for acessar
os dados do vetor, ela sera apontada diretamente para a posiao deles no vetor original.
Com isso, qualquer modifcaao num vetor passado como parametro refetida na funao
original que passou o vetor.
Uma consequncia da passagem por referncia dos vetores que nao possvel passar
para uma funao umvetor livre, ou seja, uma lista de valores ad hoc que nao esta vinculada
a uma variavel na pratica, isso quer dizer que nao possvel escrever coisas do tipo
Vetores como argumentos de funes 77
funcoo({Z 3 4 5]}
pois o vetor livre nao possui um endereo a ser passado para a funao.
Vamos primeiro ver na pratica como podemos passar vetores como argumentos de fun-
es. Quanto ao cabealho da funao, basta imitar a declaraao de vetores, exceto por um
detalhe: no devemos fornecer o tamanho do vetor os colchetes devem ser deixados sozi-
nhos, sem nada no meio.' A funao a seguir recebe um vetor de inteiros como parametro
e imprime seu primeiro elemento:
void imprimeprimeiro(inl v_]}
{
prinlf("xd`n" v_0]}
]
Para mandar um vetor como parametro de uma funao, voc simplesmente deve escre-
ver o nome dele, sem colchetes ou qualquer outro smbolo:
inl velor_] = {1 Z 3 4]
imprimeprimeiro(velor} /* 1 */
Agora, se a funao nao impe nenhum tamanho para o vetor, como vamos descobrir
qual o tamanho do vetor que a funao recebeu: A resposta que a funao nao tem como
descobrir isso sozinha; em geral, voc quem deve cuidar disso. Por exemplo, voc pode
passar para a funao dois parametros: o vetor e o tamanho do vetor. Uma ilustraao disso
uma funao que imprime todos os elementos de um vetor:
void imprimevelor(inl v_] inl n}
{
inl i
for (i = 0 i < n i++}
prinlf("xd`n" v_i]}
]
Agora, para chama-la, devemos incluir o tamanho do nosso vetor original:
inl velor_] = {1 Z 3 4]
imprimevelor(velor 4}
Da mesma maneira, poderamos fazer uma rotina que l do teclado uma srie de n-
meros inteiros e armazena todos eles num vetor. Uma implementaao simples seria como
a seguir:
void ievelor(inl v_] inl n}
{
inl i
for (i = 0 i < n i++}
sconf("xd" &v_i]}
]
Se o tamanho do vetor for explicitamente fornecido, o compilador no deve reclamar, mas o programa
no ser forado de maneira alguma a respeitar esse tamanho.
78 Captulo 5. Ponteiros e vetores
Para chama-la, faramos da mesma maneira que no exemplo anterior:
inl velor_Z0]
ievelor(velor Z0}
Fornecer o tamanho correto do vetor funao de suma importancia! Se a funao
achar que seu vetor maior do que realmente , ela tentara acessar elementos invalidos
(depois do fm do vetor), o que ja vimos que um erro bem grave especialmente se a
funao tentar gravar dados nessas areas!
5.6 Ponteiros e vetores
Vimos que vetores sao naturalmente passados por referncia como parametros de funes.
Na verdade, vetores em C tm uma estreita relaao com ponteiros. O nome de um ve-
tor funciona, na pratica, como um ponteiro para seu primeiro elemento. Isso da conta de
explicar a passagem por referncia natural dos vetores: sabendo o endereo do primeiro
elemento, podemos encontrar todos os outros elementos do vetor.
Como fazer isso: Como os elementos sao guardados juntinhos na memoria, sem bu-
racos, basta saber o tamanho T de cada elemento (por isso que importante que o vetor
tenha valores de um tipo so; sabendo o tipo, sabemos o tamanho), por exemplo, em bytes.
Da, partindo da posiao do primeiro elemento, fazemos um salto de tamanho T tantas
vezes quanto for necessario para chegar ao elemento de ndice n, devemos saltar n ve-
zes. E por isso, tambm, que os ndices comeam de 0 e nao de 1: para o computador
mais intuitiva a noao de deslocamento (em relaao ao comeo do vetor) do que a noao de
posio.
Dado um ponteiro para o primeiro elemento de um vetor, facil realizar (em C) esse
salto: basta somar ao ponteiro o nmero de elementos desejado. Note que voc nao precisa
se preocupar em saber o nmero de bytes, apenas o nmero de elementos! Por exemplo,
se p aponta para o comeo de um vetor, p + 1 aponta para o segundo elemento, indepen-
dentemente de quantos bytes ocupa cada elemento. Da mesma maneira podemos usar os
operadores como ++ e += (e, analogamente, os de subtraao):
inl v_10]
inl *p
p = v /* foz o ponleiro oponlor poro o comeo do velor */
/* (equivoie o. p = &v_0]} */
p++ /* oponlo o ponleiro poro o segundo eiemenlo */
p = p+5 /* foz mois um soilo e oponlo poro o 7" eiemenlo */
Essas operaes componteiros sao conhecidas como aritmtica de ponteiros. Veja que
elas apenas alteram o local de destino dos ponteiros; elas nao mexem com os valores apon-
tados. Note tambm que nao existem, nem fariam sentido, as operaes de multiplicaao
e divisao de ponteiros.
Agora como usamos isso para acessar o i-simo elemento do vetor v: Aexpressao v + i
(ou p + i neste exemplo acima) certamente um ponteiro para ele, pelo que acabamos de
ver; entao podemos usar *(v+i} para acessa-lo. Como isso usado com muita frequncia,
existe uma abreviaao sintatica, que nada mais que v_i]. Assim, ao acessar elementos
de vetores, estamos o tempo todo utilizando a aritmtica de ponteiros!
Strings 79
Isso nos da uma nova maneira de caminhar pelos vetores: criar umponteiro que aponta
inicialmente para o comeo do vetor e aumenta-lo de uma unidade numlao for, por exem-
plo:
inl v_10]
inl *p
for (p = v /* condio */ p++} {
/* o eiemenlo oluoi do velor pode ser ocessodo
* simpiesmenle peio expresso *p. */
]
Aparentemente ha um problema: onde parar o lao se a posiao nao controlada por um
ndice: Veremos emseguida umtipo de vetor para o qual isso nao umproblema uma
soluao.
5.7 Strings
Uma das principais utilidades do tipo char usar vetores para formar sequncias de carac-
teres, conhecidas em computaao como strings (esse termo tornou-se tao difundido que
nao costuma ser traduzido; possveis tradues sao sequncias ou cadeias de caracteres).
Lembre-se de que caracteres nada mais sao que inteiros com um signifcado especial, co-
difcados atravs de uma tabela (em geral, a tabela ASCII e suas extenses). Em C, uma
string simplesmente um vetor de caracteres, com uma convenao especial: como o va-
lor zero nao utilizado por nenhum caractere, ele utilizado para marcar o fnal de uma
string, ou seja, ele o terminador da string. O caractere de codigo zero comumente
chamado de caractere nulo, e representado pela sequncia especial `0 ou simplesmente
pela constante inteira 0.
Sabendo disso, podemos declarar uma string da mesma maneira como declaramos ve-
tores:
chor slring_] = {0 i o `0]
Certamente nao nada pratico escrever assim! Felizmente essa notaao pode ser bas-
tante abreviada: emvez de escrever explicitamente umvetor de caracteres comumcaractere
nulo, podemos deixar mais clara a cara de string, colocando os caracteres desejados entre
aspas duplas, semseparaao entre eles, e o resto (inclusive o caractere nulo) fcara por conta
do compilador. (Veja que ja usamos essa sintaxe o tempo todo quando usamos as funes
scanf e printf!) O codigo acima poderia ser reescrito como a seguir, e as duas formas sao
absolutamente equivalentes:
chor slring_] = "0io"
Uma das grandes vantagens de usar o terminador `0 (explicita ou implicitamente)
que voc nao precisa se preocupar com o tamanho das strings: ao caminharmos pelos
caracteres de uma string, nao precisamos registrar a posiao em que estamos para saber
quando parar; so parar quando encontrarmos o caractere nulo. Com isso, em vez de per-
correr os caracteres de uma string usando umndice de umvetor, podemos usar apenas um
ponteiro. Veja um uso disso neste exemplo, que imprime os caracteres da string indicada,
um por linha:
80 Captulo 5. Ponteiros e vetores
chor slring_] = "Bom dio!"
chor *p
for (p = slring *p != 0 p++}
prinlf("Coroclere. xc`n" *p}
A condiao de parada do loop simplesmente o caractere nulo, que nao depende de
forma alguma do tamanho da string! Podemos usar isso, por exemplo, para achar o tama-
nho de uma string:
inl n = 0
chor slring_] = "Bom dio!"
chor *p
for (p = slring *p != 0 p++}
n++
prinlf("A slring lem comprimenlo xd`n" n}
5.7.1 Alterando strings
Lembre-se de que, se quisermos modifcar o valor dos elementos de um vetor comum,
necessario atribuir os valores elemento por elemento; para as strings isso nao haveria de
ser diferente. Outro fator que complica nossa vida o fato de vetores terem um tamanho
imutavel; se declararmos um vetor de um certo tamanho e precisarmos posteriormente de
mais espao, nao ha, a princpio, o que fazer. Tudo isso muito relevante pois, se queremos
trocar o contedo de uma string, muito provavel que seu tamanho precise mudar tambm.
Como lidar com essas limitaes:
A questao do tamanho pode ser contornada, para certos propositos, de maneira facil:
como uma string contm informaao sobre o seu proprio tamanho (indiretamente atravs
do terminador `0), podemos declarar um vetor que tenha mais espao do que o necessario
para guarda-la, e ainda sera facil distinguir onde a frase comea e termina. Por exemplo,
podemos declarar um vetor de 100 entradas, para guardar uma frase de apenas 20 caracte-
res, sendo a 21 entrada o terminador da string; as outras 79 entradas nao serao usadas.
Essa soluao nos permite alterar posteriormente o contedo do vetor para qualquer
sequncia de caracteres que nao ultrapasse o tamanho que defnimos inicialmente. O en-
fado de atribuir elemento por elemento pode ser eliminado graas funao strcpy, que
copia o contedo de uma string para outra. Mas como isso nos ajuda: Quando precisamos
passar um array comum como parametro para uma funao, nao possvel usar uma lista
de valores ad hoc, mas apenas o nome de uma variavel ja declarada. Entretanto, as strings
sao, de certa forma, privilegiadas: possvel usar uma expressao de string com aspas du-
plas nesse tipo de situaao (e em algumas outras tambm) tanto que ja as utilizamos
diversas vezes, particularmente com as funes printf e scanf. Deste modo, usa-se a funao
strcpy (string copy) para copiar para o vetor ja declarado uma string pr-fabricada por
exemplo:
chor mensogem_Z0]
slrcpy(mensogem "Bom dio!"}
Strings 81
Isso equivalente a copiar individualmente cada caractere da nossa string pr-fabricada
para a variavel de destino:
chor mensogem_Z0]
mensogem_0] = B
mensogem_1] = o
mensogem_Z] = m
mensogem_3] =
mensogem_4] = d
mensogem_5] = i
mensogem_6] = o
mensogem_7] = !
mensogem_] = 0
Note que o terminador tambm copiado! Assim, na string de destino, devemos garantir
que o espao seja sufciente para guardar todos os caracteres da string original mais um (o
terminador). Se o destino tem tamanho 20, podemos copiar no maximo uma string de 19
caracteres.
Observe tambm que nao utilizamos o operador de endereo com a nossa variavel tipo
string, pois ela um vetor e, como ja vimos, vetores sao naturalmente passados por refe-
rncia em parametros de funes.
Essa funao tambm pode, naturalmente, ser usada para copiar o contedo de uma
string para outra quando ambas estao contidas em vetores ja declarados:
chor origem_Z0] = "Boo noile!"
chor deslino_Z0]
slrcpy(deslino origem}
Como sempre, devemos ter cuidado para nao exceder o tamanho do vetor original. Se
tentassemos copiar uma mensagem muito longa para o vetor, nosso programa iria tentar
gravar dados fora da area reservada para o vetor, o que ja vimos que nao uma boa ideia.
Por isso, existe tambmuma outra funao, strncpy, que copia o contedo de uma string para
outra semexceder umcerto nmero maximo de caracteres (especifcado por umparametro
da funao):
strncpy(destino, origem, n mximo);
Por exemplo:
chor mensogem_Z0]
slrncpy(mensogem "Copiondo umo mensogem muilo iongo" Z0}
O resultado deste codigo sera copiar a sequncia Copiondo umo mensoge, com exatamente
20 caracteres, para a variavel mensagem. O problema aqui que, como a string de origem
era mais longa do que o destino poderia suportar, nao foi inserido nenhum terminador.
Nesse caso, o que podemos fazer inserir o terminador manualmente, e pedir para copiar
no maximo 19 caracteres:
chor mensogem_Z0]
slrncpy(mensogem "Copiondo umo mensogem muilo iongo" 19}
mensogem_19] = 0
82 Captulo 5. Ponteiros e vetores
Caso a origem tenha tamanho menor do que o tamanho maximo especifcado, o termina-
dor sera copiado automaticamente.
Ja vimos, na seao anterior, como possvel encontrar o tamanho de uma string. Como
esse codigo muito usado, existe uma funao da biblioteca padrao que faz exatamente a
mesma coisa: strlen (string length). Seu uso bem simples:
chor mensogem_50] = "umo mensogem muilo iongo"
prinlf("A mensogem lem comprimenlo xd.`n" slrien(mensogem}}
Note que a funao devolve o comprimento da string (24), e nao o comprimento do vetor
que foi usado para guarda-la (que 30).
Outra tarefa muito comum descobrir se duas strings sao iguais. Como strings sao
vetores, que sao ponteiros, uma comparaao do tipo s1 == sZ compara os endereos em
que estao guardadas as strings. Obviamente essa comparaao so verdadeira quando com-
paramos uma string com ela mesma; nosso objetivo comparar duas strings distintas que,
no entanto, possam apresentar o mesmo contedo.
Para isso, devemos comparar as strings elemento a elemento; convenientemente ha uma
funao que ja incorpora esse trabalho, strcmp (string compare). Dadas duas strings s
1
e s
2
,
slrcmp(s1 sZ} devolve o valor zero caso elas sejam iguais, e um valor diferente de zero
caso sejam diferentes. Esse valor tambm serve para determinar a ordem lexicografca (a
ordem do dicionario) das duas strings: um nmero negativo indica que s
1
< s
2
, e um
nmero positivo indica que s
1
> s
2
. Por exemplo, dadas s
1
= verde e s
2
= vermelho,
temos s
1
< s
2
pois as duas palavras coincidem nas 3 primeiras letras, mas, na 4 letra, d
vem antes de m.
Aordemlexicografca estabelecida de acordo coma tabela ASCII por exemplo, n-
meros vmantes de letras maisculas, que vmantes das minsculas, de modo que verde
diferente de verde (por exemplo, vermeiho < verde < vermeiho). Se quisermos fazer
uma comparaao seguindo outra ordem, precisaremos construir nossa propria funao, o
que seremos capazes de fazer depois de estudar, no Captulo 6, o funcionamento interno
da funao strcmp.
5.7.2 Entrada e sada
Nao ha uma maneira direta de trabalhar com vetores de nmeros pelas funes de entra-
da/sada da biblioteca padrao (como scanf e printf); mas, como a entrada e a sada padrao
sao baseadas num fuxo de caracteres, possvel trabalhar diretamente com strings usando
essas funes.
Para imprimir uma string, podemos usar o codigo xs na funao printf. Por exemplo:
chor cor_] = "vermeiho"
prinlf("Meu corro xs.`n" cor}
Para ler uma string, possvel tambmusar o codigo xs na funao scanf; no entanto, ha
alguns cuidados que devem ser tomados, como explicaremos adiante. E agora o momento
de introduzir uma nova funao: fgets. A novidade dessa funao que ela tambm pode ser
usada para ler uma string de um arquivo, e portanto precisamos de um parametro especial
para dizer que o arquivo de que vamos ler a entrada do usuario pelo teclado esse
Strings 83
parametro sldin, que a abreviaao em ingls para entrada padro (standard input), que
o termo usado para denotar o canal de comunicaao entre o programa e o terminal.''
Antes de dizer como se escreve isso em C, vamos observar que, para ler uma string,
precisamos de um vetor grande o bastante para guardar os caracteres. Como nao sabemos
a priori quantos caracteres serao digitados, a estratgia que seguimos em geral a seguinte:
comeamos reservando umespao inicial (temporario) para os caracteres, e lemos os dados
disponveis at encher esse espao. Se nao houver dados para preencher todo o espao, ter-
minamos nosso trabalho; se ainda sobrarem dados para ler, precisamos transferir os dados
lidos para outro lugar, para poder continuar lendo o restante dos dados, repetindo o proce-
dimento conforme for necessario. O nome que costuma ser dado a esse espao temporario
buer, que tem exatamente esse sentido um espao temporario para armazenar dados
provenientes de algum dispositivo (que o nosso caso, com o teclado), ou destinados a
algum dispositivo.
A funao fgets precisa, alm do parametro especial sldin, de duas informaes: quan-
tos caracteres (no maximo) ler, e onde guarda-los. Isso feito da seguinte maneira:
fgets(destino, n, sldin);
Um comentario importante faz-se necessario: o parametro n na verdade indica o nmero
maximo de caracteres da string menos um, pois a funao fgets sempre adiciona o termi-
nador `0 apos o ltimo caractere lido: o tamanho n refere-se ao comprimento da string
quando se inclui o terminador. Nesse sentido preciso prestar atenao diferena em rela-
ao funao strncpy. Assim, se temos um vetor de tamanho 20, podemos chamar a funao
fgets com o parametro n = 20, e no maximo 19 caracteres serao lidos e armazenados.
Essa funao ira ler uma linha da entrada padrao at o maximo de n 1 caracteres. Se
a linha tiver menos do que isso, a leitura acabara no fnal da linha; se a linha tiver mais do
que isso, a leitura acabara no (n 1)-simo caractere.
E possvel ler strings usando a funao scanf de duas maneiras diferentes, ambas um
pouco diferentes do funcionamento de fgets. Para evitar problemas de acesso a lugares
errados na memoria, sempre necessario especifcar o nmero maximo n de caracteres
que poderao ser armazenados (Nos itens abaixo, no meio dos codigos, entenda um n como
esse nmero, e nao como uma letra n a ser digitada literalmente.)
Com o codigo xnc, serao lidos n caracteres da entrada padrao, incluindo espaos e
quebras de linha. O terminador `0 no includo.
Usando o codigo xns, sao lidos no maximo n caracteres da entrada padrao, parando
assim que encontrar algum espao em branco' que nao sera armazenado na
string. Um terminador includo automaticamente ao fnal da string, mas o tama-
nho n nao o leva em conta. Ou seja, o vetor deve ter espao para n + 1 caracteres.
E necessario prestar atenao diferena de comportamento das diferentes funes em
relaao inclusao do terminador da string. Sempre que estiver em dvida, consulte o ma-
nual da biblioteca padrao ou, melhor ainda, faa um programa simples para testar!
Se voc se perguntou por que cargas dgua precisvamos usar esse termo tcnico em vez de simplesmente
dizer teclado, adianto que a entrada padro pode ser redirecionada para pegar dados, por exemplo, de um
arquivo em vez do teclado, e portanto uma estrutura exvel que permite diversos tipos de entrada. Veremos
um pouco sobre isso mais adiante.
Espao em branco a denominao genrica para os caracteres que representam algum tipo de espaa-
mento, seja horizontal ou vertical a saber, o espao comum (ASCII 32), a tabulao horizontal `l (ASCII
9) e quebras de linha (`n, ASCII 10, ou o retorno de carro `r, ASCII 13), entre outros de uso mais raro.
84 Captulo 5. Ponteiros e vetores
Tabela 5.1: Resumo das funes de manipulaao de strings
Funo Descrio
slrcpy(dest origem} Copia todo o contedo de origem para dest.
slrncpy(dest origem n} Copia no maximo n caracteres de origempara dest.
slrien(str} Devolve o tamanho da string str.
slrcmp(s1 s2} Compara as strings s1 e s2, e devolve 0 caso elas
sejam iguais.
fgels(dest n sldin} L uma linha da entrada padrao, com no maximo
n 1 caracteres, armazenando em dest.
5.8 Mais sobre entrada e sada
6
Algoritmos
83
7
Mais ponteiros
7.1 Matrizes
Em C, um vetor pode guardar nao apenas variaveis escalares (numricas), mas tambm
outros vetores. Dessa maneira, podemos criar matrizes, representando cada linha como
um vetor de nnmeros, e uma matriz como um vetor de m vetores-linha, formando assim
uma matriz mn(mlinhas, ncolunas). Aseguir v-se uma ilustraao dessa representaao,
na qual as barras mais claras representamos vetores-linha, que estao contidos na caixa mais
escura, que corresponde matriz:
..
..
a
0,0
..
a
0,1
..

..
a
0,n1
..
a
1,0
..
a
1,1
..

..
a
1,n1
..
.
.
.
..
a
m1,0
..
a
m1,1
..

..
a
m1,n1
Dessa maneira, o primeiro ndice refere-se linha, e o segundo refere-se coluna. Nao
ha nada na estrutura computacional das matrizes que nos obrigue a interpretar os ndices
dessa maneira poderamos agrupar nos vetores secundarios os elementos de uma mesma
coluna emvez de os de uma mesma linha , mas ela seguida por concordar coma notaao
usual emmatematica. Para declarar uma variavel do tipo matriz, usamos a seguinte sintaxe,
muito semelhante sintaxe de vetores:
tipo_de_dados nome_matriz[linhas][colunas];
Aplicam-se as mesmas observaes apontadas para os vetores: os nmeros de linhas e
colunas devem ser constantes, e os ndices dos elementos sao numerados a partir do zero.
Podemos inicializar matrizes de maneira similar dos vetores, introduzindo dois nveis
de chaves os internos para agrupar os elementos de uma mesma linha, os externos para
agrupar as linhas. No entanto, nao possvel deixar os colchetes de tamanho vazios;
necessario preencher pelo menos o ltimo:
87
88 Captulo 7. Mais ponteiros
inl molriz_Z]_Z] = {{Z 3] {5 7]] /* ok */
inl molriz_]_Z] = {{Z 3] {5 7]] /* ok */
inl molriz_]_] = {{Z 3] {5 7]] /* invoiido! */
As matrizes que criamos tambm sao chamadas de vetores de duas dimenses; tambm
possvel criar vetores com mais do que duas dimenses ou seja, vetores com mais do
que dois ndices, como velor_Z]_3]_1] , e nao difcil deduzir como se faz isso. Se
um vetor tem d dimenses, cada ndice tem um intervalo de valores possveis no caso
das matrizes, o primeiro ndice variava entre os nmeros de linhas e o segundo entre os
nmeros de colunas. Dizemos que n
j
o comprimento do vetor ao longo da dimensao j
(j = 1, 2, . . . , d), o que equivale a dizer que o j-simo ndice varia de 0 at n
j
1. Para
criar um vetor de d dimenses, com n
j
entradas ao longo da dimensao j (j = 1, 2, , d),
fazemos o seguinte:
tipo_de_dados nome_vetor[n
1
][n
2
] [n
d
];
7.2 Alocao dinmica de memria
Ecomuma necessidade de lidar comuma quantidade de dados cujo tamanho imprevisvel
no momento emque escrevemos nosso programa; por exemplo, suponha que queremos ler
todo o contedo de um arquivo que esta armazenado no computador, e armazenar numa
variavel. Essa variavel devera ser um vetor de caracteres, mas nao sabemos, ao escrever
nosso programa, o tamanho que isso podera ocupar. Se inventarmos umtamanho maximo,
reservando, por exemplo, espao para um vetor de 100 000 entradas, corremos o risco de
nos deparar com um arquivo maior do que isso, e nao ha como aumentar o tamanho do
vetor posteriormente.
Para lidar comesse tipo de situaao de maneira bemmais elegante e pratica, a biblioteca
padrao do C inclui um conjunto de funes que permitem a alocao dinmica de mem-
ria; sao funes que, ao serem chamadas, pedem ao Guardiao da Memoria'` um pedao de
memoria de umcerto tamanho, e, se o pedido for aceito (isto , se houver memoria dispon-
vel), devolvemo endereo de umpedao de memoria conforme pedido (isto , umponteiro
para a regiao de memoria). A declaraao de um vetor como fazamos anteriormente, em
contrapartida, chamada de alocao esttica de memria, por razes nao muito difceis
de perceber.
Isso soluciona um dos nossos antigos problemas: a declaraao de vetores ou matrizes
lidos pelo usuario. Voc deve se lembrar que nao tnhamos como declarar um vetor cujo
tamanho seja uma variavel lida do usuario ramos obrigados a estipular um tamanho
limite e reservar um espao desse tamanho, mesmo que fossemos usar menos. Usando
a alocaao dinamica, podemos, sabendo o nmero n de dados a serem lidos, pedir um
pedao de memoria no qual caibam n inteiros (ou variaveis de qualquer outro tipo). O
bom disso que, como a porao de memoria alocada contgua (ou seja, sem buracos no
meio), ela pode ser usada como se fosse um vetor comum.
7.2.1 Mos obra
Vulgo sistema operacional.
Alocao dinmica de memria 89
Para pedir um bloco de memoria, voc usara a funao moiioc, passando como parametro
o nmero de bytes de que voc precisa:
ponleiro = moiioc(numbyles}
No entanto, na maioria das vezes, queremos alocar espao para um certo nmero de
dados ou de elementos de um vetor. Portanto, precisamos saber o nmero de bytes que
cada dado (ou elemento) ocupa. Para isso, usaremos o operador sizeof, que diz quantos
bytes ocupa uma variavel de um certo tipo. Veja como ele usado a partir deste exemplo:
prinlf("0 lomonho de um chor xd byles`n" sizeof(chor}}
prinlf("0 lomonho de um inl xd byles`n" sizeof(inl}}
prinlf("0 lomonho de um doubie xd byles`n" sizeof(doubie}}
Voc sempre deve usar o operador sizeof em vez de assumir, por exemplo, que o ta-
manho de um inteiro de 4 bytes. Isso pode causar grandes problemas quando o programa
for transportado para um sistema em que o tamanho diferente.
Voltando alocaao de memoria, podemos, agora, fazer a alocaao de vetores de tama-
nhos arbitrarios. Lembrando que, se um inteiro ocupa 4 bytes, por exemplo, um vetor de
n inteiros ocupara 4n bytes, podemos escrever a rotina de alocaao desta maneira:
inl *vinl
doubie *vdoubie
chor *vchor
vinl = moiioc(n * sizeof(inl}}
vdoubie = moiioc(n * sizeof(doubie}}
vchor = moiioc(n * sizeof(chor}}
Se a memoria tiver sido alocada com sucesso, poderemos acessar esses ponteiros como
vetores normais:
vinl_0] = -5
vinl_1] = 4
sconf("xd" &vinl_Z]}
vdoubie_5] = (vinl_0] + vinl_1]} * 1.0/vinl_Z]
Quando voc terminar de usar um bloco de memoria alocado dinamicamente, voc
deve sempre liber-lo usando a funao free. Ao liberar um pedao de memoria, o sistema
operacional toma a guarda dele, permitindo que ele seja posteriormente usado por outros
programas. Da surge uma possvel fonte de erros: se voc tentar acessar um ponteiro de-
pois que a memoria tiver sido liberada, voc podera estar mexendo comdados de umoutro
programa, o que certamente nao deve ser feito. Assim, ao liberar um bloco de memoria, o
ponteiro que apontava pra ele torna-se invalido, e voc nao deve tentar usa-lo novamente
(a menos que reaponte o ponteiro para outro bloco de memoria). Uma soluao que usada
com certa frequncia transformar o ponteiro em um ponteiro nulo apos a liberaao da
memoria:
free(ponleiro}
ponleiro = WuLL
90 Captulo 7. Mais ponteiros
Isso feito porque tentativas de acesso a ponteiros nulos sao umtipo de erro que sem-
pre detectado pelo sistema operacional e causa a parada do programa; o acesso a ponteiros
degenerados' nem sempre detectado.
7.2.2 Erros?
Pode acontecer de haver algum erro e o sistema nao conseguir alocar a memoria que voc
pediu geralmente porque nao ha mais memoria disponvel. Nessas situaes, a fun-
ao moiioc devolvera um ponteiro nulo (WuLL), que nao aponta para regiao nenhuma da
memoria; coisas terrveis acontecerao se voc tentar acessa-lo (desreferencia-lo). Por isso,
voc sempre deve verifcar se o ponteiro devolvido valido, usando um codigo parecido
com esse:
ponleiro = moiioc(lomonho}
if (ponleiro == WuLL}
{
prinlf("Socorro! Wo foi possvei oiocor memorio!`n"}
/* execulor oigumo oo poro soir desle imbrogiio */
]
Sendo essa uma tarefa comum, que sera executada varias vezes no programa, til
escrever uma funao que cuida desse trabalho repetitivo:
void *moiiocX(sizel lomonho}
{
void *ponleiro
ponleiro = moiioc(lomonho}
if (ponleiro == WuLL}
{
prinlf("Socorro! Wo foi possvei oiocor memorio!`n"}
exil(EXTTFATLuRE}
]
]
A funao exit que foi aqui usada serve para terminar a execuao do programa imedi-
atamente, como se tivesse terminado a funao main. O argumento EXTTFATLuRE indica
ao sistema operacional que houve algum erro na execuao do programa. Isso seria tam-
bm equivalente a voltar funao main e escrever relurn EXTTFATLuRE. No entanto,
usar a funao exit mais pratico pois assim nao necessario nos preocupar com a parte do
programa em que estavamos.
7.3 Ponteiros duplos
Ainda nao vimos como possvel alocar memoria dinamicamente para uma matriz, ou
qualquer vetor de mais de uma dimensao. Podemos pensar em duas maneiras de fazer
isso; a primeira delas uma nao-soluao: para criar uma matriz de m linhas e n colunas,
Traduo livre de dangling pointer (literalmente, ponteiro pendente).
Ponteiros duplos 91
alocamos um vetor de mn elementos, convencionando que o primeiro grupo de n ele-
mentos corresponde primeira linha, que o segundo corresponde segunda linha, e assim
por diante. Dessa maneira, para acessar um elemento (i, j) da matriz (usando 0 i < m
e 0 j < n), temos de fazer uma conta para descobrir em que posiao k do vetor ele se
encontra. Os elementos j da primeira linha (i = 0) podem ser acessados simplesmente
pelo ndice j; os elementos j da segunda linha (i = 1) podem ser acessados somando n ao
ndice j; prosseguindo assim, facil concluir que o ndice linearizado do elemento (i, j)
k = j + i n.
Da mesma maneira podemos proceder para vetores de d dimenses, de comprimentos
(n
1
, . . . , n
d
); os ndices sao denotados (i
1
, . . . , i
d
). O primeiro grupo de elementos cor-
responde aos elementos com o primeiro ndice igual a zero (i
1
= 0); dentro desse grupo,
havera varios subgrupos, cada um correspondendo a um valor do segundo ndice (i
2
); e
assim por diante. Nao difcil completar o raciocnio para encontrar a formula do ndice
linearizado do elemento (i
1
, . . . , i
d
). (Veja a resposta no rodap.')
Essa maneira tem a vantagem de so necessitar de uma alocaao de memoria, com o
(pequeno) custo de precisar fazer uma conta para encontrar as posies de cada elemento.
Na verdade, ao alocar uma matriz estaticamente, dessa maneira que o computador tra-
balha: com um grande bloco de memoria, para o qual os ndices multidimensionais sao
linearizados automaticamente pelo compilador.
Antes de prosseguir, vamos mostrar um exemplo simples de uso dessa tcnica:
void exempiomolriz(inl m inl n}
{
/* oioco o molriz de lomonho (m n} */
inl *molriz = moiiocX(m*n * sizeof(inl}}
inl i g
/* preenche codo eiemenlo com o posio iineorizodo
correspondenle */
for (i = 0 i < m i++}
for (g = 0 g < n g++}
molriz_g + i*n] = g + i*n
]
Aoutra maneira de criar uma matriz dinamicamente permite que ela seja acessada com
a mesma linguagem que as matrizes estaticas (molriz_i]_g]), porm aumentando a carga
computacional tanto do acesso aos elementos quanto da alocaao da matriz. Para matrizes
bidimensionais (m, n), ela consiste em alocar m vetores de n elementos, e alocar um vetor
de mponteiros no qual serao colocados os mvetores. E importante notar que os elementos
desse ltimo vetor sao ponteiros para o tipo de dados que queremos guardar, o que gera uma
pequena diferena no calculo do tamanho de cada elemento:
void exempiomolriz(inl m inl n}
{
/* oioco o velor que guordoro os m iinhos */
inl **molriz = moiiocX(m * sizeof(inl*}}
inl i g
k = i
d
+ n
d
(
i
d1
+ n
d1
(
(i
2
+ n
2
i
1
)
)
)
92 Captulo 7. Mais ponteiros
/* oioco codo iinho com n eiemenlos */
for (i = 0 i < m i++}
molriz_i] = moiiocX(n * sizeof(inl}}
/* preenche codo eiemenlo com o posio iineorizodo
correspondenle */
for (i = 0 i < m i++}
for (g = 0 g < n g++}
molriz_i]_g] = g + i*n
]
A carga computacional da alocaao evidente: em vez de fazer uma alocaao, fze-
mos m + 1 alocaes. Ja para acessar os elementos molriz_i]_g], devemos observar, em
primeiro lugar, que essa expressao equivalente a (molriz_i]}_g], ou seja, que primeiro
devemos olhar para o vetor de linhas para achar a posiao de memoria em que esta a linha
i (nao ha nenhuma garantia de que as m linhas estejam guardadas em posies contguas),
e depois ir at essa posiao e encontrar o j-simo elemento.
Dessa forma, bomumpouco de cuidado ao escolher essa segunda tcnica, pois, apesar
de mais facil quantoao acesso dos elementos, ela pode ser menos efciente. Isso nao signifca
que ela nunca deve ser usada; em cada caso pode-se escolher o que for mais apropriado.
7.4 Ponteiros para funes
Comeo com um exemplo simples: voc quer estimar a derivada de uma funao f num
ponto x
0
, atravs de avaliaes sucessivas do quociente de Newton,
f(x) f(x
0
)
x x
0
,
para valores de x cada vez mais proximos de x
0
. O algoritmo dessa operaao bastante
simples, e o mesmo para qualquer funao f. Seria interessante, portanto, ter um meca-
nismo de criar em C uma funao genrica coicuioderivodo que fzesse isso, dada uma
funao f e um ponto x0, bastando escrever um codigo que fosse, esquematicamente, como
a seguir:
coicuioderivodo(f x0}
Em C, o que passaramos para a funao coicuioderivodo seria nao a funao f em si,
mas um ponteiro para ela. Dessa maneira, a funao coicuioderivodo poderia chamar a
funao f sem saber de antemao quem ela : ela apenas chamaria a funao que apontada
pelo ponteiro que foi dado.
Agora vamos ver como se faz isso.
7.4.1 Declarando umponteiro para uma funo
A declaraao de um ponteiro para uma funao pode parecer um pouco capciosa (de fato,
nao das mais simples), mas nao chega a ser um bicho-de-sete-cabeas. O caso mais sim-
ples seria o de um ponleiro para uma funao sem parametros e que devolve um valor de
um certo lipo. Sua declaraao seria feita como:
Ponteiros para funes 93
lipo (*ponleiro}(}
Os parnteses em torno de *ponleiro sao absolutamente necessarios; se nao os usasse-
mos, teramos (verifque) uma declaraao de uma funo que devolve um ponteiro algo
totalmente diferente!
Se a funao tiver parametros, voc deve colocar seus tipos' dentro do segundo par de
parnteses. Note bem: voc so deve colocar os tipos dos parametros nao coloque os
nomes. Por exemplo, se tivssemos uma funao que devolve uminteiro e recebe uminteiro
e um nmero real, um ponteiro para ela seria declarado assim:
inl (*ponleiro}(inl fiool}
Obviamente, como voc ja deve saber, um ponteiro nao serve para nada se ele nao
aponta para algo que conhecemos. Pois bem, para que um ponteiro aponte para uma fun-
ao, procedemos da mesma maneira que para os ponteiros para variaveis:
ponleiro = &funcoo
Muitos compiladores (o GCC inclusive) permitem que voc omita o E comercial ao criar um
ponteiro para uma funao. No entanto, recomendado que voc o escreva explicitamente para
garantir a maxima portabilidade do seu codigo.
Talvez voc tenha achado estranho usarmos o endereo de uma funao. Mas, exata-
mente como ocorre comas variaveis, as funes sao guardadas emalgumlugar da memoria
quando o programa carregado, de modo que elas tambm tm um endereo.
7.4.2 Chamando uma funo atravs de umponteiro
Se voc tem um ponleiro que aponta para uma funao, voc pode chamar a funao desre-
ferenciando o ponteiro desta maneira:
(*ponleiro}(poromelro1 poromelroZ ...}
Novamente, voc nao deve se esquecer dos parnteses em volta de *ponleiro eles
estao la para indicar que a funao a ser chamada a funao resultante da desreferenciaao
de ponleiro; se esquecssemos dos parnteses, estaramos chamando a funo ponteiro e
desreferenciando o valor por ela devolvido. Se voc quer simplifcar sua vida, voc pode usar
a sintaxe abreviada (apenas para a chamada), que escrita exatamente como uma chamada
de funao comum:
ponleiro(poromelro1 poromelroZ `idols}
Voc tambm pode, naturalmente, capturar o valor da funao, da maneira usual:
x = (*ponleiro}(poromelro1 poromelroZ ...}
x = ponleiro(poromelro1 poromelroZ ...}
Novamente, tudo isso necessrio porque o compilador precisa saber que parmetros a funo est es-
perando, para poder fazer as manipulaes corretas dos dados na memria.
94 Captulo 7. Mais ponteiros
7.4.3 Ponteiros para funes como parmetros de funes
Uma das grandes utilidades dos ponteiros para funes reside na possibilidade de passa-los
entre funes, como parametros. Uma vez compreendida a maneira de declarar e acessar
ponteiros para funes, nao difcil usa-los como parametros. No cabealho da funao, um
ponteiro para funao especifcado da mesma maneira que seria declarada uma variavel
do mesmo tipo. Veja o exemplo:
inl operocoo(inl o inl b inl (*funcoo}(inl inl}}
{
relurn (*funcoo}(o b}
]
7.5 Escopo de variveis
7.6 Funes recursivas
8
Estruturas
8.1 Structs
8.2 Listas ligadas
93
A
Compilao
97
B
Tabelas de referncia
99
Referncias Bibliogrcas
[1] Paulo Feoflo, Projeto de Algoritmos, hllp.//WWW.ime.usp.br/~pf/oigorilmos/
[2] Brian W. Kernighan, Dennis M. Ritchie, e C Programming Language, Prentice Hall,
second edition, 1988.
101

You might also like