Você está na página 1de 101

Aulas de C

Aprendizado continuo. Linguagem antiga e m oderna

Tipos de Dados Personalizados em C Estruturas


Ol, pessoal!
J faz um certo tempo que no posto nenhuma aula nova, e peo desculpas, mas pretendo terminar o bsico de C
para o dia-a-dia ainda em 2011, se minhas atividades de servio assim permitir.
Antes de tudo, espero que vocs tenham visto as aulas anteriores, onde falamos de Makefiles e compilao fracionada
e tenham entendido direitinho tudo o que foi dito, pois esse ser um conceito fundamental a partir de agora.
Outra coisa, estou publicando todos os cdigos do Aulas de C em meu github. Basicamente, um github um local onde
podemos armazenar remotamente cdigos fontes como backup. Na realidade, o github um servidor que oferece
acesso aberto e gratuito a um servidor de controle de verses de cdigo fonte remoto baseado no software git, criado
por Linus Torvalds, o mesmo criador do Linux. um sistema rpido e eficiente, com a vantagem de ser distribudo, o que
permite que cada desenvolvedor trabalhe no seu cdigo sem interferir de imediato nos cdigos dos demais
desenvolvedores. No vamos nos aprofundar nesse assunto de imediato e para os interessados, a sugesto ir no site
github.com e dar uma olhada nas instrues do site, que so bem claras.
Alm disso, estarei divulgando arquivos .zip com os cdigos fontes das aplicaes para que vocs possam dar uma olhada
nas mesmas.
Bem, sem mais delongas, vamos falar do nosso assunto dessa aula: estruturas de dados em C.
1-) O que so estruturas de dados?
Bem, vocs devem ter reparado que trabalhamos com tipos de dados discretos, como nmeros inteiros e flutuantes,
caracteres, matrizes e ponteiros. Isso parece ter ficado claro at aqui. E em geral isso bom quando estamos
aprendendo
Porm, no mundo real, no utilizamos apenas esses tipos, e sim utilizamos dados como informaes cadastrais, conta
bancria, informaes de login do usurio, ficha tcnica do filme e etc. possvel, obviamente, utilizar-se caracterers,
matrizes, ponteiros e toda uma complexa combinao de tipos de dados discretos para criarmos nosso sistema com
essas informaes, mas isso cedo ou tarde ficar confuso e sujeito a bugs.
Para facilitar nossa vida, o C (como a grande maioria das linguagens de programao modernas) oferece mecanismos para
que o desenvolvedor crie e trabalhe com seus prprios tipos de dados. Existem duas formas bsicas de se fazer isso, que
so chamadas de estruturas (

) e unies (

). Unies so um tpico avanado sobre o qual no falaremos aqui.

Vamos ento falar de estruturas.


As estruturas so formas que o C oferecem de organizarmos nossos dados. Por exemplo, imagine um software para o
clculo de nmeros complexos, que possuem uma parte real e uma imaginria. Voc pode, por exemplo, ter declaraes
como a seguinte:

itra1 iaiai1 ra2 iaiai2 ra3 iaiai3.


n el, mgnro, el, mgnro, el, mgnro

Parece algo muito tranquilo, mas isso causa uma srie de problemas:
1. O cdigo fica complexo e pouco flexvel: para operaes individuais pode at parecer ok, mas conforme voc vai
trabalhando com mais e mais informaes, seu programa ficar cada vez mais complexo, com cdigo redundante e
repetitivo;
2. Voc ficar engessado: bem complicado trabalhar com o melhor do sistema, com recursos como uso de
alocao dinmica e ponteiros para melhor aproveitar os recursos do sistema e, com isso, alcanar o melhor em
termos de rendimento do sistema, alm dos problemas que voc ter quando voc precisa trabalhar com uma
quantidade arbitrria (no previamente determinada) de dados;
Uma forma mais inteligente criar uma espcie de tipo de dados novo. Em teoria isso impossvel, pois voc teria que
recriar compiladores e afins, pensando de uma maneira rudimentar. Mas o C j prev, em seu padro, a possibilidade de
extender-se os tipos padres do C e do sistema e com isso criar-se tipos personalizados de dados que representem os
dados que estamos trabalhando, desse modo tornando o cdigo mais lgico e aproveitando de todos os recursos do C.
O mecanismo que nos permite isso so as estruturas de dados. No nosso caso, criamos uma estrutura. Toda estrutura
criada com a palavra-chave

seguida do nome da estrutura e o que compe essa informao dentro de chaves ( ),

como se estivssemos declarando variveis normalmente. Formalmente a

representada como abaixo:

src [oed_srtr]
tut nm_aetuua
{
tpVlr vlr;
ioao1 ao1
tpVlr vlr;
ioao2 ao2

tpVlr vlr;
ioaoN aoN
}
Imaginando os nmeros complexos que falamos anteriormente, um tipo de dados que representa nmeros complexos
poderia ser representada assim:
src topeo
tut Cmlx
{
itra;
n el
itiaiai;
n mgnro
}
;

Perceba que colocamos o nome da estrutura como

. uma boa prtica um nome como esse, pois o C oferece

um comando que permite criar um apelido para um tipo de dados que tornaria a coisa mais fcil. Porm, se voc
nomeasse ele como, por exemplo, complexo, esse nome no ficaria mais disponvel como apelido.
OK Criamos nosso tipo Mas como o usamos?
Bem, primeiro de tudo, temos que declarar nossas variveis com nosso tipo de dados desejado. A declarao feita
exatamente da mesma forma que a declarao de qualquer varivel, com uma pequena diferena, que a presena do
palavra-chave struct junto com o nome do tipo, como abaixo:
src topeocm1 cm2
tut Cmlx op, op;
Esse comando faz o mesmo que faria com um

, etc

E o acesso aos dados? Ele se d por meio do operador

(ponto). Ele permite que voc indique ao compilador qual

informao especfica do seu tipo de dados voc deseja acessar. Por exemplo, vamos criar uma rpida funo de clculo
de nmeros complexos, baseado nesse cdigo que mostramos. O cdigo completo do exemplo pode ser encontrado no
meu github:

src topeosmCmlx (tuttopeoa src topeob


tut Cmlx oaopeo src Cmlx , tut Cmlx )
{
src topeosm;
tut Cmlx oa
sm.elara+.el
oara=.elbra;
sm.mgnroaiaiai+.mgnro
oaiaiai=.mgnrobiaiai;
rtr sm;
eun oa
}

U a c i a i p r a t q e a s m c m n c s d s strings (lembrando que strings so matrizes/ponteiros


m os motne
u, si oo o ao a
de caracteres), as estruturas no podem ser manipuladas diretamente. O que a estrutura ajuda que podemos com elas
organizarmos melhor nosso cdigo e, assim, tornarmos ele mais legvel e,. ao mesmo tempo, termos acesso todas as
demais benesses do C para qualquer tipo de dados, inclusive alocao dinmica de memria e coisas do gnero.
Voc deve estar se perguntando agora: se uma estrutura personalizadas passa a ser considerada um tipo de dados
dentro do meu programa, ento posso colocar uma estrutura dentro de outra? A resposta SIM, voc pode. Depende
do compilador, mas em geral os que seguem o padro C permitem que voc aninhe estruturas em at 8 nveis de
interao (estrutura dentro de estrutura dentro de estrutura dentro de estrutura dentro de estrutura dentro de
estrutura dentro de estrutura dentro de estrutura pior que Duna). Alguns compiladores permitem at mais nveis de
abstrao, mas v por mim, isso vai bastar para 99,999999999999999999999999999999999999% das necessidades.
E como eu fao para aninhar uma estrutura dentro de outra. simples. Primeiro crie a estrutura a ser aninhada
(pegamos o exemplo abaixo do Curso de C da UFMG):

src tp_neeo
tut ioedrc
{
ca ra[0;
hr u 5]
itnmr;
n ueo
ca bir [0;
hr aro 2]
ca cdd [0;
hr iae 3]
ca sgaetd []
hr il_sao 3;
ln itCP
og n E;
}
;

E em seguida a adicionamos como um campo dentro da estrutura que ir a receber:

src fcapsol
tut ih_esa
{
ca nm [0;
hr oe 5]
ln ittlfn;
og n eeoe
src tp_neeoedrc;
tut ioedrc neeo
}
;

Perceba que voc continua precisando adicionar o

l dentro. O acesso s informaes continua a mesma.

Imagina que voc tem uma varivel struct ficha_pessoal de nome eu e voc quer definir o estado onde mora. Para isso,
voc usa algo como:

srp (uedrc.saoP)
tcy e.neeoetd,S;

No caso de um tipo numrico, usaria-se normalmente o operador de atribuio ( ), sem maiores mistrios.
Uma coisa que voc deve estar pensando agora: e se eu usar ponteiros nos tipos de dados, e como aponto memria
para nosso tipo de dados?
OK A gente vai ver um programinha de agenda que vai nos ajudar O cdigo dele est disponvel no meu github. Ele
est dividido em 5 arquivos: um Makefile genrico similar ao que vimos anteriormente, na nossa ltima aula sobre
makefiles. Voc pode copiar o que disponibilizei no github, ou ento utilizar um similar ao que colocamos no final do post
sobre Makefiles. Nesse caso, troque as referncias a r l t a d por a e d .
oerno
gna
O segundo arquivo o arquivo de cabealho do nosso projeto, a e d . :
gnah

#nld <ti.>
icue sdoh

#nld <tlbh
icue sdi.>

#nld <tigh
icue srn.>

tpdfsrc _gnano
yee tut aedif

ca nm[0;
hr oe8]

ca ra8]
hr u[0;

ca cdd[0;
hr iae4]

ca etd[0;
hr sao4]

ca cp1]
hr e[1;

ca dtns[1;
hr aaac1]

ca eal10;
hr mi[0]

itsx;
n eo

/ uiiaeo 0pr fmnn e1pr msuio


/ tlzrms
aa eiio
aa acln

} aed_no
gnaif;

tpdfsrc _gna
yee tut aed

aed_no*nrd;
gnaif etaa

src _gna*et
tut aed nx;

}aed;
gna

vi ceio (od;
od rdts vi)

vi isrIe(gna*ha)
od neetmaed *ed;

vi lsagnaaed *ed;
od itAed(gna ha)

Perceba que a nossa primeira estrutura (struct _agendainfo) precedida de uma palavra-chave t p d f Essa palavra
yee.
chave usada para dar um apelido ao nosso tipo de dados. No nosso caso, utilizamos typedef para criar um apelido ao
nosso tipo t p d f s r c _ g n a n o chamado a e d _ n o Isso facilita muito a coisa para a legibilidade do
yee tut aedif
gnaif.
cdigo.
Veja que temos uma outra estrutura s r c _ g n a que tem um apelido definido via t p d f chamado a e d .
tut aed,
yee
gna
Essa estrutura tem duas coisas interessantes: primeiro, veja que temos um ponteiro usando o apelido a e d _ n oda
gnaif
nossa s r c _ g n a n o
tut aedif.
aed_no*nrd;
gnaif etaa

Essa declarao equivalente a:


src _gnano*nrd;
tut aedif etaa
Mas percebe-se que mais legvel que a entrada acima. Essa a grande vantagem do uso de t p d f e por isso usar
yee
t p d f pode ser considerado (na maioria dos casos) uma boa prtica.
yee
Abaixo da declarao de nossa entrada de agenda, perceba que temos uma segunda declarao dentro dessa estrutura:
src _gna*et
tut aed nx;
Quer dizer que posso ter uma declarao dentro de uma estrutura para uma estrutura igual? SIM, voc pode! A nica
regra que normalmente voc no pode usar um apelido (alias) para essa estrutura, pois nesse momento ele no sabe
que o apelido definido representa a estrutura em questo Portanto importante tomar-se cuidado ao fazer isso
OK O que cada estrutura dessas faz
Nossa estrutura a e d _ n o(vamos usar os alias para facilitar a leitura) representa os dados a serem usados na agenda
gnaif
para cada entrada individual. J a nossa estrutura a e d quem vai realmente montar a agenda. Para isso, vamos usar
gna
uma estrutura de dados.
Como assim?. No caso, vamos criar uma estrutura de fila, onde cada item ser lido um aps o outro e ter um ponteiro
para o item seguinte (struct _agenda *next;) Por conveno, vamos usar n l nesse ponteiro quando quisermos
ul
indicar o final da lista.
E qual a idia? Cada item agenda ir apontar para dois itens: a entrada de dados (agenda_info *entrada;) e o j citado
ponteiro para o prximo item. Veremos no m i que o sistema ter como saber onde fica o comeo da lista.
an
Alm dessas declaraes de dados, declaramos trs funes:

vi ceio (od;
od rdts vi)

vi isrIe(gna*ha)
od neetmaed *ed;

vi lsagnaaed *ed;
od itAed(gna ha)

A primeira meio clara: apenas ir exibir crditos. A segunda tem uma estrutura estranha: foi declarada uma
funo i s r I e ,, com um parmetro a e d * h a . O que queremos dizer a?
neetm
gna *ed
Ponteiros de Ponteiro:
Voc deve ter percebido que usamos dois * ou seja, dois smbolos de indicao de endereo. Aqui temos uma situao
,

bastante comum em programao C. Se voc lembra anteriormente, que falamos que tem como modificar o endereo
de uma varivel quando a passamos por referncia. Nesse caso, utilizamos exatamente isso, um ponteiro para um
ponteiro. Ou seja, ao invs de alterarmos o endereo que leva ao contedo, alteramos o endereo que leva ao
endereo de memria onde est o contedo em questo um pouco complexo isso. Por enquanto basta saber que
iremos alterar o local de memria de uma determinada varavel Isso ir garantir que consigamos acessar corretamente o
sistema.
Vamos ver o m i . desse nosso projeto:
anc

#nld gnah
icue aed.

vi lmaao (gna*ihAed)
od ipDds aed mnagna;

itmi(od
n anvi)
{
aed *ihAed=UL
gna mnagnaNL;
ito;
n p
ceio(;
rdts)
d
o
{
pit(Eclauadsoe aax pr asaaed\\;
rnfsoh m a ps bio aa
u gnann)
pit(1Isrru nv rgsr\;
rnf
nei m oo eiton)
pit(2Lsa o rgsrsisrdsn)
rnf
itr s eito neio\;
pit(3Si\;
rnf
arn)
pit(4Cio\\\Eclasaoo;
rnf
rdtsnnnsoh u p:)
safd,o)
cn(%&p;
wiegthr)=\;
hl(eca(!n)

sic(p
wtho)
{
cs 1
ae :
isrIe(mnagna;
neetm&ihAed)
bek
ra;
cs 2
ae :
lsagnamnagna;
itAed(ihAed)

bek
ra;
cs 3
ae :
lmaao(ihAed)
ipDdsmnagna;
rtr()
eun0;
cs 4
ae :
ceio(;
rdts)
bek
ra;
dfut
eal:
pit(Ooivld!n)
rnfp nia\;
}
}wie(p=)
hl o!3;
}

vi ceio(od
od rdtsvi)
{
pit(Porm d aed smlsd crod Clved Alsd Cnnnn)
rnfrgaa e gna ipe o us e
ir o ua e \\\\;

pit(Atr:FboEii Csa<aicsa35galcm\;
rnfuo i mlo ot fboot00@mi.o>n)
pit(Lcna:GL2nn)
rnfie. P \\;
}

vi lmaao (gna*ihAed)
od ipDds aed mnagna
{
aed *et*o;
gna nx,nw

nx=ihAed;
etmnagna

wienx!NL)
hl(et=UL
{
nwnx;
o=et
nx=et>et
etnx-nx;

fe(o-etaa;
renw>nrd)
fe(o)
renw;
}
}
Nenhum mistrio nesse m i . : usamos nosso cabealho personalizado a e d . para importar todos os cabealhos
anc
gnah
que precisamos e tambm trazer os tipos a e d e a e d _ n o que usaremos no nosso cdigo. Perceba que
gna
gnaif
marcamos em vermelho a declarao de nossa agenda (na varivel m n a g n a e em seguida, passamos o endereo
ihAed)
aonde est essa informao com o comando i s r I e ( m n a g n a . Perceba que utilizamos o operador de deneetm&ihAed)
referenciamento (& para obtermos o endereo onde o C ir guardar o endereo para o ponteiro da nossa estrutura da
)
agenda. Isso permitir que possamos alterar o contedo desse ponteiro dentro da nossa funo. O resto do cdigo no
tem muito mistrio, e em geral no deve ser de maior complexidade se voc estudou corretamente nosso curso at
aqui e tambm fez com calma todos os exemplos que j mostramos.
OK E como feita a insero dos dados? Para isso, utilizamos os cdigos de nosso arquivo insert.c:

#nld gnah
icue aed.
vi lmadDdsaed_no*ao)
od ipnoao(gnaif dds
{
mme(ao-nm,,iefdds>oe)
estdds>oeszo(ao-nm);
mme(ao-raszo(ao-ra)
estdds>u,,iefdds>u);
mme(ao-cdd,,iefdds>iae)
estdds>iaeszo(ao-cdd);
mme(ao-etd,,iefdds>sao)
estdds>saoszo(ao-etd);
mme(ao-cpszo(ao-cp)
estdds>e,,iefdds>e);
mme(ao-dtns,,iefdds>aaac)
estdds>aaacszo(ao-dtns);
mme(ao-ealszo(ao-eal)
estdds>mi,,iefdds>mi);
dds>eo0
ao-sx=;
}
vi isrIe (gna*ha)
od neetm aed *ed
{
aed_no*ao=aed_no)alcszo(gnaif);
gnaif dds(gnaif*mlo(iefaed_no)
aed *nrd=aed*mlo(iefaed),*ok
gna etaa(gna)alcszo(gna) ho;
ca sx=M;
hr eo
i (!nrd)| (dds)
f (etaa | !ao)
ei()
xt1;
lmadDdsdds;
ipnoao(ao)
pit(Dgt onm d pso,o etoFMs eto pregn:\;
rnfiie
oe a esa u n I e nru o nao n)
fesdds>oeszo(ao-nm)sdn;
gt(ao-nm,iefdds>oe,ti)
i(tnm(ao-nm,FM,tlnI)=)
fsrcpdds>oeIsre(FM)=0
{
fe(ao)
redds;
rtr;
eun
}
pit(Dgt araod es pso mr:;
rnfiie
u ne sa esa oa )
fesdds>u,iefdds>u)sdn;
gt(ao-raszo(ao-ra,ti)
pit(Dgt acdd od es pso mr:;
rnfiie
iae ne sa esa oa )
fesdds>iaeszo(ao-cdd)sdn;
gt(ao-cdd,iefdds>iae,ti)
pit(Dgt oetd od es pso mr:;
rnfiie
sao ne sa esa oa )
fesdds>saoszo(ao-etd)sdn;
gt(ao-etd,iefdds>sao,ti)
pit(Dgt ocpod es pso mr:;
rnfiie
e ne sa esa oa )
fesdds>e,iefdds>e)sdn;
gt(ao-cpszo(ao-cp,ti)
pit(Dgt oealdsapso:;
rnfiie
mi es esa )
fesdds>mi,iefdds>mi)sdn;
gt(ao-ealszo(ao-eal,ti)
pit(Dgt adt d nsiet dsapso:;
rnfiie
aa e acmno es esa )
fesdds>aaacszo(ao-dtns)sdn;
gt(ao-dtns,iefdds>aaac,ti)
d
o
{

pit(Dgt osx [/] ;


rnfiie
eo MF: )
safc,sx)
cn(%&eo;
gthr)
eca(;
sx=(eo=a)&sx<)sx+A-a:eo
eo(sx>&(eo=z)?eosx;
pit(%\sx)
rnfcn,eo;
i (eo=M&sx! pit (sx ivld\;
f sx!&eo=F) rnf eo nion)
}wie(eo=M&sx!;
hl sx!&eo=F)
dds>eo(eo=F)01
ao-sx=sx=?:;
/*
*
* Ceas j eitmiesdnr d aed
hc e xse tn eto a gna
*
/
i (*ed
f !ha)
{

*edetaa
ha=nrd;
(ha)>etNL;
*ed-nx=UL
}
es
le
{
ho=ha;
ok*ed
wie(ok>et=UL ho=ok>et
hl ho-nx!NL) okho-nx;
ho-nx=nrd;
ok>etetaa
}
etaa>nrd=ao;
nrd-etaadds
rtr;
eun
}

Perceba que aqui temos alguns segredos que iro nos ajudar a montar nossa lista de dados:
Primeiro, perceba que alocamos memria para uma entrada de dados de agenda, na varivel d d s e uma entrada para
ao,
a agenda, chamada e t a a alm de declararmos uma terceira varivel de agenda, chamada h o (gancho, em ingls).
nrd,
ok
Essa varivel ir ser usada para fazer uma corrida para achar o final da agenda e colocar nela os dados necessrios. O
preenchimento de dados no possui grandes mistriosd, exceto que utilizamos, ao invs do operador . (ponto),
utilizamos o operador - (que chamaremos de operador seta, e composto por um hifen e um sinal de maior). Tambm
>
no precisamos usar o *para indicar que voc quer alterar a informao apontada, como em, d d s > u . Lemos essa
ao-ra
entrada como o elemento r a da estrutura apontada por d d s O resto como j vimos anteriormente. Usamos
u
a o .
uma funo utilitria para inicializar corretamente a estrutura como se deve. Agora, vamos falar sobre o cdigo abaixo

i (*ed
f !ha)
{

*edetaa
ha=nrd;
}
es
le
{
ho=ha;
ok*ed
wie(ok>et=UL ho=ok>et
hl ho-nx!NL) okho-nx;
ho-nx=nrd;
ok>etetaa
}
etaa>nrd=ao;
nrd-etaadds
etaa>etNL;
nrd-nx=UL

esse cdigo que faz toda a parte da insero de dados. Perceba que testamos para ver se o valor apontado pelo
endereo obtido no incio da funo e passado pelo m i ( NULL (i ( * e d ): isso ir ocorrer apenas uma vez,
an)
f !ha)
logo no incio do programa. Quando isso acontecer, ele ir pegar e substituir o endereo apontado por * e d pelo
ha,
endereo entrada. Caso contrrio, ele ir utilizar o seguinte procedimento para correr a lista at o fim:
1. Usando a varivel h o , ir armazenar a varivel o endereo apontado por * e d
ok
ha;
2. Em seguida, ir associar o endereo do prximo item apontado na varivel h o (o item em questo) prpria
ok
varivel h o , at alcanar ltimo item (aquele cujo next for NULL). Lembre-se desse procedimento: veremos ele
ok
novamente mais adiante;
Agora que sabemos onde a fila termina, adicionamos no novo item fila, modificando o n x do item apontado em h o
et
ok
pelo endereo de e t a a com isso amarrando essa entrada lista.
nrd,
Aps ambos os caso, associamos os dados entrados ao valor de e t a ao endereo de nossa estrutura, e definimos
nrd
n x como NULL, o que indicar que esse o ltimo item (no faremos organizao das entradas para facilitar).
et
E como funcionar a visualizao desses itens?
Bem, vamos ver o cdigo de exibio dos dados:
#nld gnah
icue aed.

vi lsagnaaed *ed
od itAed(gna ha)
{
aed *tm
gna ie;
aed_no*eito
gnaif rgsr;
itcutr0
n one=;

ie=ed
tmha;

wie(tm=UL
hl ie!NL)
{
rgsr=tm>nrd;
eitoie-etaa
cutr+
one+;

pit(Rgsr n.%\\cutr;
rnfeito o dnn,one)
pit(Nm. %\rgsr-nm)
rnfoe.: sn,eito>oe;
pit(Edrc. %\rgsr-ra;
rnfneeo: sn,eito>u)
pit(Cdd %\rgsr-cdd)
rnfiae: sn,eito>iae;
pit(Etd %\rgsr-etd)
rnfsao: sn,eito>sao;
pit(CP %\rgsr-cp;
rnfE: sn,eito>e)
pit(DtNs. %\rgsr-dtns)
rnfaaac: sn,eito>aaac;
pit(Eal. %\rgsr-eal;
rnfmi: sn,eito>mi)
pit(Sx. %\(eito>eo=);
rnfeo.: cn,rgsr-sx=0?F:M)

pit(Pesoeqaqe tcapr cniur\;wie!eca()


rnfrsin ulur el aa otna!n) hl(gthr);

ie=tm>et
tmie-nx;
}

pit(Eiio % rgsrsn,cutr;
rnfxbds d eito\ one)
rtr;
eun

Seguir

Seguir Aulas de C
Obtenha todo post novo

Obtenha todo post novo


entregue na sua caixa de
entrada.

bem simples Lembra da corrida que fazemos na entrada de dados? Basicamente fazemos exatamente a mesma
coisa: seguimos o seguinte procedimento at acharmos o ltimo item da nossa lista:
Insira seu endereo de e-mail
1. Armazenamos em r g s r o ponteiro para os dados que iremos exibir;
eito

Cadastre-me

2. Exibimos os dados utilizando p i t (como de costume);


rnf

3. E associamos o ponteiro n x e atribumos a ela o valor de i e ;


et
tm

Tecnologia WordPress.com

Esse procedimento ir se repetir at que o valor de i e seja NULL


tm
Antes de encerrarmos, vamos falar de uma rotina importante que uma boa prtica no desenvolvimento de qualquer
sistema com alocao dinmica de memria, mas especialmente em programas como nosso, onde usamos intensivamente
esse procedirmento. Esse procedimento ocorre no nosso programa apenas no encerramento do mesmo. Observe o
seguinte cdigo em m i . :
anc

vi lmaao (gna*ihAed)
od ipDds aed mnagna
{
aed *et*o;
gna nx,nw
nx=ihAed;
etmnagna
wienx!NL)
hl(et=UL
{
nwnx;
o=et
nx=et>et
etnx-nx;
fe(o-etaa;
renw>nrd)
fe(o)
renw;
}
}

Essa funo chamada apenas no final do programa Note que ele segue a mesma idia de sempre quando falamos na
fila, portanto guarde esse procedimento na mente:
1. Ele atribui o valor de n x para um ponteiro temporrio n w(importante fazer isso para que no haja erros de
et
o
lgica onde o sistema pire).
2. Em seguida, como vamos usar apenas o ponteiro n wna liberao de memria, podemos passar para n x o
o
et
valor do prximo item (n x );
et
3. Aps isso, liberamos primeiro os dados (com f e ( o - e t a a ) e em seguida, liberamos a entrada
renw>nrd)
(f e ( o )
r e n w );
4. E ento repetimos o processo, enquanto o n x no for NULL;
et
uma boa prtica desalocar toda a memria usada durante o uso do programa. A maioria dos sistemas operacionais
modernos conseguem detectar a memria usada pelo programa (incluse do heap, onde armazenada toda a informao
alocada dinamicamente) e elimin-la, principalmente porque em geral cada programa recebe uma determinada
quantidade de memria no momento em que ele executar. Ainda assim, existe a possibilidade de memria alocada no
ser liberada no momento do encerramento do programa, formando o que se chama de memory leakage (vazamento de
memria), pois para o SO essa memria ainda est alocada, s no sabendo-se por quem. Ao desalocar explicitamente a

memria, voc diminiu e muito a chance de um memory leakage. Lermbre-se sempre dessa mxima em C:

Aloque ao entrar e desaloque ao sair


Com essa mxima, terminamos nossa aula. Guarde esse cdigo, pois o usaremos em nossa prxima aula, onde falaremos
de arquivos em disco.
Como brincadeiras, sugiro:
1. Se voc reparar, no removemos o ENTER na entrada dos dados. Tente criar uma funo que remova os
terminadores \ n das entradas de dados;

2. Quando falamos que no iramos organizar os dados, h um jeito: lembre-se que voc pode apontar informaes
de ponteiros e estrururas dentro de outras estruturas. Tente criar um cdigo que permita que voc ordene os
dados armazenados. Procure informaes sobre a funo s r o ppara algumas idias;
tcm
Uncategorized

Utilitrios Make, Makefiles e sua importncia


OK
Ento continuaremos sem programar C real. Essa a m notcia da aula de hoje.
A boa que terminaremos o tpico que comeamos na aula passada, quando falamos sobre o conceito de projeto,
separao de cdigos fonte e compilao individual.
Como vimos, possvel dividir um programa em arquivos fontes individuais (que formam, em conjunto, um projeto) e
compilar os mesmos individualmente, de modo que no caso de uma modificao pontual no seja necessrio recompilar
totalmente os fontes para obter-se o executvel. Isso deve-se ao fato de os compiladores modernos na verdade
executarem duas funes simultaneamente: a compilao (transformao de cdigos fontes em cdigos objetos) e a
linkedio ou ligao (a unio de vrios cdigos objetos em binrios que possam ser executados pela mquina).
At aqui nenhuma novidade.
Mas lembremos novamente do que mostramos na aula passada. Programas reais, como pacotes Office e navegadores
possuem milhes de linhas de cdigo, que, por sua vez, podem estar espalhadas em milhares de arquivos de cdigo
fonte. Mesmo com essa diviso, a tarefa de gerar um novo cdigo-objeto para cada fonte alterado e ligar todos os
objetos em um executvel seria MUITO enfadonha e propensa a erros.
Para resolver esse problema, antigamente usavam-se scripts especficos para cada plataforma de desenvolvimento e uso.
Porm, isso ainda assim era ineficiente, pois a adio ou remoo de novos arquivos e a mudana na estrutura do projeto
demandava a total modificao dos scripts, sendo que os prprios scripts tinham que ser mantidos, e eram enfadonhos
de se manter.
Em 1977, porm, Stuart Feldman criou o primeiro sistema de automao para a compilao de programas, o m k . A
ae
funo do m k , construir todas as dependncias descritas em um arquivo especial chamado M k f l . Makefiles
ae
aeie
seguem um padro razoavelmente simples de construo. Embora o formato Makefile original seja um padro de facto,
muitos compiladores trazem consigo o seu prprio make, e IDEs, como a Code::Blocks, o Eclipse e o Netbeans tambm
possuem suas prprias regras e mecanismos, usando ou no e baseado ou no no make UNIX.
Para explicarmos o conceito geral e demonstrarmos o funcionamento, utilizaremos o GNU Make. GNU Make parte dos
utilitrios includos no GCC (GNU Compiler Chain), que includo em quase todas as distribuies Linux e est disponvel
em vrias plataformas, como Windows, MacOS/X, etc

Um Makefile simples:

Sem muitas delongas, vamos mostrar como o Make trabalha e como criar um Makefile:
Basicamente, make funciona em um sistema de alvos e dependncias. Ou seja, m k precisa saber quais so os arquivos
ae
que ele ir processar (dependncias) para realizar alguma tarefa e obter alguma outra coisa (alvo). Por exemplo, vamos
fazer um Makefile simples.

al
l:
eh el,Mk!
co Hlo ae

Aqui dizemos que queremos obter all, ou seja, tudo ( o default do GNU Make. Se no encontrado, executa o primeiro
alvo de cima para baixo dentro do Makefile). Depois dos dois-pontos (: indicaramos qualquer dependncia que
)
precisssemos. Porm, como no temos nenhuma, deixamos em branco mesmo.
Em seguida, colocamos a tarefa a ser executada. Ela pode ser quaisquer seqncias de comandos vlidos para o sistema
operacional em questo. No nosso caso, utilizamos um comando e h e l , M k ! , que usado no Linux para
co Hlo ae
emitir uma mensagem na tela (no Windows tambm funciona). Uma coisa importante: os comandos da tarefa devem ser
espaados do incio da linha por uma tabulao (tecla TAB). Embora algumas ferramentas m k modernas consiga
ae
reconhecer espaos no lugar do TAB para efeito de indicao da tarefa, melhor manter o padro para no incorrer em
problemas em outras plataformas.
Bem, digitado esse arquivo, salve-o com o nome de M k f l . Esse nome o nome default que o GNU m k (e a
aeie
ae
maioria dos demais) ir procurar. Em vrias ferramentas make, possvel que voc defina, por meio de uma opo de
linha de comando, qual o Makefile a ser usado. Consulte o manual de sua ferramenta M k para maiores informaes.
ae
Bem, voltando: uma vez salvo o mesmo (no momento no interessa onde voc ir o jogar), digite o comando m k na
ae
linha de comandos de seu sistema operacional, no diretrio onde voc salvou o seu arquivo Makefile. A sada resultante
ser algo mais ou menos como a seguinte:

$mk
ae
eh el,Mk!
co Hlo ae
Hlo Mk!
el, ae

No muito til, mas j mostra resultados. Uma vez que voc disparou o comando, ele procurou um alvo-padro (a l e
l)
verificou se suas dependncias estavam resolvidas (no momento nenhuma, ento ok). Estando tudo OK, ele executou a
tarefa determinada (no caso, o comando e h e l , M k ! ) e se encerrou.
co Hlo ae
Exemplo bobo

Um Makefile til
Bem, pelo menos sabemos como ele funciona. Agora vamos fazer algo realmente til:
V ao diretrio onde voc guardou o seu projeto do Roletrando que mostramos na aula passada. Vamos criar um
Makefile um pouco mais interessante:

#
#Pier tnaiad Mkfl
rmia ettv e aeie
#
al rltad
l: oerno

rltad:mi. rga. lta.


oerno ano erso erso
gc- rltad mi. rga. lta.
c o oerno ano erso erso
mi.:mi. rltad.
ano anc oernoh
gc- mi. - mi.
c o ano c anc
rga.:rga. rltad.
erso ersc oernoh
gc- rga. - rga.
c o erso c ersc
lta.:lta. rltad.
erso ersc oernoh
gc- lta. - lta.
c o erso c ersc

Agora temos um Makefile realmente til. Primeira coisa que voc deve ter notado que, de certa forma, colocamos
todos os comandos que usaramos em uma compilao parcial normal, como vimos anteriormente:

Sobre arquivos de cdigo-fonte, arquivos de cabealhos e projetos Aulas de C

No nosso caso, utilizaremos primeiro os comandos:

gc- lta. - lta.


c o erso c ersc
gc- rga. - rga.
c o erso c ersc
gc- mi. - mi.
c o ano c anc

Para gerarmos os arquivos .o (os cdigos-objeto) de cada um dos cdigo-fonte e, aps


isso, utilizaremos o comando:

gc- rltad lta. rga. mi.


c o oerno erso erso ano

Para fazermos o link das funes e obtermos o executvel.

Agora, precisamos entender o que estamos fazendo.


As primeiras linhas de nosso Makefile:

#
#Pier tnaiad Mkfl
rmia ettv e aeie
#

So apenas comentrios. No caso do m k , os comentrios utilizam o smbolo sustenido, ou sharp, ou qualquer outro
ae
nome que voc j ouviu falar (vale at lasanha :D). Como no C e em qualquer outra linguagem ou ferramenta, os
comentrios so simplesmente ignorados.
A linha seguinte:

al rltad
l: oerno

Indica que, para o make atingir o alvo a l ele tem como dependncia que executar o alvo r l t a d . Importante
l,
oerno
notar que, no m k , uma dependncia pode ser quaisquer arquivos E outros alvos. Isso importante e veremos abaixo o
ae
por que.
Outra coisa: repare que esse alvo no possui tarefas. O que fizemos aqui criar uma espcie de alvo nulo, ou phony.
Simplesmente fizemos isso para chamar a ateno do make para c. Normalmente ele executa o primeiro alvo de cima
para baixo dentro do Makefile por padro se ele no encontrar um alvo a l Em alguns casos, porm, voc pode querer
l.
que o m k gere vrios alvos ao mesmo tempo (por exemplo, se voc desenvolver um sistema composto por vrios
ae
programas). Nesse caso, basta listar outros alvos como dependncias em a l
l.
Seguindo adiante, vemos uma entrada completa de um alvo (chamado tambm de regra):

rltad:mi. rga. lta.


oerno ano erso erso
gc- rltad mi. rga. lta.
c o oerno ano erso erso

Uma regra composta pelo nome da regra (o alvo), uma ou mais dependncias, e comandos que permita ao sistema
obter essa dependncia. O m k precisa dessa informao para fazer suas tarefas. No caso acima:
ae
O nome da regra (alvo) r l t a d ;
oerno
As dependncias so m i . r g a . l t a . ;
ano erso erso

O comando a ser executado g c - r l t a d m i . r g a . l t a . ;


c o oerno ano erso erso
Perceba como a coisa est construda: quando usamos vrias dependncias, elas so separadas por espao. Alm disso,
normalmente o nome da regra (ou alvo) o nome do arquivo final a ser obtido por aquela regra (no nosso caso, o
binrio r l t a d ). O comando (ou comandos) devem seguir aps a linha com a descrio do alvo e das
oerno
dependncias. Uma regra separada da outra por uma linha em branco.
OK Aqui vemos que o m k sabe que, para obter o arquivo r l t a d , ele precisa ter as dependncia (ou seja, os
ae
oerno
arquivos) m i . , r g a . e l t a . . Alm disso, ele sabe que, tendo esses arquivos, ele precisar executar o
ano erso
erso
comando g c - r l t a d m i . r g a . l t a . para obter o seu alvo e, portanto, cumprir a regra.
c o oerno ano erso erso
Mas at aqui, o Makefile no sabe como obter nenhuma das dependncias em questo.
Para isso, inclumos as linhas seguintes:

mi.:mi. rltad.
ano anc oernoh
gc- mi. - mi.
c o ano c anc
rga.:rga. rltad.
erso ersc oernoh
gc- rga. - rga.
c o erso c ersc
lta.:lta. rltad.
erso ersc oernoh
gc- lta. - lta.
c o erso c ersc

Aqui, vemos que colocamos as diversas regras para gerar cada um dos arquivos . (objetos) necessrios como
o
dependncia em r l t a d . Perceba que as dependncias so os nomes dos arquivos de fonte . e (no nosso caso),
oerno
c
o arquivo de cabealho r l t a d . . Isso visa garantir que o arquivo de cabealho exista e, no caso de ele ser
oernoh
modificado (por exemplo devido a uma funo modificada) o cdigo seja reconstrudo de maneira adequada. Claramente
isso vai depender de como funciona seu cdigo fonte: haver situaes onde adicionar muitos arquivos fonte ou
cabealhos gerar inconvenientes (como compilaes desnecessrias). Mas isso no vem ao caso agora.
O m k checar se (1) os arquivos indicados nas dependncias existem ou se (2) eles so alvos resolvidos por outras
ae
regras. Se ambas as coisas no forem possveis, ele ir dar uma mensagem de erro como a seguinte (No caso, renomeei
o r l t a d . , exigido por m i . , para outro nome qualquer):
oernoh
ano

mk: ** Sm rga pr poesr o av `oernoh, ncsi pr


ae
*
e
er
aa rcsa
lo rltad.
eesro o
`ano. Pr.
mi.
ae

Estando tudo OK, ao rodar-se o comando m k (por via das dvidas, antes apague todos os arquivos . e o arquivo
ae
o
r l t a d ), ele ir mostrar algo como abaixo:
oerno

$mk
ae
gc- mi. - mi.
c o ano c anc

gc- rga. - rga.


c o erso c ersc
gc- lta. - lta.
c o erso c ersc
gc- rltad mi. rga. lta.
c o oerno ano erso erso

Vamos descrever o que aconteceu:


1. m k foi executado, procurou a regra default a l e a encontrou. Viu que ela demandava um alvo chamado
ae
l
r l t a d . Ento foi para a regra r l t a d ;
oerno
oerno
2. Em r l t a d , ele viu que precisava dos arquivos (ou alvos) m i . , r g a . e l t a . . Antes de fazer
oerno
ano erso
erso
qualquer coisa, ele ir verificar se ele tem uma regra que explique como obter esses arquivos.
3. O make percebe que existe uma regra para m i . . Ela demanda os arquivos m i . e r l t a d . .
ano
anc
oernoh
Verificando que os dois existem, ele verifica se o arquivo indicado no alvo existe e mais novo que os arquivos de
dependncia (o que indica que nenhum deles foi alterado). Caso contrrio, ele ir executar as regras para obterse uma nova verso do arquivo m i . (ou obt-lo, caso no exista).
ano
4. Se houver alguma falha (por exemplo, um erro de sintaxe), o m k ir abortar sua execuo, no gerando
ae
nenhum binrio e no criando o r l t a d .
oerno
5. Em caso de sucesso no passo 3 para m i . , o sistema repetir o processo dos passos 3 e 4 para os demais
ano
arquivos de dependncia r g a . e l t a . . Se algum deles exigisse um outro arquivo gerado por um outro
erso erso
alvo, os passos 3 e 4 seriam repetidos para tal arquivo e assim sucessivamente. m k realiza uma pesquisa recursiva
ae
para ver se cada alvo necessriao como dependncia de cada outro alvo (incluindo a l foi obtido com sucesso.
l)
6. O passo 5 tendo sido realizado com sucesso para TODOS os alvos e suas dependncias (incluindo outros alvos), o
m k ir executar os comandos indicados na regra roletrando para obter o arquivo r l t a d , resolvendo a
ae
oerno
dependncia a l
l;
7. Resolvida todas as dependncias de a l(r l t a d e suas dependncias), m k ir se encerrar com sucesso;
l oerno
ae
Agora, aps rodar m k , voc ter o arquivo r l t a d . Se tentar rodar make novamente, ver que ele no far
ae
oerno
absolutamente NADA, pois ele sabe que nenhuma das dependncias de r l t a d foi modificada (nehum dos . ),
oerno
o
pois todas existem e so mais atuais que qualquer modificao em quaisquer um dos arquivos . .
c
Vamos simular uma alterao em um dos arquivos . (no caso, o arquivo r g a . ). Para isso:
c
ersc
em Windows, abra o arquivo em um editor e o salve sem alterar nada;
no Linux, na linha de comando, d o comando t u h r g a . ;
oc ersc
Execute ento o comando m k . Sua sada deve ser como a seguinte:
ae

$mk
ae
gc- rga. - rga.
c o erso c ersc
gc- rltad mi. rga. lta.
c o oerno ano erso erso

Por que isso aconteceu?


Make analisa o timestamp do arquivo (o registro de quando o mesmo foi alterado pela ltima vez) de todas as
dependncias necessrias e, caso o timestamp de uma dependncia seja maior que o do arquivo do alvo (ou seja, a
dependncia em questo foi alterada), ele executa novamente a regra para ele e para todas as regras que tenha o alvo
como dependncia e assim sucessivamente. Portanto:

1. O sistema identificou que a dependncia r g a . de r g a . mudou e reconstruiu r g a . ;


ersc
erso
erso
2. Ento o m k percebeu que a dependncia r g a . de r l t a d mudou e, portanto, reconstruiu
ae
erso
oerno
rltad;
oerno
Ou seja, voc no precisa se preocupar com re-executar manualmente os comandos em questo. Estando tudo OK, o
prprio m k ir executar e processar os arquivos adequados para gerar um binrio.
ae

Varaveis e Macros no Makefile


Bem, agora temos um Makefile til
Mas agora pense em uma coisa
Aqui colocamos regras, alvos e dependncias para CADA arquivo fonte. Isso em sistemas pequenos bem til, mas
conforme o sistema aumenta de tamanho e funcionalidades, obviamente aumenta o nmero de arquivos de cdigo
fonte. Ou seja, teramos que fazer mais entradas de regras, mais dependncias
Alm disso, imagine que voc deixa de usar o g ccomo compilador. Voc provavelmente teria que modificar todos os
c
alvos e regras para usar os padres do novo compilador, o que seria um pesadelo!
O m k , porm, salva a nossa vida, oferecendo variveis e macros que nos ajudam a fazer com que o make realize tarefas
ae
genricas (como gerar um objeto . a partir de um fonte . ), alm de embutir alguma inteligncia para que ele
o
c
descubra se existem novos arquivos . no local e coisas do gnero. No caso, digite o seguinte Makefile e o salve no
c
diretrio do roletrando com o nome M k f l - :
aeie1

#
#Sgnatnaiad Mkfl
eud ettv e aeie
#
CCM=c
_OPgc
FNE=(idad*c
OTS$wlcr .)

#euvl adzrFNE=ancrga. lta.


qiae
ie OTSmi. ersc ersc

HAES$wlcr *h
EDR=(idad .)
al rltad
l: oerno

rltad:$FNE:c.)
oerno (OTS.=o
$CCM)- $ $
(_OP o @ ^
%o %c$HAES
.: . (EDR)
$CCM)- $ - $
(_OP c < o @

Perceba que ele parece muito mais complexo do que o nosso Makefile anterior. Mas no se preocupe que iremos
descrev-lo aos poucos.
Primeiro vamos pegar as primeiras trs linhas:

CCM=c
_OPgc

FNE=(idad*c
OTS$wlcr .)

#qiae adzrFNE=ancrga. lta.


euvl
ie OTSmi. ersc ersc

HAES$wlcr *h
EDR=(idad .)

Aqui estamos criando trs variveis: C_COMP, FONTES e HEADERS. A primeira varivel, C_COMP, indica o compilador que
estamos usando. O nome poderia ser qualquer um ( exceo de alguns nomes que o m k reserva para uso prprio,
ae
que no detalharemos aqui), e abaixo veremos o porque. No nosso caso atual, definimos que C C M = c , o que
_OPgc
lembra uma atribuio em C. Estamos atribuindo varivel do Makefile C_COMP esse valor.
A varivel seguinte, FONTES, atribuda com $ w l c r * c .
(idad .)
O que isso quer dizer, afinal de contas?.
Quando voc usa o smbolo de $, quer dizer que voc est usando uma macro do m k . As macros so certos recursos
ae
embutidos no m k que esto disponveis para facilitar sua vida na criao de um Makefile. Quando usamos $ )cercando
ae
(
algo, estamos indicando uma macro de expanso, ou seja, vamos fazer com que o make entenda que o valor que est ali
equivale ao de um comando ou varivel previamente definido. No nosso caso, usamos o comando w l c r para pedir
idad
que o make realize uma busca no diretrio onde o Makefile se encontra e localize todos os arquivos que seguem o
padro indicado. No nosso caso, $ w l c r * c , pode ser lido como procure todos os arquivos .c no diretrio onde
(idad .)
voc est e os d como valor onde voc est. Colocamos em seguida um comentrio:

#qiae adzrFNE=ancrga. lta.


euvl
ie OTSmi. ersc ersc

para dizer o nosso resultante. No fim das contas, usar:

FNE=(idad*c
OTS$wlcr .)

equivale a dizer, na situao atual

FNE=ancrga. lta.
OTSmi. ersc ersc

Em seguida, criamos uma varavel HEADERS, com o mesmo tipo de contedo de FONTES.
Uma coisa importante de dizer aqui, que no foi dita, que as variveis podem conter um valor s (como em C_COMP),
ou vrios valores separados entre si por espao (como no caso da expanso de FONTES). Isso importante em alguns
casos, como veremos adiante.

Aps estipularmos o alvo all, com dependncia roletrando (igual ao nosso primeiro Makefile), definimos nosso alvo
roletrando.
rltad:$FNE:c.)
oerno (OTS.=o
$CCM)- $ $
(_OP o @ ^
Que coisa maluca essa pelamordeus?!
Calma. Aqui estamos usando vrias macros para resumir nosso servio.
Primeiro, vamos olhar a parte das dependncias: $ F N E : c . )
(OTS.=o
O que isso quer dizer?
O m k possui alguma inteligncia para saber que determinados arquivos geram outros determinados arquivos. Por isso,
ae
ele capaz de traduzir nomes por substituio simples se corretamente indicado. No caso, estamos usando o : c .
.=o
na frente de FONTES. Isso indica ao make para entender que, ao expandir FONTES ali, ele deve substituir todas as
extenses . para . . Ou seja, ele ser capaz de montar uma lista dos arquivos objetos necessrios a partir da lista de
c
o
arquivos fontes (que colocamos na varivel FONTES, lembra).
Em seguida, temos a regra para obtermos os alvos: $ C C M ) - $ $
(_OP o @ ^
Temos a expanso de C_COMP, o que j deve ser claro, e um - que parmetro do g c(aqui fica uma sugesto: o
o
c
ideal aqui que outra varivel contivesse todos os parmetros da compilao, uma vez que eles podem mudar de
compilador para compilador). Em seguida temos duas macros novas: $ e $ . No caso, $ deve ser lido como o alvo a
@
^
@
ser alcanado e $^ indica a lista de dependncias passadas.
Como tudo isso ento se comporta, no fim das contas?
1. m k ir expandir a lista de dependncias. Para isso, ir olhar o valor de FONTES, pegar qualquer valor listado em
ae
FONTES que termine com .c e modificar o valor dele nessa expanso para .o. Considerando que FONTES
equivale, nesse momento a m i . r g a . l t a . , ele expadir FONTES como m i . r g a .
anc ersc ersc
ano erso
l t a . nas dependncias de r l t a d ;
erso
oerno
2. Em seguida, ele ir montar a regra para atingir-se o alvo r l t a d . Primeiro ir expandir a varivel C_COMP
oerno
(valendo g c colocar o - na frente do mesmo (que, como no uma macro ou varivel deixado como
c ),
o
est), aps isso inserindo o nome do alvo (no caso, roletrando) e a lista de dependncias do mesmo! O valor final
para a regra expandida ser g c - r l t a d m i . r g a . l t a . (ou valor similar: na realidade
c o oerno ano erso erso
normalmente ser g c - r l t a d l t a . m i . r g a . , pois a expanso com w l c r em
c o oerno erso ano erso
idad
F N E gera um lista em ordem alfabtica dos arquivos cujo nome casem com o padro desejado);
OTS
OK, ento temos o comando para gerar o executvel a partir dos objetos Mas e quanto a gerao dos objetos a partir
dos fontes?
O m k tem outra boa inteligncia que permitir que um alvo seja estabelecido a partir de um padro, ou melhor, que
ae
haja uma regra padro para alvos de um determinado tipo. Um exemplo de alvo assim est no final do nosso novo
Makefile:

%o %c$HAES
.: . (EDR)
$CCM)- $ - $
(_OP c < o @

O smbolo de porcento (% serve para indicar um padro genrico que pode ser usado na regra como um todo.
)
Colocando no alvo, ele diz ao m k que ele sabe o que fazer para qualquer arquivo que obedea o padro em questo.
ae
No nosso caso, o alvo passa a ser qualquer arquivo que termine em . . O bom que esse valor passa a ser salvo e
o
pode ser usado na dependncia, como fizemos aqui: nossa dependncia % c $ H A E S , ou seja, qualquer arquivo
. (EDR)

que tenha o mesmo nome do alvo, mas termine em . e o valor da varivel HEADERS (que um wildcard de arquivos
c
. ).
h
A linha de regra tem algumas similaridades com o que temos na linha de regra para r l t a d : primeiro expandimos
oerno
C_COMP para gcc e temos um - que mantido como est (lembrando que - apenas compila o arquivo oferecido
c
c
como entrada, sem fazer link, gerando um arquivo objeto).
Em seguida, temos a macro $ . Essa macro pega o primeiro valor listado na lista de dependncias e o usa como valor.
<
Perceba que, se analisarmos a lista de dependncias, teremos normalmente o arquivo . e um ou mais header files. Por
c
isso, usamos apenas o primeiro item da lista ( bom deixar o header file como dependncia, para o caso de alterao,
mas o que precisamos usar mesmo o arquivo de fonte . ) como parmetro aqui. Em seguida, temos a montagem do
c
resto da regra, que idntica a como fizemos no alvo roletrando: - como est e o $ sendo expandido para o nome
o
@
do alvo.
Como ento se d a execuo passo a passo do Makefile desde que mandamos o comando make, nesse caso?
Ele ir expandir a regra roletrando e ver se todos os arquivos .o existem. Caso algum deles no exista ou seja mais antigo
que o seu . , ele perceber isso e, usando o alvo genrico % o ir gerar o . necessrio a partir do . . Em seguida ir
c
.,
o
c
gerar o alvo r l t a d .
oerno
Apague todos os arquivos . e mande executar nosso Makefile com m k - M k f l - . Sua sada provavelmente
o
ae f aeie1
ser algo como abaixo:

gc- lta. - lta.


c c ersc o erso
gc- mi. - mi.
c c anc o ano
gc- rga. - rga.
c c ersc o erso
gc- rltad lta. mi. rga.
c o oerno erso ano erso

Ou seja, ele far todo o servio necessrio sem voc criar regras individuais e aumentar muito a complexidade do seu
Makefile.
Um efeito colateral importante do uso de w l c r s que qualquer arquivo pego pelo padro usado como valor de
idad
varivel (e como dependncia, em geral). Isso pode acarretar problemas. Por exemplo, mantendo nosso Makefile como
est, crie um arquivo .c em branco no diretrio do roletrando. No interessa o nome, apenas crie (para usurios de
Linux, use o comando t u h o n m _ o m u a q i o a e t r o c sem contedo nenhum. Aps isso, execute o
o c _ o e d _ e _ r u v _ l a i . ),
Makefile-1 como fizemos anteriormente. Voc perceber que ele ir achar o arquivo r n o . na lista de fontes e
admc
colocar o arquivo r n o . como parte das dependncia de r l t a d (devido expanso), compilando r n o .
admo
oerno
admc
com a regra genrica.
Isso parece bobagem, uma vez que random.c est vazio Mas tente, por exemplo, copiar um arquivo que tenha uma
funo m i ( para dentro de r l t a d . Ao usar w l c r s voc pode incorrer no risco de, cedo ou tarde,
an)
oerno
idad,
provocar erros de compilao e ligao devido incluso inadvertidade de um arquivo . que contenha um cdigo para
c
uma funo similar a uma que j exista em seu cdigo. Tome muito cuidado quanto a isso.
Uma sugesto seria no utilizar as expanses com w l c r se apenas incluir os nomes de arquivo fontes, separados por
idad
espao, em FONTES. Ainda demandaria alguma edio no Makefile aps a incluso de um novo arquivo, mas isso
permitiria uma melhor administrao dos fontes a serem compilados. Alm disso, o resto das regras e macros do Makegile
continuariam com sua utilidade mantida.
No caso de a pessoa tentar isso, ela no poderia normalmente dar ENTER e em seguida colocar o nome de um arquivo

(considera-se que o valor da varivel termina de ser lido em um caracter nova-linha). Porm, possvel enganar o
sistema para que ele ache que um ENTER no o inicio de uma nova linha. Para isso, coloque \ antes de dar ENTER e

siga em frente. O ENTER dado no ser lido pelo m k (ser escapado) e o valor na linha de baixo ser acrescentado
ae
aos demais normalmente. Por exemplo,

FNE=ot1cfne. fne.
OTSfne. ot2c ot3c
fne.
ot4c

Faria com que voc recebesse a mensagem de erro M k f l : : * * f l a d o s p r d r P r . Mas:


aeie2 * atno
e a a o . a e .

FNE=ot1cfne. fne. \
OTSfne. ot2c ot3c
fne.
ot4c

Seria correto e retornaria a expanso para f n e . f n e . f n e . f n e . .


ot1c ot2c ot3c ot4c
Bem, estamos quase acabando esse assunto chato de Makefiles. Vamos apenas falar de um ltimo tpico.

Phony targets (Alvos nulos)


L no incio, quando falamos do alvo default a l mencionamos o fato de ele ser um phony target, um alvo nulo
l ,
Na verdade, phony targets so alvos que no vo gerar algum arquivo de sada. Um alvo assim tem como objetivo facilitar
atividades que o desenvolvedor necessita fazer, como limpar os arquivos objeto antigos, empacotar um tarball (um
arquivo compactado . a . zou . a . z ), entre outros.
trg
trb2
Uma coisa importante que o mesmo no ir gerar arquivos, mas bom por via das dvidas indicar que esses alvos so
Phony Targets para que o m k no exija um arquivo com o nome do Phony Target ou para evitar que a existncia de
ae
um arquivo com o mesmo nome do alvo faa com que a regra pare de funcionar. Para isso, utilizamos o alvo especial
. H N para isolar todos os Phony Targets por meio dele. Caso contrrio, podemos receber mensagens de erros como a
POY
seguinte, ao utilizar uma Phony Target c e n por exemplo:
la,

mk:**Smrgapr poesroav `la Pr.


ae * e er aa rcsa
lo cen. ae

Abra seu arquivo Makefile-1 e adicione as seguintes linhas ao final do mesmo:

cen
la:

r - *orltad *
m f . oerno ~
tr
a:
trcj rltad.a.z $FNE)$HAES
a vf oernotrb2 (OTS (EDR)

Seu arquivo Makefile-1 dever estar parecendo algo assim:

#
#Sgnatnaiad Mkfl
eud ettv e aeie
#
CCM=c
_OPgc
FNE=(idad*c
OTS$wlcr .)

#euvl adzrFNE=ancrga. lta.


qiae
ie OTSmi. ersc ersc

HAES$wlcr *h
EDR=(idad .)
al rltad
l: oerno

rltad:$FNE:c.)
oerno (OTS.=o
$CCM)- $ $
(_OP o @ ^
%o %c$HAES
.: . (EDR)
$CCM)- $ - $
(_OP c < o @
cen
la:
r - *orltad *
m f . oerno ~
tr
a:
trcj rltad.a.z $FNE)$HAES
a vf oernotrb2 (OTS (EDR)

Perceba que ainda no definimos c e n e t r como Phony Thargets. Agora, vamos definir nossas Phony Thargets.
la
a
Antes de a l inclua a seguinte regra:
l,

.HN:alcentr
POY l la a

Perceba que agora definimos eles como Phony Targets, perceba que incluimos a l Essa uma boa prtica, incluir a l
l.
l
como Phony Target para evitar problema maiores no caso da existncia de uma arquivo a l Teste as regras clean e tar,
l.
lembrando de usar - M k f l - para indicar o arquivo de Makefile desejado (se voc colocar essas regras no arquivo
f aeie1
Makefile padro, no precisar do - ). Muitos softwares livres incluem em seus makefiles Phony Targets como c e n
f
la,
i s a l p c a e etc Tente criar vrios Phony Targets para fazer operaes de instalao, remoo, limpeza dos
ntl, akg,
objetos antigos, compactao, e por a afora.

O nosso Makefile genrico deve ficar como o abaixo. Esse um timo Makefile padro para ser adotado no dia a dia:

CCM=c
_OPgc
FNE=(idad*c
OTS$wlcr .)
HAES$wlcr *h
EDR=(idad .)
.HN:alcentr
POY l la a
al rltad
l: oerno

rltad:$FNE:c.)
oerno (OTS.=o
$CCM)- $ $
(_OP o @ ^
%o %c$HAES
.: . (EDR)
$CCM)- $ - $
(_OP c < o @
cen
la:
r - *orltad *
m f . oerno ~
tr
a:
trcj rltad.a.z $FNE)$HAES
a vf oernotrb2 (OTS (EDR)

Para saber mais


No pretendo falar por um bom tempo mais sobre Makefiles. Essa introduo deve ser o suficiente para o dia a dia e para
o que precisaremos por um LONGO TEMPO no nosso curso. Se voc desejar maiores informaes, abaixo alguns links
teis:
Artigo sobre make da Wikipedia: http://en.wikipedia.org/wiki/Make_%28software%29;
Manual do GNU Make: http://www.gnu.org/s/hello/manual/make/index.html#Top;
Especificao
do
make
segundo
http://pubs.opengroup.org/onlinepubs/009695399/utilities/make.html;

OpenGroup:

Alm dos seguintes tutoriais, onde me baseei para escrever esse artigo:
http://www.opussoftware.com/tutorial/TutMakefile.htm
http://ubuntuforum-pt.org/index.php/topic,21155.0.html
http://www.eng.hawaii.edu/Tutor/Make/
http://www.cs.umd.edu/class/fall2002/cmsc214/Tutorial/makefile.html
http://www.metalshell.com/view/tutorial/120/
http://developers.sun.com/solaris/articles/make_utility.html#5
Bem, agora chega de falarmos de ferramentas. Prometo na prxima aula voltar programao em C, com estruturas e
tipos do usurio. At l!

Boas Prticas, GCC, GNU Compiler Chain, Intermedirio, Introduo, make, Makefiles, Projetos, Tcnicas

Sobre arquivos de cdigo-fonte, arquivos de cabealhos e projetos


Ol!
Hoje no iremos apresentar nenhum cdigo.
Putzgrila! voc deve estar dizendo.
Na realidade eu estou dizendo apenas meia-verdade, pois no apresentaremos nenhum cdigo, digamos assim, novo.
que chegamos em um momento do nosso curso onde precisaremos de alguma teoria antes de seguir em frente.
At agora, temos visto programas que podemos compilar e editar em apenas um arquivo de cdigo-fonte e algumas
dezenas de linhas. Talvez o jogo de roletrando que fizemos na ltima aula tenha sido o maior cdigo que escrevemos,
com em torno de 170 linhas.
Isso pode parecer grande coisa, mas um programa assim extremamente incomum.
Como assim?, voc deve estar pensando.
Se pararmos para pensar e compararmos com programas como os que usamos no dia a dia, o nosso roletrando
extremamente tosco e rudimentar. No precisamos pegar leve, pois essa a verdade. Programas comuns do dia a dia,
como um navegador web, o prprio sistema operacional ou um compilador, so programas muito mais complexos e,
portanto, com muito mais cdigo. Por exemplo: o Windows XP, segundo a Wikipedia, possui em torno de 45 milhes
(sim, MI-LH-ES) de linhas de cdigo. No mesmo artigo, mostra-se que o kernel (a parte mais fundamental do sistema
operacional) do Linux 2.6.35 possui 13,5 milhes de linhas de cdigo. Em Julho de 2011, quando esse artigo foi escrito,
segundo o site Ohloh.net, o LibreOffice (uma suite de produtividade livre) possuia mais de 7,5 milhes de linhas de
cdigo. Pelo mesmo site, e na mesma poca, constava que o Mozilla Firefox (um popular navegador Web) possuia em
torno de 5 milhes de linhas de cdigo.
Usar tal estatstica problemtico, uma vez que o nmero de linhas de cdigo para descrever-se um programa pode
mudar de linguagem de programao para linguagem de programao: as linguagens podem incorporar em sua
infraestrutura uma maior ou menor quantidade de bibliotecas e funes utilitrias, permitindo que o programador
desenvolva programas mais enxutos ao utilizar-se de funcionalidades j previstas na linguagem de programaao. Porm,
o caso aqui no comparar a complexidade no desenvolvimento ou coisas do gnero. Isso no vem ao caso, pois meu
objetivo aqui no tentar determinar a linguagem que permite produzir mais programa com menos cdigo. Meu objetivo
aqui fazer voc pensar: imagine ter que carregar em um editor todos os, por exemplo, 5 milhes de linhas de cdigo
do Mozilla Firefox para uma manuteno. Triamos srios problemas para localizar o que desejamos corrigir, editar e
compilar esse cdigo novo, alm do tempo gasto para tais procedimentos.
Entretanto, esses programas existem. Como?, voc deve se perguntar?
No caso, graas possibilidade de compilao parcial.
Quando falamos, l no incio do curso, que um compilador compila todo o cdigo e gera o cdigo binrio, eu disse apenas
meia-verdade. Realmente, o compilador gera UM cdigo binrio a partir do cdigo fonte. Prm, quem realmente cria o
binrio esxecutvel baseado no cdigo binrio gerado pelo compilador um programa ou rotina do compilador chamado
linkeditor (ou linker).
Por que isso necessrio?
O tempo todo, desde o incio do curso, temos citado funes. Temos visto funes o tempo todo, desde os p i t (
rnf)
at nossas funes personalizadas. Porm, no momento em que o sistema operaciona executa um programa, ele vai

apenas lendo e lendo cdigos at chegar ao fim do mesmo. Ele pode ter execues condicionais, baseadas em i ,
f
w i e etc (na verdade, tudo vai a um nvel ainda mais baixo no fim das contas, mas no vamos entrar nesses
hl,
detalhes), mas na verdade mesmo com esses saltos cedo ou tarde o programa volta para onde ele estava, at chegar ao
fim dele.
Cada funo, no importa qual, representa, digamos assim, um salto. Como dissemos no passado:

Quando uma funo chamada, o programa principal passa o controle da execuo para outro ponto
completamente arbitrrio dentro do espao que o programa ocupa na memria e executa os comandos
informados na funo. Uma vez que termine, ele tem que devolver o controle para que o programa
principal volte a ser executado, o que em C indicamos com r t r , e assim sucessivamente at que o
eun
programa chegue ao fim do programa principal e seja encerrado.

A funo do linker justamente calcular as posies de cada funo na memria (lembra que falamos que as funes
ocupam memria, quando falamos dos ponteiros de funo?) e colocar no cdigo binrio as posies corretas, de modo
que o sistema operacional consiga fazer todos os procedimentos de carga do cdigo e execuo de maneira adequada.
Alm disso, o linker tambm liga (em ingls, to link significa ligar) determinadas funes e cdigos que no fazem parte
do cdigo binrio que estamos usando para gerar o executvel, sendo que esses ltimos podem incluir tanto cdigos
binrios de bibliotecas padrao quanto cdigos que esto em outros arquivos passados para o linker.
Atualmente a separao entre compilador e linker est cada vez menor, pois muitos compiladores trazem o linker como
uma rotina embutida em si, e no como um programa em separado, mas a funo do linker e sua utilidade ainda existe.
OK Toda essa teoria para que?
Se pensarmos agora, com o que sabemos, muito mais interessante separarmos o cdigo fonte em pequenos arquivos
englobando uma pequena parte do cdigo-fonte, pois:
1. O programador que for fazer manuteno em determinada parte do cdigo ter uma chance muito menor de,
inadvertivamente, mexer em cdigo fonte indevido e provocar erros e bugs, pois cada parcela do cdigo estar
contido em um arquivo de cdigo-fonte;
2. A compilao ser mais rpida com o tempo. claro que muito provavelmente a compilao da primeira vez
ainda ser demorada, pois cada arquivo de cdigo binrio ter que ser gerado e, aps todos os arquivos serem
gerados, eles devero ser alimentados ao linker (ou ao compilador atualmente), para que o mesmo seja
transformado em cdigo executvel final. Porm, conforme as manutenes forem se fazendo necessrias, a
compilao ser mais rpida pois poderemos compilar apenas as parcelas de cdigo (ou seja, os arquviso de
cdigo-fonte) que foram alterados e alimentar os cdigos binrios modificados junto com os demais ao linker.
Desse modo, ao invs de compilar, por exemplo, alguns milhes de linhas de cdigo, podemos restringir a
compilao a algumas dezenas, ou at menos. De fato, muitos desses programas de grande porte, como o
Firefox, utilizam ferramentas que permitem automatizar o processo de compilao de modo que elas detectam
quando um ou mais arquivos foram modificados e geram novamente o cdigo binrio e o executvel final;
3. Podemos fechar o cdigo. Essa expresso quer dizer que, ao oferecermos um cdigo para algum, podemos
oferec-lo na forma de um binrioo, ao invs de oferec-lo na forma de cdigo fonte. Isso pode ser interessante
quando a pessoa ou empresa est desenvolvendo um cdigo cujo funcionamento interno no deve ser divulgado
por razes comerciais ou legais. Desse modo, pode-se oferecer o cdigo binrio ao usurio do mesmo que ir
incorpor-lo ao seu cdigo por meio do linker;
4. Fica mais fcil aprender o que o programa faz com o tempo: ao invs de obrigar o desenvolvedor a ler todo o
cdigo de uma s vez e tentar entender o que ele faz, o desenvolvedor pode se restringir a ler apenas

determinadas partes do cdigo. Como a leitura de um cdigo-fonte escritto por um outro desenvolvedor no
um procedimento exatamente simples, ao reduzir a necessidade do desenvolvedor em se aprofundar no estudo
do cdigo para resolver um problema (que podde ser imediato), ganha-se tempo no desenvolvimento de
solues;
OK E como dividimos um programa e o compilamos?
Primeiro, temos que criar em nossa mente a estrutura dos arquivos de cdigo fonte. claro que podemos ter arquivos
individuais de cdigo fonte para cada funo que formos criar, mas isso no uma boa ideia: se um arquivo nico de
cdigo fonte algo ruim, uma grande quantidade de arquivos de cdigo fonte tambm ruim, pois dificultaria a
localizao do arquivo que nos interessa em meio ao mar de arquivos gerado no processo. Em geral, todo bom projeto
de programao (chama-se projeto todo o conjunto de arquivos cujo fim resultar em um programa binrio executvel
e/ou em bibliotecas de funes teis ao desenvolvedor) tem seus arquivos fontes organizados por funo.
Para exemplificarmos esse processo, vamos pegar nosso programa do jogo do Roletrando e transform-lo em um
projeto. Organizaremos nosso projeto Roletrando em trs arquivos:
m i . nele est a lgica principal do jogo. Existe o hbito entre desenvolvedores C de chamar-se o arquivo de
anc
cdigo fonte principal do programa de m i . , mas essa uma deciso arbitrria, uma boa prtica que no precisa
anc
necessariamente ser seguida ( uma boa prtica, no uma obrigao). Estou seguindo essa boa prtica como uma
forma de facilitarr a vida de quemm for ler o cdigo, mas isso no obrigatrio;
r g a . nesse arquivo, colocaremos as duas funes que lidam com as regras do jogo: e p i a R g a ( ,
ersc
xlcrers)
que mostrada no incio do jogo para que o jogador conhea o funcionamento do jogo, e t s a e t ( que
etCro)
checa se o jogador conseguiu acertar a palavra em questo;
l t a . colocaremos aqui as funes que validam o acerto ou erro na tentativa de acerto das letras:
ersc
c e a e r ( , que indica se a letra existe ou no na palavra, e c n a a t n e ( , uma funo utilizada para
hcLta)
otFlats)
saber quantas letras ainda falta (pois a partir da metade de letras acertadas, o jogo passa a pedir que o jogador
tente descobrir qual a palabra em questo);
H um porm: como os cdigos de cada arquivo faro referncias a funes contidas nos demais arquivos, seria
necessrio adicionar os prottipos de funo de TODAS as funes do programa em TODOS os arquivos. Isso tornaria o
programa complexo e suscetvel a erros: pense, por exemplo, que voc modificou uma funo, mas no seus prottipos.
O linker no conseguiria ligar corretamente as funes, provocando o erro. O correto seria que o compilador provocasse
um erro ou alerta indicando que os cdigos antigos no foram adaptados para aquela funo nova. Para que isso no
acontea, criaremos o nosso prprio arquivo de cabealhos. Quando falamos dos prottipos de funo dissemos que:

Funes ? Parte 2 Aulas de C


O que o compilador normalmente precisa saber como trabalhar com uma funo, ou seja, os valores
que ele precisa passar para a mesma como parmetros e o tipo de retorno da mesma. O cdigo em si
no precisa sequer ser descrito como parte do seu programa, podendo estar (o cdigo em si) em
qualquer outro lugar. (lembram das bibliotecas, como s d o h s r n . e s d i . ? Na realidade
ti., tigh
tlbh
eles so teis para o compilador saber como usar as funes que eles ?representam?. Os cdigos esto
armazenados em outras bibliotecas e arquivos dentro do sistema operacional ou do compilador).

Em C, chamamos esses arquivos que contm os prottipos de funo de arquivos de cabealho ou header files (por
isso a extenso deles tipicamente . de header). Como esses arquivos so includos pelo compilador (por meio das
h

diretivas # n l d ) ao cdigo fonte no momento da compilao, esses arquivos so teis para definir valores padro e
icue
prottipos de funes que sero usadas em vrias partes do cdigo fonte, de modo a isolar funes desnecessrias ou
com nomes similares e assim no provocar erros, alm de ajudar na documentao e manuteno do cdigo. No nosso
caso, criariamos um arquivo roletrando.h com os cabealhos e definies necessrias. Uma outra grande vantagem dos
arquivos de cabealhos que o compilador consegue trazer arquivos de cabealho que estejam includos em arquivos de
cabealho at um determinado nvel, em geral suficiente para que um arquivo de cabealho do projeto nosso inclua os
arquivos de cabealho padro do C que precisamos.
Sem muito papo, vamos ver como ficou disposto o cdigo no nosso projeto e explicar pequenas alteraes no mesmo. O
arquivo m i . ficar como abaixo:
anc

/
*
*mi.
anc
*
/
#nld oernoh
icue rltad.
itmi (od
n an vi)
{
/ Dcaad vraes
/ elrno aivi
/ Bbitc d plva
/ iloea e aars
ca plva[[AAH_AARS=oia,eia,ahc ebac
hr aars]TMNOPLVA]{rtn rtn plao, lmrna,
itm uia,croo,atsa,
ssea, mscuis fnai
aaaeohdr,aiue,pcc
mlbrs,snaottdaoa,
ohdr}
snao;
/ Pnersncsais
/ otio eesro
ca *aarEclia *ersets
hr plvasohd, ltaCra;
/ Agmsvraesd aoo
/ lua aivi e pi
ittnaiaErds0i
n ettvsraa=,;
epiaRga(;
xlcrers)
sadtm(UL) / Srepr mdfcratbl d neo pedrn(ieNL); / ev aa oiia
aea e mrs suo
aetis
laro
plvasohd=aarsrn(%(iefplva)TMNOPLVA)1]
aarEcliaplva[ad)(szo(aars/AAH_AARS-);
ltaCra=alcsre(aarEclia+)
ersetsmlo(tlnplvasohd)1;
i (ltaCra)
f !ersets
{
pit(Nocneu aoa mmra\;
rnfa osgi lcr eoi!n)
rtr()
eun1;
}

mme(ersets sre(aarEclia+)
estltaCra,, tlnplvasohd)1;
mme(ersets sre(aarEclia)
estltaCra,-, tlnplvasohd);
ittnaiata=;
n ettvAul0
wietnaiaErdsNMTNAIA)
hl(ettvsraa<U_ETTVS
{
ca mnaer;
hr ihLta
tnaiata+;
ettvAul+
pit (Oa,tnaian % (dtnaia erds:,tnaiata,
rnf ky ettv o d % ettvs raa) ettvAul
tnaiaErds;
ettvsraa)
saf(% &ihLta;
cn c, mnaer)
gthr)
eca(;
i (ceaer(aarEclialtaCra,ihLta)
f !hcLtaplvasohd,ersetsmnaer)
{
pit(Qepn,es ltanoaaee\;
rnfu ea sa er a prc!n)
tnaiaErds+
ettvsraa+;
cniu;
otne
}
pit(Aplvaaeaoae %\ltaCra)
rnf aar t gr : sn,ersets;
i (otFlatsltaCra)=sre(aarEclia/)
f cnaatne(ersets<(tlnplvasohd)2)
{

i(cnaatne(ersets=0|tsaet(aarEclia&ettvsraa)
f(otFlatsltaCra)=)|etCroplvasohd,tnaiaErds)
{
pit(Miobm Vc aetu\;
rnfut e! oe cro!n)
fe(aarEclia;
replvasohd)
fe(ersets;
reltaCra)
rtr()
eun0;
}
}
}
}

Primeira coisa: se voc comparar o cdigo que estamos colocando aqui com o que colocamos originalmente, ver que,
alm de curto, ele no traz as funes como c e a e r e afins. Isso normal e exatamente o que queremos: isolar
hcLta
o cdigo principal em um arquivo e os demais em outros.
Alm disso, veja que, ao invs dos # n l d s, # e i e e prottipos de funo da verso antiga, colocamos apenas
icue dfns
um # n l d , que levemente diferente do que os # n l d s que vimos anteriormente:
icue
icue

#nld oernoh
icue rltad.

Esse #include por trazer o nome do arquivo de incluso entre aspas duplas, ao invs de cercada por sinais de maior e
menor (como em # n l d < t i . > Qual a diferena?, voc deve se perguntar.
i c u e s d o h ).
A diferena est relacionada com a idia de biblioteca padro:
Toda linguagem de programao (e C no exceo), tem sua biblioteca padro de funes. Essa biblioteca representa
conjuntos de funes que o programador pode esperar que um ambiente de desenvolvimento inclua, embora isso no
seja obrigatrio (em ambientes de pouca memria ou com usos especficos, pode-se implementar C sem vrias das
bibliotecas padro). Somada biblioteca padro, toda linguagem de programao permite que o desenvolvedor a
expanda, incluindo bibliotecas para as mais diversas finalidades.
Em C, quando um arquivo de cabealho no # n l d vem cercado de sinais de maior e menor, o compilador procura o
icue
arquivo com o nome em questo no apenas nos diretrios do projeto em questo, mas em diretrios adicionais
normalmente definidos em uma varivel de ambiente chamada I C U E(isso no obrigatrio, mas tem se tornado um
NLD
padro de fato devido ao intensivo uso desse procedimento). Esses diretrios contm diversos arquivos de cabealho
para diversas bibliotecas, tanto padro do C quanto para as bibliotecas adicionadas pelo desenvolvedor. Alm disso, o
prprio compilador tem certa inteligncia para localizar bibliotecas padro. Por exemplo, no Linux e na grande maioria
dos ambientes Unix padronizados, os arquivos de cabealho da biblioteca padro esto em / s / n l d , e caminhos
uricue
como / s / o a / n l d so reservados para bibliotecas adicionais.
urlclicue
Quando, porm, no C, a diretiva de compilao # n l d cercada por aspas duplas, o C compreende que o cabealho
icue
em questo parte especfica desse projeto e no utiliza os caminhos em I C U Epara procurar por ele, procurando-o
NLD
dentro do diretrio no qual o projeto est sendo compilado.
Essa a diferena bsica nesses dois casos: existem mais detalhes, mas iremos nos restringir no momento a essa
explicao bsica.
Bem, dito isso, o resto do cdigo no possui mistrios: continua sendo o m i ( do jogo de roletrando que fizemos na
an)
ltima aula, sem modificao alguma no cdigo. Porm, se voc tentar compilar ele agora, voc receber mensagens
de erro como as a seguir (no caso, se voc usar GCC):

mi.::4 err rltad.:Aqioo drtronoecnrd


anc52: ro: oernoh ruv u iei notao
mi.:I fnto an:
anc n ucin mi
mi.:3 err AAH_AARS udcae (is uei ti fnto)
anc1: ro: TMNOPLVA nelrd frt s n hs ucin
mi.:3 err (ahudcae ietfe i rpre ol oc
anc1: ro: Ec nelrd dniir s eotd ny ne
mi.:3 err frec fnto i apasi.
anc1: ro: o ah ucin t per n)
mi.:6 err UL udcae (is uei ti fnto)
anc2: ro: NL nelrd frt s n hs ucin
mi.:0 wrig icmail ipii dcaain o biti fnto
anc3: ann: noptbe mlct elrto
f ul-n ucin
alc
mlo
mi.:0 wrig icmail ipii dcaain o biti fnto
anc3: ann: noptbe mlct elrto
f ul-n ucin
tln
sre
mi.:4 wrig icmail ipii dcaain o biti fnto
anc3: ann: noptbe mlct elrto
f ul-n ucin
rnf
pit

mi.:8 wrig icmail ipii dcaain o biti fnto


anc3: ann: noptbe mlct elrto
f ul-n ucin
est
mme
mi.:2 err U_ETTVS udcae (is uei ti fnto)
anc4: ro: NMTNAIA nelrd frt s n hs ucin
mi.:7 wrig icmail ipii dcaain o biti fnto
anc4: ann: noptbe mlct elrto
f ul-n ucin
rnf
pit
mi.:8 wrig icmail ipii dcaain o biti fnto
anc4: ann: noptbe mlct elrto
f ul-n ucin
cn
saf

A primeira linha j d a pista do que fizemos de errado:

mi.::4 err rltad.:Aqioo drtronoecnrd


anc52: ro: oernoh ruv u iei notao

Sem o arquivo r l t a d . , o compilador no sabe uma grande quantidade de coisas, como, por exemplo, qual o
oernoh
valor de T M N O P L V A . Em alguns compiladores modernos, o compilador de certa forma inteligente a ponto
AAH_AARS
de conseguir evitar erros mais srios, como em funes bsicas como m l o e p i t , mas ainda assim o sistema
alc
rnf
possui problemas para determinar tudo o que o programa precisa saber para compilar. Portanto, vamos criar o arquivo
rltad.:
oernoh

/
*
*rltad.
oernoh
*
/
#nld <ti.>
icue sdoh
#nld <tlbh
icue sdi.>
#nld <ieh
icue tm.>
#nld <tigh
icue srn.>
#eieNMTNAIA 6
dfn U_ETTVS
#eieTMNOPLVA 2
dfn AAH_AARS 0
it ceaer (os ca* plvasohd, cnt ca* ltaCra, ca
n
hcLta cnt hr
aarEclia
os
hr
ersets
hr
lta;
er)
vi epiaRga (od;
od xlcrers vi)
itcnaatne (os ca*ltaCra)
n otFlats cnt hr ersets;
ittsaet (os ca*plvasohd,it tnaiaErds;
n etCro cnt hr aarEclia n* ettvsraa)

Aqui, interessante comparar o header file com o nosso programa original: perceba que como se tivssemo pego o
cdigo do incio da nossa verso original e extrado apenas as primeiras linhas, at o incio da funo m i ( e colocado
an)

ele no arquivo de cabealho.


Ento eu posso colocar qualquer cdigo C em um arquivo de cabealho?
Sim, voc pode, mas NO, voc no deveria.
Ao colocar cdigo convencional no arquivo de cabealho, voc est jogando fora todas as vantagens relacionadas
separao de cdigo em arquivos individuais E adicionando problemas adicionais. Por exemplo: imagine que voc tenha
adicionado uma funo real (no um prottipo) a um arquivo de cabealho que ser usado em vrios arquivos de cdigo
fonte. Ao compilar o cdigo, o compilador ir adicionar ao cdigo fonte original o arquivo de cabealho e o compilar.
Aqui parece OK. Mas, quando o linker ou o compilador forem fazer as ligaes do cdigo, haver VRIAS cpias da
mesma funo em forma de cdigo binrio para o linker usar. Como ele no saber qual usar, ele provocar mensagens
de erro.
Como boa prtica, podemos considerar que um arquivo de cabealho deve restringir-se a:
1. Diretivas de pr-processador, como #include ou #define, que sero usadas por todos os arquivos que incluirem
esse arquivo de cabealho, e;
2. Prottipos de funo;
Obviamente voc pode incluir qualquer coisa vlida em um programa C normal, mas isso aumentaria a complexidade do
cdigo, jogaria fora todas as vantagens do isolamento de cdigo e colocaria potenciais erros e bugs.
OK, podemos agora compilar o nosso cdigo fonte sem aquele monte de mensagens de erro. Porm, ao tentarmos
compilar o cdigo em m i . , teremos a seguinte mensagem de erro:
anc

/m/cdz4o I fnto `an:


tpcr9r.: n ucin mi
mi.:.et03c:udfndrfrnet `xlcrers
anc(tx+x2) neie eeec o epiaRga
mi.:.et047:udfndrfrnet `hcLta
anc(tx+xc) neie eeec o ceaer
mi.:.et059:udfndrfrnet `otFlats
anc(tx+0) neie eeec o cnaatne
mi.:.et05f:udfndrfrnet `otFlats
anc(tx+x3) neie eeec o cnaatne
mi.:.et058:udfndrfrnet `etCro
anc(tx+5) neie eeec o tsaet
clet:l rtre 1ei sau
olc2 d eund
xt tts

Se voc parar para pensar, vai notar que realmente no implementamos nenhuma das funes mostradas na mensagem
de erro e, embora o compilador saiba da existncia das mesmas (devido aos arquivos de cabealho), no sabe onde
elas esto (elas no esto a mesmo) e, portanto, no consegue gerar o cdigo fonte em questo.
Portanto, vamos implementar as funes que faltam por meio dos dois arquivos que faltam, que so r g a . :
ersc

/
*
*rga.
ersc
*
/
#nld oernoh
icue rltad.
vi epiaRga (od
od xlcrers vi)
{

/ Epiad a rga d jg pr ojgdr


/ xlcno s ers o oo aa
oao
pit (Oa,vmsepia a rga!n)
rnf ky ao xlcr s ers\;
pit (E cneo agms plva, e vu ecle aetraet ua
rnf u ohc
lua
aars
o
sohr laoimne m
dls\;
ea.n)
pit (Vc vim dzrqa ltavc ah qees plvatm\;
rnf oe a e ie ul er oe ca u sa aar e!n)
pit (S vc err vucniea uatnaiaerd!n)
rnf e oe ra, o osdrr m ettv raa\;
pit (S vc aetr vut msrrod a lta eto\;
rnf e oe cra, o e ota ne s ers sa!n)
pit (Qad vc tvraetd mtd dsplva,vut dr;
rnf uno oe ie crao eae a aars o e a )
pit (acac d dzrqa plvaees.\;
rnf hne e ie ul aar
sa n)
pit (S vc err vucniea uatnaiaerd!n)
rnf e oe ra, o osdrr m ettv raa\;
pit (S vc aetratsd aaa a tnaia,vc vne\\\;
rnf e oe cra ne e cbr s ettvs oe ec!nnn)
pit (Agmsosraos\;
rnf lua bevce:n)
pit (Vc tm% tnaia qevc pd err\NMTNAIA)
rnf oe e d ettvs u oe oe ra.n,U_ETTVS;
pit (Nnuaplvapsu aets cdlao tea\;
rnf ehm aar osi cno, eih u rm.n)
pit (Noetudfrninomiclsemnsua.\\;
rnf a so ieecad asua
iucls nn)
}
ittsaet (os ca*plvasohd,it*ettvsraa)
n etCro cnt hr aarEclia n tnaiaErds
{
ca *ihRsot;
hr mnaepsa
mnaepsa(hr)alcsre(aarEclia+)
ihRsot=ca*mlo(tlnplvasohd)1;
mme(ihRsot,,sre(aarEclia+)
estmnaepsa tlnplvasohd)1;
i (mnaepsa
f !ihRsot)
{
pit(Nocneu aoa mmra\;
rnfa osgi lcr eoi!n)
rtr()
eun1;
}
ca *ovra=ihRsot;
hr cnesomnaepsa
pit(Qa plvavc ah qeees?;
rnful aar oe ca u
sa )
fesmnaepsasre(aarEclia+,ti)
gt(ihRsot,tlnplvasohd)1sdn;
/ Cnetnopr mnsua eeiiad o crcee d nv lna\
/ ovred aa iucls
lmnno s aatrs e oa ih
n
wie(cneso
hl *ovra)
{
*ovra=(cneso=A)&*ovra<)
cneso(*ovra>&(cneso=Z)?
*ovra-A+a:cneso
cneso*ovra;
cneso+
ovra+;
}
mnaepsasre(ihRsot)=;
ihRsot[tlnmnaepsa]
i(tnm(ihRsot,aarEcliasre(aarEclia)=)
fsrcpmnaepsaplvasohd,tlnplvasohd)=0
{
fe(ihRsot)
remnaepsa;
rtr()
eun1;

}
es
le
{
pit(Qepn,vc eru\;
rnfu ea oe ro!n)
(tnaiaErds+;
*ettvsraa)+
fe(ihRsot)
remnaepsa;
rtr()
eun0;
}
}

E letras.c:

/
*
*lta.
ersc
*
/
#nld oernoh
icue rltad.
itceaer (os ca*plva cntca*ltaCra,ca lta
n hcLta cnt hr aar, os hr ersets hr er)
{
/ Aetsotds( idc qenotmaltae qeton plva
/ cro bio 0 nia u a e
er m usa a aar)
itaets0
n cro=;
/ Pnersasrmiiilzds
/ otio
ee ncaiao
ca *aarAaiaa *ets
hr plvanlsd, cra;
/ Iiildnoo pnerscmo vlrsrcbds
/ ncaiad s otio o s aoe eeio
plvanlsd=ca*plva
aarAaiaa(hr)aar;
cra=ca*ltaCra;
ets(hr)ersets
ca ltaoprd=(er>&(er<)ltalta
hr erCmaaa(lta=A)&lta=Z)?er-A+a:er;
wie(plvanlsd)
hl *aarAaiaa
{
i (plvanlsd=ltaoprd)
f *aarAaiaa=erCmaaa
{
aets+
cro+;
*etsltaoprd;
cra=erCmaaa
}
plvanlsd+;
aarAaiaa+
cra+;
ets+
}
rtr aets
eun cro;

}
itcnaatne(os ca*ltaCra)
n otFlatscnt hr ersets
{
ca *ers(hr)ersets
hr lta=ca*ltaCra;
itflats0
n atne=;
wie(lta)i ((ers+= flats+
hl *ers f *lta+)=-) atne+;
rtr flats
eun atne;
}

OK, voc deve estar pensando


E agora, como compilar esse programa?
Se voc tentar compilar apenas um dos dois ltimos cdigos, voc receber uma mensagem de erro como a seguinte:

/s/i/c/36rda-iu/../../.ct.:I fnto `sat:


urlbgci8-ehtlnx412./../r1o n ucin _tr
(tx+8:udfndrfrnet `an
.et01) neie eeec o mi
clet:l rtre 1ei sau
olc2 d eund
xt tts

Isso quer dizer que o compilador compilou corretamente o cdigo, mas o linker no encontrou nenhuma funo m i ( .
an)
Portanto, o comando padro de compilao que usamos desde o comeo do curso, que vimos l atrs:
O primeiro programa: HelloWorld.c Aulas de C

Entre no mesmo diretrio onde voc gravou o seu H l o o l . e digite o seguinte comando:
elWrdc
gc- Hlool Hlool.
c o elWrd elWrdc

J no serve mais.
Existem duas formas de compilar-se o cdigo fonte com arquivos individuais e obter-se o binrio. A primeira utilizar-se o
comando:

gc- rltad mi. rga. lta.


c o oerno anc ersc ersc

Perceba que a nica diferena aqui entre isso e o que temos feito desde sempre que adicionamos os arquivos .
c
adicionais aps o m i ( . Entretanto esse mtodo no oferece grandes vantagens: voc ainda gastar muito tempo
an)
compilando cdigo-fonte pois, embora o cdigo esteja separado, o compilador precisar reun-lo antes de compilar e ligar

o cdigo. Desse modo, ao menos um dos benefcios da separao dos cdigos em arquivos individuais ter sido perdida
(voc ainda gastar muito tempo compilando todo o cdigo, inclusive os trecho que no mudaram).
O mtodo que nos permite os melhores benefcios aquele em dois passos, onde (1) o compilador gera cada um dos
arquivos binrios de cada cdigo fonte e (2) o linker gera o binrio final a partir do cdigo binrio gerado pelo compilador
previamente. Cada compilador tem sua metodologia para gerar esse processo, e voc deve ler cuidadosamente a
documentao do seu compilador.
No GCC, primeiro iremos gerar o cdigo binrio (que chamaremos de cdigo-objeto para distingir do binrio final, ou
executvel), por meio do comando:

gc- nm_ocdg_beoo- muaqiofnec


c o oed_oioojt. c e_ruv_ot.

A opo -c do compilador GCC apenas faz a compilao do cdigo, sem tentar fazer o link, conforme dito na
documentao do mesmo:

- Compile or assemble the source files, but do not link. The linking stage simply is not done. The
c
ultimate output is in the form of an object file for each source file.
By default, the object file name for a source file is made by replacing the suffix . , . , . , etc., with
c i s
..
o
Unrecognized input files, not requiring compilation or assembly, are ignored.

Ou seja, ele no gerar o o executvel.


No nosso caso, utilizaremos primeiro os comandos:

gc- lta. - lta.


c o erso c ersc
gc- rga. - rga.
c o erso c ersc
gc- mi. - mi.
c o ano c anc

Para gerarmos os arquivos . (os cdigos-objeto) de cada um dos cdigo-fonte e, aps isso, utilizaremos o comando:
o

gc- rltad lta. rga. mi.


c o oerno erso erso ano

Para fazermos o link das funes e obtermos o executvel.

interessante notar que, no caso especfico do GCC, ele possui alguma inteligncia e capaz de distinguir cdigo-fonte
de cdigo-objeto. Portanto, a ltima linha do nosso primeiro passo:

gc- mi. - mi.


c o ano c anc

Poderia ser substituda por:

gc- rltad lta. rga. mi.


c o oerno erso erso anc

Sem problemas. Normalmente, porm, fazemos o passo da gerao de arquivos objetos para cada arquivo-fonte.
E onde est o ganho? At agora, gastei mais tempo lanando comandos! voc deve estar se perguntando.
Imagine que voc est traduzindo o jogo do roletrando para um outro idioma e tem que, portanto, mexer na funo
e p i a R g a ( , em r g a . . Se voc tiver com todos os cdigos-objetos gerados, tudo o que voc precisar
xlcrers)
ersc
fazer gerar novamente o cdigo-objeto de r g a . (r g a . ) e refazer o executvel com o comando para fazer o
ersc erso
link das funes.
Existem muitas outras vantagens em usarmos esse mtodo, mas no vem ao caso agora. Falaremos sobre outras
vantagens no futuro.
Como brincadeira, tente visualizar os programas que criamos at agora e que usam funes, e tente recri-los,
separando as funes em arquivos fontes individuais e criando arquivos de cabealho para esses cdigos. Esse processo
algo padro no desenvolvimento de software e algo muito comum.
Na nossa prxima aula, continuaremos esse assunto, aprofundando um pouco na automao da construo de binrios
a partir de arquivos de cdigo fonte individuas e outras estratgias para organizao de projetos.

Bsico, Boas Prticas, Funes, Projetos, Tcnicas

Matrizes e Ponteiros Parte 3 Ponteiros e Funes


Ol!
Esse tpico ser um pouco diferenciado, portanto vou avisando que ser BEM MAIS LONGO e complexo do que os
anteriores. Isso porque iremos ver como as funes em C so afetadas pelos ponteiros. um tpico bem avanado em
teoria, mas interessante mencion-lo agora, enquanto os conceitos relacionados a ponteiros esto frescos na
memria. Na verdade, esse tpico ser dividido em dois subtpicos:
Ponteiros de funo, ou seja, usar um ponteiro para chamar diferentes funes decididas em tempo de
programao. Isso muito usado, por exemplo, em plugins de programas. Dizendo de maneira bem simplista,
poderamos carregar para a memria o contedo de um arquivo e fornecer o endereo onde esse contedo

carregado se localiza para um ponteiro de funo que o sistema executaria esse cdigo dinamicamente (na
verdade, existem mais complicadores, mas em termos gerais podemos dizer isso);
Passagem de ponteiros para funes: existem detalhes a serem mencionados quando passamos um ponteiro para
uma funo. Isso ocorre com muita freqencia, uma vez que muitos tipos que um programador pode esperar de
uma linguagem de programao (como strings, filas, listas, etc) so resolvidas em C pelo uso de ponteiros;
Bem, vamos comear com o primeiro de nossos subtpicos:

Ponteiros de Funo
Como j cansamos de dizer nesse curso, ponteiros so variveis que armazenam um endereo no qual est um
determinado contedo. Se pensarmos dessa forma, podemos imaginar que isso possa se aplicar a trechos de cdigo.
Por que?, voc deve se perguntar. Porque, embora o cdigo no fique no heap ou em outros pontos de memria
como os dos dados, ele PRECISA ESTAR na memria para funcionar. Ou seja, se um cdigo no estiver carregado na
memria RAM (ao menos no momento da execuo), ele no pode ser executado. De fato, quando chamamos um
programa binrio, a primeira coisa que o sistema operacional faz chamar uma rotina chamada LOADER para copiar os
contedos do arquivo onde o binrio se encontra para um espao de memria especfico (programas podem eles prprio
solicitarem ao SO para carregarem partes de si que ficaram de fora da memria em tempo de execuo isso se chama
carga dinmica e um tpico mais avanado, que no iremos tratar em um futuro prximo). Em seguida, so feitos
procedimentos para iniciar a execuo do programa que mudam conforme a arquitetura do sistema operacional e do
hardware, mas em geral envolve inicializar uma srie de variveis de baixo nvel do sistema operacional e algumas
informaes no nvel dos registradores de hardware (um registrador um espao de memria muito pequeno que
usado diretamente pela CPU para coordenar as operaes de sistema. Fundamentalmente so eles que carregam as
informaes e cdigos para dentro da CPU e devolvem o resultado). De qualquer modo, o programa carregado em
memria e em seguida inicializado sua execuo.
Quando um programa encontra uma chamada a uma funo, ele salta para o local de memria onde o cdigo daquela
funo est e comea a executar o cdigo da funo (existem certos procedimentos internos para impedir que a
execuo da funo afete de maneira indesejada o funcionamento do cdigo que a chamou, mas no entraremos em
detalhes aqui). Ao terminar essa execuo, ele volta para o comando seguinte ao qual a funo foi chamado. Para isso,
obviamente o sistema armazena antes as informaes sobre onde ele estava antes de a funo ser chamada.
Se pensarmos bem, vamos ver que o tempo todo existe manipulao de endereos no momento da execuo de
cdigo. E se h endereos, h ponteiros!
Chega de papo! Nosso programa exemplo da vez uma calculadora simples. O cdigo o seguinte:

#nld <ti.>
icue sdoh
#nld <tlbh
icue sdi.>
itsm (n,it;
n oa it n)
itsbrco(n,it;
n utaa it n)
itmlilcco(n,it;
n utpiaa it n)
itdvso(n,it;
n iia it n)
itmi(od
n anvi)
{
it(fnPitr(;/ omupner d fna vmau
n *ucone)) /
e otio e uco e qi

ita0b0c0oe=;
n =,=,=,pr0
pit (Etecmdi nmrsitio sprdsprepc:;
rnf nr o os ueo ners eaao o sao )
saf(% % &,&)
cn d d, a b;
d
o
{
pit (Eclauaoea as raia cmessnmrs\;
rnf soh m pro
e elzr o se ueo:n)
pit (1)Sm\;
rnf - oan)
pit (2)Sbrcon)
rnf - utaa\;
pit (3)Mlilccon)
rnf - utpiaa\;
pit (4)Dvson)
rnf - iia\;
safd,&pr;
cn(% oe)
}wie((oe>1 & (pr=))
hl !(pr=) & oe<4);
sic(pr
wthoe)
{
cs 1
ae :
{
fnPitrsm;
ucone=oa
bek
ra;
}
cs 2
ae :
{
fnPitrsbrco
ucone=utaa;
bek
ra;
}
cs 3
ae :
{
fnPitrmlilcco
ucone=utpiaa;
bek
ra;
}
cs 4
ae :
{
fnPitrdvso
ucone=iia;
bek
ra;
}
}
c(fnPitr(,)
=*ucone)ab;
pit(Orslaod oeaa dsjd cmo vlrs% e% e%\abc;
rnf eutd a prco eeaa o s aoe d
d
dn,,,)
pit(A fna uaa pr eeua a oeaa dsjd lclz-e e
rnf
uco sd
aa xctr
prco eeaa oaias
m
%\fnPitr;
pn,ucone)

rtr()
eun0;
}
itsm (n a itb
n oa it , n )
{
rtr ab
eun +;
}
itsbrco(n a itb
n utaa it , n )
{
rtr ab
eun -;
}
itmlilcco(n a itb
n utpiaa it , n )
{
rtr ab
eun *;
}
itdvso(n a itb
n iia it , n )
{
i (=0
f b=)
{
pit(Nopsodvdrnd przr!n)
rnfa os iii aa o eo\;
rtr 0
eun ;
}
rtr ab
eun /;
}

Bem, se observarmos bem, no existem grandes novidades aqui. Portanto, vamos nos restringir s explicaes sobre os
ponteiros de funo. A primeira coisa importante a se notar est na declarao do ponteiro de funo:

it(fnPitr(;/ omupner d fna vmau


n *ucone)) /
e otio e uco e qi

Se voc reparar, lembra levemente um prottipo de uma funo. Porm, perceba que voc tem o ( f n P i t r ( ,
*ucone))
que indica que um ponteiro de funo. Poderamos ler esse cdigo como f n P i t r um ponteiro que aponta
ucone
para o cdigo de uma funo que devolve i t Outra coisa a se notar que aqui no tentamos resolver a parte da
n .
parametrizao. Isso importante de ser lembrado pois em teoria voc pode usar f n P i t rpara executar qualquer
ucone
funo que retorne i t No momento em que usarmos o ponteiro de funo, porm, o momento no qual teremos
n.
que resolver a parte da parametrizao. No momento, basta saber que aqui na realidade declaramos uma varavel,
ponteiro, que ir apontar para o cdigo de uma funo que ir retornar um valor i t
n.
Em seguida, temos um switchcase que se baseia em um valor que passamos para ele como um i tpara decidir qual a
n

operao a ser feita. Cada nmero possui um cdigo similar ao abaixo:

fnPitrmlilcco
ucone=utpiaa;

Se olharmos no incio, veremos que declaramos funes que iro retornar valores int (ou seja, do tipo que aceito por
fnPitr
u c o n e ):

itsm (n,it;
n oa it n)
itsbrco(n,it;
n utaa it n)
itmlilcco(n,it;
n utpiaa it n)
itdvso(n,it;
n iia it n)

OK. Mas a sua pergunta deve ser como o C sabe tratar as coisas nesse caso?. Para o C, se voc passa o nome de uma
funo sem os seus parmetros, voc est na realidade passando uma varivel, ponteiro, para uma funo cujo retorno
ser o tipo que a funo retorna. Se pegarmos o nosso caso:

fnPitrmlilcco
ucone=utpiaa;

Veremos que na nossa lista de funes declaradas existe uma funo i t m l i l c c o ( n , i t ; Desse modo,
n utpiaa it n).
o C trata o m l i l c c o nesse caso no como um comando, mas como uma varivel. Portanto, ele devolver o
utpiaa
endereo da posio de memria onde essa funo comea. Isso comprovado pelo comando:

pit(A fna uaa pr eeua a oeaa dsjd lclz-e e


rnf
uco sd
aa xctr
prco eeaa oaias
m
%\fnPitr;
pn,ucone)

que ir exibir o endereo de memria onde a funo est armazenada.


Voltando

ao

programa:

sabemos

como

declarar

um

ponteiro

de

funo

(algo

como

tp
io

( n m D P n e r D F n a ) ) e sabemos como passar o endereo para um ponteiro de funo (como para


*oeootioeuco()
qualquer outro). E para executarmos esse comando, como fazemos?

c(fnPitr(,)
=*ucone)ab;

dessa forma. Usamos uma escrita similar da declarao do ponteiro, mas agora indicando os parmetros desejados.

AQUI MORA DRAGES!!!


O C no faz checagem de cabealho de funo e das tipagens envolvidas em um ponteiro de funo. Isso passa a ser
responsabilidade do programador, tomar os cuidados de passar os parmetros da forma correta. Portanto, devemos ter
muito cuidado quanto parametrizao das funes a serem passadas via poibteiros de funo. Em geral, quando
precisamos desse recurso, utilizamos algum tipo de padronizao dos parmetros. Mesmo assim, vale a ressalva de que se
tome muito cuidado com isso.
Perceba uma coisa: o retorno da funo executada pelo ponteiro armazenado na varivel i t c Se o retorno para o
n .
ponteiro de funo fosse v i , no teramos necessidade de guardar retorno nenhum. Os retornos continuam sendo
od
resolvidos normalmente e warnings e erros so retornados por devolver retornos errados. Mas temos que tomar cuidado
tambm quanto a isso: imagine que voc declare a funo d v s o como retornando f o t Embora o compilador
iia
la.
retorne um warning relacionado passagem de tipo incompatvel, ainda assim ele ir gerar o cdigo binrio (voc
consegue evitar isso usando as opes de compilao pedntica do seu compilador consulte a documentao do
mesmo para maiores informaes).
De resto, o cdigo no possui muitos mistrios: uma vez que o switch escolha a funo a ser executada, baseada na
entrada do usurio, o ponteiro funcPointer recebe o endereo da mesma. Em seguida, feita a execuo da mesma,
como dissemos anteriormente, e o valor retornando para a varvel c Por fim, os comandos p i t ao final de m i
.
rnf
an
exibira os resultados e o endereo onde a funo alocada se encontra. Como brincadeira, para voc entender melhor,
mexa a seqncia nas quais as funes esto dispostas no cdigo fonte. Essa seqncia meio que determina qual funo
ficar primeiro no cdigo binrio. Assim voc poder perceber que o cdigo manipulado no importa onde.
Bem, brinque um pouco com esse programa para entender ponteriros de funo.

Passagem de ponteiros (Passagem por referncia e por valor):


At agora, todas as nossas funes tem passado seus parmetros por valor, ou seja, passamos uma cpia da informao
armazenada na varivel que passamos. Isso permite um certo isolamento entre a funo chamada e a que chama, o que
garante uma segurana quanto aos contedos da varivel local.
Porm, em muitas situaes em C, precisamos manipular diretamente a informao das variveis em uma funo. Um
exemplo de funo que faz isso a funo da biblioteca STDIO s a f Ela precisa saber onde est a varivel cujo
cn.
contedo ser alterado para fazer a leitura da informao via teclado (utilizando-se do sistema operacional) e gravando o
contedo no mesmo local apontado pela varavel em questo. Isso chamado tecnicamente de Passagem de
parmetros por referncia e com isso o contedo no endereo apontado pela varivel pode ser manipulado.
O C oferece, atravs dos ponteiros, formas de passarmos parmetros por referncia. Veremos isso no nosso prximo
programa, que uma brincadeira similar ao programa de TV Roda-a-Roda (antigo Roletrando):

#nld <ti.>
icue sdoh
#nld <tlbh
icue sdi.>
#nld <ieh
icue tm.>
#nld <tigh
icue srn.>
#eieNMTNAIA 6
dfn U_ETTVS
#eieTMNOPLVA 2
dfn AAH_AARS 0

int checaLetra (const char* palavraEscolhida, const char* letrasCertas, char letra);
void explicarRegras (void);
int contaFaltantes (const char* letrasCertas);
int testaCerto (const char* palavraEscolhida, int* tentativasErradas);
int main (void)
{
// Declarando variaveis
// Biblioteca de palavras
char palavras[][TAMANHO_PALAVRAS]={rotina, retina, palhaco, lembranca,
sistema, musica,curioso, fantasia,
malabares,sonhador,atitude,pacoca};
// Ponteiros necessarios
char *palavraEscolhida, *letrasCertas;
// Algumas variaveis de apoio
int tentativasErradas=0,i;
explicarRegras();
srand(time(NULL)); // Serve para modificar a tabela de nmeros pseudo-aleatrios
palavraEscolhida=palavras[rand()%((sizeof(palavras)/TAMANHO_PALAVRAS)-1)];
letrasCertas=malloc(strlen(palavraEscolhida)+1);
if (!letrasCertas)
{
printf(Nao consegui alocar memoria!\n);
return(1);
}
memset(letrasCertas,, strlen(palavraEscolhida)+1);
memset(letrasCertas,-, strlen(palavraEscolhida));
int tentativaAtual=0;
while(tentativasErradas
{
char minhaLetra;
tentativaAtual++;
printf (Okay, tentativa no %d (%d tentativas erradas):, tentativaAtual, tentativasErradas);
scanf (%c, &minhaLetra);
getchar();
if (!checaLetra(palavraEscolhida,letrasCertas,minhaLetra))
{

printf(Que pena, essa letra nao aparece!\n);


tentativasErradas++;
continue;
}
printf(A palavra ate agora e: %s\n,letrasCertas);
if (contaFaltantes(letrasCertas)<=(strlen(palavraEscolhida)/2))
{
if((contaFaltantes(letrasCertas)==0)||testaCerto(palavraEscolhida,&tentativasErradas))
{
printf(Muito bem! Voc acertou!\n);
return(0);
}
}
}
}
void explicarRegras (void)
{
// Explicando as regras do jogo para o jogador
printf (Okay, vamos explicar as regras!\n);
printf (Eu conheco algumas palavras, e vou escolher aleatoriamente uma delas.\n);
printf (Voce vai me dizer qual letra voce acha que essa palavra tem!\n);
printf (Se voce errar, vou considerar uma tentativa errada!\n);
printf (Se voce acertar, vou te mostrar onde as letras estao!\n);
printf (Quando voce tiver acertado metade das palavras, vou te dar );
printf (a chance de dizer qual palavra e essa. \n);
printf (Se voce errar, vou considerar uma tentativa errada!\n);
printf (Se voce acertar antes de acabar as tentativas, voce vence!\n\n\n);
printf (Algumas observacoes:\n);
printf (Voce tem %d tentativas que voce pode errar.\n,NUM_TENTATIVAS);
printf (Nenhuma palavra possui acentos, cedilha ou trema.\n);
printf (Nao estou diferenciando maisculas e minusculas. \n\n);
}
int checaLetra (const char* palavra, const char* letrasCertas, char letra)
{
// Acertos obtidos (0 indica que nao tem a letra em questao na palavra)
int acertos=0;
// Ponteiros a serem inicializados
char *palavraAnalisada, *certas;
// Inicialidando os ponteiros com os valores recebidos
palavraAnalisada=palavra;
certas=letrasCertas;

char letraComparada=((letra>=A)&&(letra<=Z))?letra-A+a:letra;
while (*palavraAnalisada)
{
if (*palavraAnalisada==letraComparada)
{
acertos++;
*certas=letraComparada;
}
palavraAnalisada++;
certas++;
}
return acertos;
}
int contaFaltantes(const char* letrasCertas)
{
char *letras=letrasCertas;
int faltantes=0;
while (*letras) if (*(letras++)==-) faltantes++;
return faltantes;
}
int testaCerto (const char* palavraEscolhida, int *tentativasErradas)
{
char *minhaResposta;
minhaResposta=(char*)malloc(strlen(palavraEscolhida)+1);
memset(minhaResposta,, strlen(palavraEscolhida)+1);
if (!minhaResposta)
{
printf(Nao consegui alocar memoria!\n);
return(1);
}
char *conversao=minhaResposta;
printf(Qual palavra voce acha que e essa? );
fgets(minhaResposta,strlen(palavraEscolhida)+1,stdin);
// Convertendo para minusculas e eliminando os caracteres de nova linha \n
while (*conversao)
{
*conversao=((*conversao>=A)&&(*conversao<=Z))?*conversao-A+a:*conversao;
conversao++;

}
minhaResposta[strlen(minhaResposta)]=;
if(strncmp(minhaResposta,palavraEscolhida,strlen(palavraEscolhida))==0)
return(1);
else
{
printf(Que pena, voce errou!\n);
(*tentativasErradas)++;
return(0);
}
}

Existe pouca coisa nova aqui, exceo do uso que faremos de passagem de ponteiros a funes e suas idiossincrsias.
Inicialmente fazemos algumas incluses e criamos duas constantes: NUM_TENTATIVAS e TAMANHO_PALAVRAS. Ambas
so usadas para evitarmos nmeros mgicos. Em especial, ns estamos incluindo a biblioteca padro s r n . , que
tigh
define funes especiais para strings, alm de ter a funo m l o ( , que precisaremos para alocar dinamicamente
alc)
algum espao de memria. Tambm declaramos quatro funes por meio de seus prottipos:

it ceaer (os ca* plvasohd, cnt ca* ltaCra, ca


n
hcLta cnt hr
aarEclia
os
hr
ersets
hr
lta;
er)
vi epiaRga (od;
od xlcrers vi)
itcnaatne (os ca*ltaCra)
n otFlats cnt hr ersets;
ittsaet (os ca*plvasohd,it tnaiaErds;
n etCro cnt hr aarEclia n* ettvsraa)

As funes c e a e r , c n a a t n e e t s a e t so as funes que nos interessam. e p i a R g a no


hcLta otFlats
etCro
xlcrers
faz nada, exceto exibir algum texto informativo explicando o funcionamento do nosso jogo. No vou me ater a explicar o
jogo, pois o jogo mesmo ir se explicar.
No main, comeamos declarando todas as variveis teis:

char palavras[][TAMANHO_PALAVRAS]={rotina, retina, palhaco, lembranca,


sistema, musica,curioso, fantasia,
malabares,sonhador,atitude,pacoca};

// Ponteiros necessarios
char *palavraEscolhida, *letrasCertas;

// Algumas variaveis de apoio

int tentativasErradas=0,i;

Declaramos uma matriz bidimencional de caracteres (ou, para ser mais exato, uma matriz de strings), onde uma das
dimenses est em branco e a outra tem como tamanho TAMANHO_PALAVRAS. Isso permitido para no mximo
uma dimenso: se uma dimenso estiver em branco, o C ir alocar o espao suficiente para conter os elementos nela,
mas APENAS se a inicializao acontecer no momento da declarao, como no nosso cdigo. Em seguida, declaramos
dois ponteiros de caracteres, palavraEscolhida e letrasCertas, e algumas variveis de apoio.
Em seguida, o sistema chama e p i a R g a para explicar como funciona nosso jogo. Em seguida, usamos a funo
xlcrers
s a d t m ( U L )para gerarmos uma nova raiz de nmeros pseudo-aleatrios (j vimos isso anteriormente, lembra?)
rn(ieNL)
e, usando

plvasohd=aarsrn(%(iefplva)TMNOPLVA)1]
aarEcliaplva[ad)(szo(aars/AAH_AARS-);

escolhemos uma das palavras.


Aqui tem uma pegadinha de programao: se voc se lembra bem, mencionamos anteriormente que para o C, matrizes
e ponteiros so estruturas similares. Isso porque em C uma matriz nada mais que uma quantidade de memria alocada
pelo sistema e declarada em ponteiro. Isso vale tambm para matrizes quaisquer dimenso. Podemos, desse modo,
utilizar uma tcnica para apontar diretamente a matriz de maneira correta (na realidade, existe um pequeno bug aqui,
que vou deixar como uma brincadeira para voc, leitor, identificar. A pista : tem a ver com o tamanho. Voc vai
entender a seguir). No caso, o que fazemos fazer com que o sistema escolha uma posio no comeo de uma palavra.
Para isso, existe alguma matemtica com a funo r n , usando o tamanho da matriz p l v a e
ad
aars

T M N O P L V A de modo que r n retorne um valor entre 0 e o nmero de palavras-1, devolvendo o endereo do


AAH_AARS
ad
incio dessa palavra para p l v a s o h d . Voc pode se perguntar e porque no copiar a palavra escolhida. Isso
aarEclia
possvel, mas mais complexo e consome memria e tempo de processamento. Novamente: no pense em
programao apenas para PCs, com seus gigas de memria e processamento. Pense, por exemplo, em um celular com
alguns KB de espao til e poucos megahertz de processamento. Aproveitar a palavra que J EST NA MEMRIA uma
idia melhor. Ns veremos como usar essa informao de maneira segura j j.
Em seguida usamos m l o para alocar um espao de tamanho igual ao tamanho da palavra escolhida. Fazemos uma
alc
checagem para ver se a alocao foi bem sucedida (NUNCA SE ESQUEA DISSO!) e usamos a funo memset para
preencher a memria alocada com terminadores nulos e, em seguida, com hifens. Essa segunda varivel ir armazenar
aonde voc j acertou na escolha das palavras.
Entramos ento no loop principal. Declaramos um caracter e adicionamos um a cada tentativa. O caracter s declarado,
sem ser inicializado. Aqui podemos quebrar a boa prtica de C de inicializar uma varivel assim que declar-la, pois aqui
no iremos fazer mais nada que ler um caracter. No h problema imediato nesse caso, pois uma leitura rpida e o
endereo usado ser sempre o mesmo (no estamos alocando dinamicamente esse caracter). O jogador recebe uma
informao sobre o nmero de tentativas que ele fez e quantas ele j errou (o lao compara o valor de
t n a i a E r d scom o de N M T N A I A ). Foi colocado um g t h r )para limpar qualquer caracter esprio
ettvsraa
U_ETTVS
eca(
que tenha sobrado (em especial o ENTER).
E agora entramos em nossa primeira checagem: o jogador escolheu uma letra certa?
Para isso, temos a funo:

it ceaer (os ca* plvasohd, cnt ca* ltaCra, ca


n
hcLta cnt hr
aarEclia
os
hr
ersets
hr
lta;
er)

Que recebe dois ponteiros e a letra a ser comparada.


Aqui existe um detalhe: os dois ponteiros so declarados como const.
Por que? No queremos manipular o contedo deles?, voc deve estar se perguntando.
Aqui importante clarificar uma coisa: quando voc manda um ponteiro para uma funo, ela tem, digamos assim, poder
total sobre ele. Isso inclui trocar o endereo de memria para o qual o ponteiro aponta. Se voc lembrar de quando
usamos ponteiros para executar a funo de Fibonacci, utilizamos dois ponteiros apontando para o mesmo lugar, sendo
que um deles que se deslocaria atravs do local alocado para armazenarmos os valores.
Para garantirmos que um ponteiro que estamos passando no vai ter seu endereo alterado, usamos a palavra-chave
const antes do c a * para indicarmos ao sistema que esse ponteiro constante (ou seja, ir apontar SEMPRE para o
hr,
mesmo lugar enquanto em uso), o que oferecer uma garantia de que, mesmo que o CONTEDO do endereo
apontado seja alterado, o ENDEREO no o seja. Por segurana, a no ser que voc precise manipular os endereos
apontados, sempre coloque const quando usar ponteiros em parmetros de funo.
Indo para a funo c e a e r , encontramos o seguinte cdigo:
hcLta

int checaLetra (const char* palavra, const char* letrasCertas, char letra)
{
// Acertos obtidos (0 indica que nao tem a letra em questao na palavra)
int acertos=0;

// Ponteiros a serem inicializados


char *palavraAnalisada, *certas;

// Inicialidando os ponteiros com os valores recebidos


palavraAnalisada=palavra;
certas=letrasCertas;

char letraComparada=((letra>=A)&&(letra<=Z))?letra-A+a:letra;

while (*palavraAnalisada)
{
if (*palavraAnalisada==letraComparada)
{
acertos++;
*certas=letraComparada;
}

palavraAnalisada++;
certas++;
}

return acertos;
}

Vamos analisar essa funo. Para comear, temos a declarao de trs variveis: um i tonde guardaremos a quantidade
n
de vezes que a letra escolhida casou com alguma na palavra (0 se nenhuma), dois ponteiros de caracter (c a * que
hr)
sero quem realmente far o trabalho sujo, e um c a que ir conter uma verso em minscula do caracter digitado
hr
(isso deve-se ao fato de todas as palavras estarem em minsculas, o que provocaria problemas na comparao entre os
caracteres em caso de maisculas). A funo no tem muito mistrio: iremos correr os ponteiros de p l v a n l s d
aarAaiaa
(que recebe a palavra que o sistema escolheu) e c r a (onde sero gravados os acertos). Se o caracter que for
ets
apontado por p l v a n l s d casar com a letra a ser comparadas, ser somado um ao nmero de acertos e a
aarAaiaa
letra ser gravada na posio apontada em c r a . Como ambas se movem ao mesmo tempo (no fim do loop de
ets
comparao), a posio de c r a onde a letra ser gravada corresponder posio em que a letra fica em
ets
p l v a n l s d . Parece estranho no w i e no haver nenhuma condio de sada aparente, mas lembre-se que
aarAaiaa
hl
para C, qualquer valor 0 ou equivalente considerado falso e provoca a sada do loop. No caso de strings, o terminador
nulo da string considerado um valor equivalente a 0 e portanto falso. Por isso, no precisamos especificar uma condio
de sada. Esse tipo de construo comum em programas C de alto nvel e faz parte do modo C de pensar no
declarar uma condio de sada explcita aqui. Aps o loop, a funo retornar quantos acertos ocorreram.
Vejamos agora uma coisa: necessrio criar os dois ponteiros de char?
SIM!
Usando const, garantimos que os endereos armazenados nos ponteiros que passaram os mesmos para o sistema no
tero seus valores alterados. Porm, pela prpria lgica que usamos, precisaremos de ponteiros que se movam atravs
das strings, comparando os caracteres um a um e obtendo os acertos. No teramos como fazer isso usando o ponteiro
const que passamos, pois ele constante e portanto no pode ser manipulado. Porm, ao declararmos dois ponteiros e
copiarmos os endereos de memria apropriados para eles, podemos deslocar esses segundo ponteiros, sem interferir
nos ponteiros originais (constantes). Lembre-se que const nesse caso apenas impede que o endereo do ponteiro seja
trocado, no seu contedo. Por meio dos ponteiros que declaramos dentro da funo, podemos modificar os contedos
das strings apontadas pelos mesmos e deslocar-nos atravs dos locais apontados, operando por meio de aritmtica de
ponteiros.
OK, acho que devemos estar claros quanto a isso. Voltemos ao nosso programa.
Lembra que dissemos que a funo c e a e r retorna o nmero de acertos e 0 se nenhum? Isso tem um motivo:
hcLta

i (ceaer(aarEclialtaCra,ihLta)
f !hcLtaplvasohd,ersetsmnaer)

Esse i explica nossa escolha. Assim como no caso do w i eem c e a e r , o i aqui uma boa prtica de C: ao
f
hl
hcLta
f
invs de retornarmos um nmero arbitrrio como falso, usamos o prprio mecanismo de booleanos em C (0 ou

equivalente igual a falso, qualquer outro valor verdadeiro). Desse modo, o programa fica mais enxuto e rpido por
dispensar comparaes que seriam desnecessrias. Mas ateno: BOAS PRTICAS NO SO OBRIGAES! Se em
algum cdigo voc precisar fazer comparaes exijam o valor 0 como um valor verdadeiro, no se exime de usar o 0
como verdadeiro e comparar valores com operadores relacionais normalmente. O fato de voc se ater a boas prticas
ajuda na legibilidade e manuteno do cdigo, mas no deve ser um grilho para voc.
Esse i ir apenas exibir uma mensagem de que o jogador errou e adicionar um ao nmero de tentativas, e iniciar uma
f
nova interao do w i epelo comando c n i u .
hl
otne
Caso o jogador tenha acertado a letra, ele ver a palavra com as letras que ele j acertou e com traos nas posies
onde ainda tm letras que o jogador ainda no acertou. Em seguida chamamos outra funo que usa parmetros por
referncia, que a c n a a t n e :
otFlats

itcnaatne(os ca*ltaCra)
n otFlatscnt hr ersets
{
ca *ersltaCra;
hr lta=ersets
itflats0
n atne=;
wie(lta)i ((ers+= flats+
hl *ers f *lta+)=-) atne+;
rtr flats
eun atne;
}

Basicamente ela conta quantos traos ainda existem em l t a C r a (ou seja, quantas letras ainda precisam ser
ersets
descobertas). Novamente declaramos um i t para armazenar o total de faltantes (com 0 caso no haja nenhuma
n
faltante) e um char* para ser usado para nos deslocarmos atravs da string e contarmos os caracteres faltantes. O
w i edessa funo estranho:
hl

wie(lta)i ((ers+= flats+


hl *ers f *lta+)=-) atne+;

O que queremos dizer aqui que: enquanto * e r sno apontar um terminador nulo, se o valor apontado por letras
lta
no momento do i for um trao, adicione um aos faltantes. Aps a comparao, mova uma posio adiante o ponteiro
f
letras (perceba a ps-fixao do ponteiro). Esse outro tipo de construo caracterstica que o C permite e muitas
vezes ela vista em programas profissionais C.
Depois de contadas as faltantes, comparamos esse total com a metade das letras da palavra escolhida. Se isso for
verdade, uma nova contagem feita para ver se todas as letras foram acertadas:

i(cnaatne(ersets=0|tsaet(aarEclia&ettvsraa)
f(otFlatsltaCra)=)|etCroplvasohd,tnaiaErds)

Caso contrrio, ser chamada a funo testaCerto. No caso, o C garante que, caso c n a a t n e retorne 0,
otFlats
t s a e t no ser chamada ( a short-fuse logic lgica de curto-circuito, que faz com que, uma vez uma
etCro
comparao lgica seja irrefutavelmente verdadeira ou falsa, o resultado seja considerado sem necessidade de realizar os
demais

processamentos,

poupando

tempo).

Perceba

que

aqui

usamos

comparao

c n a a t n e ( e r s e t s = 0 Embora aqui pudessemos usar ! o t F l a t s l t a C r a ) aqui a boa


otFlatsltaCra)=.
cnaatne(ersets,
prtica no contribuiria, e sim atrapalharia a legibilidade. importante ter isso em mente ao escrever seus cdigos.
Vejamos agora a funo t s a e t , que chamada no caso de ainda haver letras a serem descobertas em uma
etCro
palavra com mais de metade de seus caracteres descobertos:

int testaCerto (const char* palavraEscolhida, int *tentativasErradas)


{
char *minhaResposta;

minhaResposta=(char*)malloc(strlen(palavraEscolhida)+1);
memset(minhaResposta,, strlen(palavraEscolhida)+1);

if (!minhaResposta)
{
printf(Nao consegui alocar memoria!\n);
return(1);
}

char *conversao=minhaResposta;
printf(Qual palavra voce acha que e essa? );
fgets(minhaResposta,strlen(palavraEscolhida)+1,stdin);

// Convertendo para minusculas e eliminando os caracteres de nova linha \n


while (*conversao)
{
*conversao=((*conversao>=A)&&(*conversao<=Z))?*conversao-A+a:*conversao;
conversao++;
}

minhaResposta[strlen(minhaResposta)]=;

if(strncmp(minhaResposta,palavraEscolhida,strlen(palavraEscolhida))==0)
return(1);
else
{

printf(Que pena, voce errou!\n);


(*tentativasErradas)++;
return(0);
}
}

Quando chamamos t s a e t , passamos a palavra em um ponteiro c n t c a *e um i t .


etCro
os hr
n*
Por que esse i tno c n t
n
o s ?.
O i taqui no c n tpois, vamos dizer assim, o original no era um ponteiro. A validade do que dissemos sobre a
n
os
alterao potencial do endereo s vale para quando passamos ponteiro-para-ponteiro. Quando derreferenciamos uma
varivel comum e passamos sua referncia, mesmo que haja uma alterao no endereo dentro do cdigo da funo,
no haver outros impactos na varivel de retorno (alm de possveis crashes de sistema e, na melhor das hipteses,
erros de lgica). Colocar um c n tcomo precauo a mais errado? No. Mas como o cdigo aqui no vai alterar essa
os
posio (apenas o contedo do endereo), no h tanta necessidade assim de colocar c n tnesse momento.
os
imporante aqui dizer que o retorno 1 para o caso de voc acertar a palavra e 0 se errar. A passagem por referncia
de t n a i a E r d s importante pois precisaremos, no caso de erro aqui, adicionar 1 ao nmero de tentativas
ettvsraa
erradas (no haveria como fazer uma segunda chamada, uma vez que a entrada do usurio feita nessa funo, e
armazenar o valor de retorno dessa funo antes da comparao no i que a chama no programa principal, ainda que
f
possvel, complexo e propenso a erros). Inicialmente alocamos o mesmo tanto de memria que usamos na
p l v a s o h d para o usurio entrar sua tentativa usando m l o e colocamos um segundo ponteiro chamado
aarEclia
alc
c n e s opara esse endereo. Usamos f e spara aceitar a resposta dada pelo usurio e colocamos o terminador nulo
ovra
gt
ao fim da entrada do usurio. Usando o ponteiro c n e s o convertemos a palavra para minscula (uma converso que
ovra,
fazemos usando o fato de que variveis c a podem ser entendidas como i te podemos subtrair um c a de outro
hr
n
hr
mais exatamente, o valor ASCII de um do valor ASCII de outro e som-los com um terceiro c a ).
hr
Em seguida, usamos a funo s r c p da biblioteca s r n . , para comparar a resposta dada com a
tnm
tigh
p l v a s o h d . Essa funo compara um determinado trecho de uma string com o trecho de mesmo tamanho da
aarEclia
outra string. Para evitar que o jogador use um cheat e digite apenas a parte que ele sabe da palavra, determinamos que
o tamanho a ser usado o de p l v a s o h d , no da tentativa do jogador. Isso porque s r c pdevolve 0 se os
aarEclia
tnm
dois pedaos forem exatamente iguais, -1 se o primeiro for alfabeticamente anterior ao segundo e 1 se o primeiro for
alfabeticamente posterior ao segundo (e aqui voc deve ter entendido porque disse que o jogador poderia cheatar
aqui). Se o resultado for 0, ou seja, as duas palavras forem iguais, a funo retorna 1, o que vai fazer o i no cdigo
f
principal ser verdadeiro. Caso contrrio, retornar 0, tornando o i falso (lembrando que pelo short-fuse logic, a primeira
f
funo deve ser falsa seno essa nem seria executadas). Antes de devolver o 0 no caso de erro, porm, ele mostra
uma mensagem de erro e soma um ao nmero de t n a i a E r d s (atente que o + fora do parnteses em
ettvsraa
+
(*tentativasErradas)++ indica que o valor a ser alterado o que est armazenado no endereo apontado por
*tentativasErradas, no o prprio *tentativasErradas.).
Se voc notar, caso o i seja falso, ele acaba no final do loop. Uma nova iterao ser aberta obviamente, se o nmero
f
de t n a i a E r d s ainda for menor que o N M T N A I A . Caso contrrio, o programa sair do loop e se
ettvsraa
U_ETTVS
encerrar.
Algumas brincadeiras:
1. Descubra o bug na instruo que escolhe as palavras;

2. Aumente o nmero de palavras e veja se isso altera alguma coisa no funcionamento do cdigo e se ele no chega
a escolher alguma das palavras novas ou antigas. Coloque palavras grandes e veja o que acontece. Modifique o
cdigo para corrigir tal circunstncia;
3. Uma coisa que no foi considerada propositalmente nesse cdigo: no jogo normal, voc no pode pedir uma letra
j pedida. Alm disso, a qualquer momento voc pode pedir uma lista das letras j usadas. Crie uma lgica que
trate dessas condies;
4. Em algumas verses desse jogo, uma tentativa errada de informar a palavra completa acaba com o jogo de uma
vez. Tente criar uma lgica assim;
5. Em algumas verses, existe uma regra que, caso um determinado nmero de tentativas erradas consecutivas seja
alcanada, o jogo acaba. Tente adaptar o jogo para essa regra;
Bem, com esse tpico, encerramos (ao menos por agora) o tpico Matrizes e Ponteiros. Ainda existe um tema dentro
dela que falta, que o de ponteiros de ponteiros, mas veremos ele no futuro, quando tratarmos de estruturas de dados
complexas em C. Por agora s e tenho s que agradecer por vocs terem aguentado tanto.
Saiba mais

Bsico, Funes, Ponteiros

Matrizes e Ponteiros Parte 2 Alocao dinmica de memria


Ol todos e desculpem a demora! Espero que todos tenham pulado o Carnaval no Unidos da Programao em C!
Bem, vamos continuar de onde paramos, ento vamos dar prosseguimento ao curso.
Quando, na primeira parte do assunto matrizes e ponteiros, falamos sobre os ponteiros, ns dissemos que existem
situaes onde precisamos utilizar uma posio de memria que no conhecemos previamente. Na realidade, isso mais
comum do que se imagina: no fosse assim, todo programa deveria ser artificialmente limitado em suas capacidades
baseado em nmeros arbitrrios de informao a ser processada. Por exemplo: um programa teria que criar previamente
500 variveis de notas para processar o boletim escolar de uma classe de 20 alunos, sendo intil para uma classe de 501
alunos. Isso tambm aumentaria o custo de desenvolvimento e manuteno de um programa de computador, alm de
utilizar os recursos de um computador de maneira pouco eficiente. Isso acontece porque pelas regras do padro ANSI C
Original (seguida pela maior parte dos compiladores), o compilador no pode gerar cdigo que utilize-se de matrizes de
tamanho varavel (em uma verso atualizada do padro, C-99, os compiladores j passaram a dar suporte a esse recurso,
porm nos focaremos ao C padro ANSI.). Por exemplo, em C padro ANSI, o cdigo abaixo:

pit (Qatseeetspeiao?)
rnf uno lmno rcsms;
saf(%&Eeets;
cn d,nlmno)
iteeetsnlmno]
n lmno[Eeets;

considerado errado e gera um erro similar (PS: se voc colocar esse tipo de cdigo em um compilador com suporte ao
C-99, em geral ele ir compilar, mas ir dar alertas. Ser necessrio fazer o compilador enxergar o cdigo como C-99, no
ANSI C Original. verifique o manual do compilador para maiores informaes).
Para escaparmos a esse tipo de problema, a soluo fugir das matrizes e recorrermos aos ponteiros. Mas como?

O C Original prev funes de alocao de memria dinmica. Com o uso dessas funes, podemos obter, em tempo de
execuo, qualquer quantidade de memria que precisarmos (claro, imaginando que ela esteja disponvel), normalmente
de um espao conhecido como heap e gerenciado pelo sistema operacional. Com isso, no precisamos artificalmente
definirmos o uso de memria em um programa C (PS: vrias outras linguagens, como Pascal, possuem tambm
mecanismos de alocao dinmica de memria), podendo aproveitar os recursos do sistema com maior eficincia.
Bem, chega de papo! Vamos ao que interessa.

O cdigo
Vamos refazer um programa similar a algo que fizemos anteriormente, que o programa da seqncia de Fibonacci (fs
de Cdigo Da Vinci, regozijem-se). Porque disse similar? Pois a seqncia de Fibonacci calculada de maneira muitssimo
parecida com a do Fatorial, que vimos quando falamos de recursividade. Para no recorrermos recurso e mostrarmos a
seqncia de Fibonacci resultante, utilizaremos alocao dinmica de memria para reservamos o espao adequado e
aritmtica de ponteiros para realizar os clculos. Abaixo est o cdigo:

#nld <ti.>
icue sdoh
#nld <dlbh
icue sti.>

itmi(od
n anvi)
{
usge ln iti0nmrFb0*euniFb*pri;
nind og n =,ueoi=,sqecai,oeFb
d
o
{
pit(Idqeotmnod sqecaFbnci( pri d 3:;
rnfniu
aah a euni ioac a atr o ) )
safd,nmrFb;
cn(%&ueoi)
}hl (ueoi<)
wie nmrFb3;
sqecai=usge ln it*mlo(iefusge ln it*ueoi)
euniFb(nind og n )alcszo(nind og n)nmrFb;
i(sqecai)
f!euniFb
{
pit(Smmmra\;
rnfe eoi!n)
rtr()
eun1;
}

oeFbsqecai;
pri=euniFb
*pri=;
oeFb1
+oeFb
+pri;
*pri=;
oeFb1
+oeFb
+pri;
fr(=;<nmrFb2i+
o i0i<ueoi-;+)

{
*pri=(pri-)*oeFb2;
oeFb*oeFb1+(pri-)
oeFb+
pri+;
}
oeFbsqecai;
pri=euniFb
fr(=;nmrFbi+
o i0<ueoi;+)
{
pit(%o nmr Fbnci e % e et amznd n edrc
rnfd. ueo ioac
u
sa raeao o neeo
%\i1*pri,pri)
pn,+,oeFboeFb;
+oeFb
+pri;
}

fe(euniFb;
resqecai)
rtr()
eun0;
}

Bem, no precisamos comentar o incio, exceo que precisamos incluir a s d i . (STanDard LIBrary, se voc no se
tlbh
lembra bem), pois ela quem fornece a funo m l o (Memory ALLOCation) que precisaremos. Logo falaremos sobre
alc
ela. Em seguida declaramos quatro variveis u s g e l n i t Na realidade, duas u s g e l n i t e dois
nind og n
nind og n
ponteiros u s g e l n i t (u s g e l n i t * Fizemos a opo de usar o u s g e l n i t pois
nind og n
nind og n
).
nind og n
podemos com isso executar um nmero muito alto de interaes e, ao mesmo tempo, no precisamos nos preocupar
com valores negativos de Fibonacci (que no existem). Para no termos problemas, fazemos com que o sistema s
aceite um valor mnimo de 3 (ou seja, para mostrar at o terceiro nmero de Fibonacci).

Alocao dinmica de memria os comandos m l o e f e :


alc re
Continuando o programa, encontramos o comando:

sqecai=usge ln it*mlo(iefusge ln it*ueoi)


euniFb(nind og n )alcszo(nind og n)nmrFb;

Essa linha que faz todo o truque de alocao dinmica de memria com o uso do comando m l o (Memory
alc
ALLOCation). m l o aloca uma determinada quantidade de bytes para um ponteiro do tipo v i (v i *
alc
o d o d ).
importante lembrar disso pois podemos provocar srios erros seno lembrarmos constantemente disso. Para que
possamos usar corretamente m l o , devemos obter o nmero de bytes necessrios para armazenarmos as informaes
alc
necessrias. Nesse caso, utilizamos o operador s z o para obter o tamanho de bytes de um u s g e l n i t
ief
nind og n
(lembrando que esse valor pode mudar conforme a plataforma) e multiplicamos pelo nmero de elementos que
precisaremos (no caso, o tamanho da seqncia de Fibonacci que desejamos desenvolver). Depois disso, devemos dar
um typecast para indicar ao sistema que vamos transformar o ponteiro v i retornado por m l o em um ponteiro
od
alc

u s g e l n i t(u s g e l n i t * Desse modo, temos como sada um ponteiro do tipo u s g e l n


nind og n
n i n d o g n ).
nind og
i t pronto para nosso uso.
n
Antes de usarmos o ponteiro, interessante de, por via das dvidas, ver se realmente temos a memria alocada. Isso
pode no parecer vlido agora, mas qualquer pequena falha ao lidar com alocao dinmica de memria pode
comprometer o uso do sistema. O comando m l o nos oferece um bom mecanismo para isso: ele retorna n l
alc
ul
quando a alocao de memria falhou. Portanto, podemos testar isso com o seguinte cdigo:

i(sqecai)
f!euniFb
{
pit(Smmmra\;
rnfe eoi!n)
rtr()
eun1;
}

Provocando uma sada do sistema caso a memria que precisemos no seja alocada. Isso nos garante que no vamos
operar sobrescrevendo memria apontada por um ponteiro desgarrado (ponteiro no-inicializado corretamente).
Agora, e essa uma regra importante: sempre que voc alocar memria, desaloquea-a assim que no for mais
necessria. Isso vai garantir que no s seu programa, mas qualquer programa no computador onde o seu estiver
executando, ir encontrar memria sempre que precisar. Caso no desaloque memria, voc pode entrar em uma
situao conhecida como memory leakage (Vazamento de memria), onde o sistema ir dar falha aps vrias alocaes
de memria seguirem-se sem a devida desalocao (o C no possui os sistemas de coletor de lixo Garbage Collector
existente em outras linguagens devido sua filosofia de trabalhar com o mnimo possvel de complexidade). Para
desalocar-se memria usado o comando f e .No nosso caso, o ltimo comando do programa :
re

fe(euniFb;
resqecai)

Que avisa ao sistema operacional que desejamos desalocar o espao de memria que alocamos para s q e c a i ,
euniFb
devolvendo esses recursos ao sistema operacional.

Aritmtica de ponteiros:
Bem, agora vamos continuar analisando o cdigo, vendo um tpico que causa muiita confuso e portanto deve ser
clareado totalmente, que a aritmtica de ponteiros.
Ns j vimos na aula anterior que existem formas de fazer um ponteiro correr pela memria vendo seus contedos,
somando e subtraindo elementos do ponteiro em questo. Para isso, usa-se a aritmtica de ponteiros, que nada mais
que usarmos os operandos matemticos mais simples (+ - + e para que eles alterem a informao de que endereo
, , + )
deve ser apontado por um ponteiro.
Vamos analisar ento como o programa ir usar a aritmtica de ponteiros:

oeFbsqecai;
pri=euniFb
*pri=;
oeFb1
+oeFb
+pri;
*pri=;
oeFb1
+oeFb
+pri;
fr(=;<nmrFb2i+
o i0i<ueoi-;+)
{
*pri=(pri-)*oeFb2;
oeFb*oeFb1+(pri-)
oeFb+
pri+;
}

O primeiro comando simples: salvamos em o e F b o endereo apontado por s q e i F b Perceba que ambos
pri
eucai.
esto sem o operador de derreferenciamento (sim, o nome confuso) * Nesse caso, o que queremos: aqui estamos
.
atribundo o endereo apontado por s q e c a i como o endereo a ser apontador o e F b
euniFb
pri.
A pergunta que voc deve estar se fazendo : por que usar dois ponteiros?
Na realidade aqui importante fazer uma considerao MUITO SRIA: o C NO POSSUI LEMBRANA do endereo
apontado por um ponteiro. Ele trabalha sempre no instante, por assim dizer. Se voc modificar o endereo de
s q e c a i , ele ser usado em tudo o mais por esse novo valor, INCLUSIVE NA DESALOCAO. Isso pode gerar
euniFb
problemas srios, pois o sistema operacional, no momento de alocar memria, tambm armazena a informao de quanta
memria foi alocada, mas no a posio inicial-final. Se voc mover o ponteiro e mandar desalocar a memria, voc
pode muito bem tentar desalocar memria de programas que no o seu, incluindo a programas do sistema operacional.
Desse modo, voc pode comprometer o funcionamento do sistema como um todo ao fazer esse tipo de desalocao.
Por via das dvidas, muito mais interessante fazer todas as operaes por um segundo ponteiro, esse sim livre para
ser usado vontade e modificado como necessrio. Voc pode, obviamente, fazer o movimento do ponteiro usando
sequenciaFib, se voc souber como recuar o ponteiro de volta para o incio de tudo, mas o nvel de complexidade que
isso vai gerar, em especial se voc precisar modificar a memria alocada ou amarr-la a outras coisas por meio de
ponteiros de ponteiros (veremos esse tpico no futuro) vai mostrar que usar um ponteiro ncora representa um gasto
de memria muito pequeno e o aumento de complexidade de cdigo no compensar.
Bem, voltando ao nosso cdigo: agora colocamos operFib como ponteiro para traabalharmos. Vamos ver o que ele ir
fazer:

*pri=;
oeFb1
+oeFb
+pri;
*pri=;
oeFb1
+oeFb
+pri;

A primeira coisa que fazemos : pegar os dois primeiros espaos de memria alocados para s q e c a i (e que vamos
euniFb
alterar usando o ponteiro o e F b e definir eles como 1. Eles so os primeiros dois nmeros de Fibonacci, segundo a
pri)

definio do mesmo. O que fazemos repetido ento pode-se pensar bem. A primeira linha: * p r i = , define que
oeFb1
queremos armazenar na posio de memria apontada por operFib o valor 1. Aqui estamos tratando a memria
apontada, portanto a derreferenciamos usando o operador * Em seguida, a segunda linha, + o e F b indica que
.
+pri,
queremos passar para o prximo elemento de o e F b
pri.
AQUI MORA DRAGES!!!
Pode parecer que simplesmente somamos 1 ao valor de o e F b como mostramos na outra aula sobre ponteiros. Mas
pri,
j falamos l que nesse caso, o C soma o equivalente ao nmero de bytes ocupados por 1 elemento do tipo apontado
pelo ponteiro (no caso, u s g e l n i t Normalmente, fazendo as coisas da maneira correta, nem precisamos nos
n i n d o g n ).
preocupar com isso, mas muito importante e explica, por exemplo, o porqu da exigncia do typecast do ponteiro
resultante do m l o de v i * para o tipo necessrio (alm da bvia mensagem de warning ou erro ao colocar-se um
alc
od
ponteiro v i *em um ponteiro u s g e l n i t * Com o devido typecast, o compilador, ao gerar o cdigo,
od
n i n d o g n ).
faz com que todas as abstraes sejam devidamente configuradas no nvel do cdigo de mquina. Sem isso, seria muito
fcil o programador provocar erros e bugs devido ao apontamento incorreto de posies.
Dito isso, no h muito mais o que falar nesse momento.
O lao for que se segue, porm, muito mais interessante:

fr(=;<nmrFb2i+
o i0i<ueoi-;+)
{
*pri=(pri-)*oeFb2;
oeFb*oeFb1+(pri-)
oeFb+
pri+;
}

Vamos analisar ele: a primeira coisa que a condio de sada o nmero de elementos de nossa seqncia de
Fibonacci-2. Fazemos isso pois os dois primeiros valores j foram estipulados.
Em seguida vem o comando que mais interessante nesse lao:

*pri=(pri-)*oeFb2;
oeFb*oeFb1+(pri-)

Aqui, usando a aritmtica de ponteiros, conseguimos declarar normalmente a funo para o clculo de um nmero de
Fibonacci: o valor da posio apontada por operFib ser a soma do valor na posio apontada pelo elemento anterior ao
atual (* o e F b 1 ) e pelo valor anterior a este (* o e F b 2 ).
(pri-)
(pri-)
Como ler uma dessas seqncia. A primeira coisa perceber que temos uma aritmtica de ponteiro a, que pedir para
retornar o endereo corresponte a o e F b 1 Nesse caso, lembre-se de que subtrado o nmero de bytes do
pri-.
tamanho do tipo indicado pelo ponteiro operFib. Por exemplo, imagine que o e F baponte no momento o endereo
pri
1000 (no caso, para facilitar a compreenso, trate como decimal normalmente os endereos so apresentados pelo C
em Hexadecimal) e que o tamanho de um u s g e l n i t seja 64 bits (ou seja, 8 bytes). Ao pedirmos para
nind og n
obtermos o elemento anterior (o e F b 1 ele ir nos apresentar o valor que est na posio de memria 1000-8, ou
p r i - ),
seja, na posio 992. Por sua vez, o elemento anterior a esse (o e F b 2 estar na posio de memria 1000-16, ou
pri-)

seja, 984.
Isso no afeta o e F b voc deve estar se perguntando?
p r i ?,
Na verdade no. Ao usarmos os operadores aritmticos na aritmtica de ponteiros, seus comportamentos so similares
aos dos mesmos na aritmtica convencional, ou seja, quando fazemos contas. Isso importante ressaltar pois os
comportamentos dos operando ++ e similar, assim como os efeitos de pr e ps-fixao que vimos quando falamos
sobre os operadores e lgica em C. Em especial, importante que voc tome muito cuidado com pr e ps-fixao na
aritmtica de ponteiros. Por exemplo, se eu usar: * m u o t i o + , ele me retornar o valor apontado por
(ePner+)
meuPonteiro no momento em que o comando executado e logo em seguida ir executar a soma de uma posio de
memria do tipo apontado pelo ponteiro. Porm, se eu usar * + m u o t i o , ele ir avanar uma posio de
(+ePner)
memria antes de efetuar a leitura da memria. Particularmente no uso esse tipo de construo pois pode gerar
confuso. Na dvida, no a use: prefira escrever um cdigo mais claro e limpo. Quando ganhar experincia poder
escrever cdigo mais avanado.
Bem, em seguida o program ir executar o comando o e F b + , que ir avanar uma posio de memria dentro dos
pri+;
valores apontados. Isso afetar a prxima interao, inclusive o clculo acima mostrado, onde a posio o e F b 1
prirepresentar a posio de o e F bantes dessa soma, ou seja, o valor calculado nessa iterao.
pri
Bem, isso mostra como iremos calcular a nossa seqncia de Fibonacci.
Em seguida temos o cdigo que ir exibir nosso:
oeFbsqecai;
pri=euniFb
fr(=;nmrFbi+
o i0<ueoi;+)
{
pit(%o
rnfd.

nmr
ueo

Fbnci
ioac

%
u

et
sa

amznd
raeao

n
o

edrc
neeo

%\i1*pri,pri)
pn,+,oeFboeFb;
+oeFb
+pri;
}
Primeira coisa que fazemos resetar o e F b sobrescrevendo o endereo dele com o de s q e c a i . Perceba
pri,
euniFb
que manipulamos o endereo, no o contedo do mesmo que continuar intacto.
Em seguida, um lao ir apresentar para ns o nmero Fibonacci recuperado de o e F b seu valor e o endereo onde
pri,
ele est armazenado, adicionando uma posio de memria a cada vez que a iterao f r for executada. A sada
o
apresentada ser algo como (para 12 nmeros de Fibonacci):
1.nmr Fbncie1eet amznd n edrc 09908
o ueo ioac
sa raeao o neeo xa10
2.nmr Fbncie1eet amznd n edrc 0990c
o ueo ioac
sa raeao o neeo xa10
3.nmr Fbncie2eet amznd n edrc 09900
o ueo ioac
sa raeao o neeo xa11
4.nmr Fbncie3eet amznd n edrc 09904
o ueo ioac
sa raeao o neeo xa11
5.nmr Fbncie5eet amznd n edrc 09908
o ueo ioac
sa raeao o neeo xa11
6.nmr Fbncie8eet amznd n edrc 0990c
o ueo ioac
sa raeao o neeo xa11
7.nmr Fbncie1 eet amznd n edrc 09900
o ueo ioac
3
sa raeao o neeo xa12
8.nmr Fbncie2 eet amznd n edrc 09904
o ueo ioac
1
sa raeao o neeo xa12
9.nmr Fbncie3 eet amznd n edrc 09908
o ueo ioac
4
sa raeao o neeo xa12
1o nmr Fbncie5 eet amznd n edrc 0990c
0. ueo ioac
5
sa raeao o neeo xa12
1o nmr Fbncie8 eet amznd n edrc 09900
1. ueo ioac
9
sa raeao o neeo xa13
1o nmr Fbncie14eet amznd n edrc 09904
2. ueo ioac
4
sa raeao o neeo xa13
Aqui, perceba o endereo marcado em vermelho (ele variar na sua mquina conforme as execues, e dificilmente ser

o mesmo que estou apresentando aqui). Note que ele vai subindo de 4 em 4 (cem Hexadecima representa o nmero
12). Isso deve-se ao fato de que na plataforma onde executei esse cdigo, o tamanho de um u s g e l n i t
nind og n
de 4 bytes (32 bits). Esse valor pode variar conforme a plataforma, mas o importante aqui que ele vai almentando de 4
em 4 bytes, ou seja, a cada posio do tamanho de um u s g e l n i t enfatizando o que dissemos
nind og n,
anteriormente sobre o tamanho do tipo de dado na questo da aritmtica de ponteiro. Se voc lembrar do cdigo e da
sada do programa que fizemos ao comearmos a explorar ponteiros, voc ver que l ele subia de 1 em 1 byte, o
tamanho de um c a . Se voc tivesse usando um tipo cujo tamanho fosse de 20 bytes, a aritmtica de ponteiro
hr
aumentaria o valor da posio de memria de 20 em 20 bytes (imaginando que eles fossem cotiguos, ou seja, em
seqncia, que uma obrigatoriedade para o m l o funcionar corretamente).
alc
Por fim, antes de terminar o programa, o mesmo libera a memria com o comando f e . Embora no seja exatamente
re
obrigatrio (a maioria dos sistemas operacionais modernos desalocam completamente qualquer memria utilizada por um
programa ao trmino de sua execuo), uma excelente prtica ao sair do programa desalocar qualquer ponteiro que
possa vir a estar alocado no momento do encerramento do programa. Na realidade, a melhor prtica desalocar
qualquer memria alocada dinmicamente to logo o programa no mais precise dela, de modo a aproveitar melhor os
recursos do computador. Tenha isso sempre em mente ao desenvolver com ponteiros.
Bem, aqui terminamos essa nossa aula. O prximo tpico ainda envolver ponteiros: na realidade, ele ir mostrar as
complexidades envolvendo ponteiros e funes, inclusive a tcnica de ponteiros de funo, muito usada em
programao. At l, sugiro que brinque um pouco. Tente remover as limitaes que impedem o ponteiro de
desgarrar e veja as conseqncias (PS: no faa isso em produo. Vou repetir: NO FAA ISSO EM PRODUO. NEM
PENSE EM FAZER ISSO EM PRODUO!!!). Analise os cdigos aos poucos. Tente implementar outros algoritmos (por
exemplo, o algoritmo de Fatorial).
At a prxima, e lembre-se: os comentrios esto abertos para dvidas e sugestes!

Bsico, Ponteiros

Matrizes e Ponteiros Parte 1


Ol todos! Espero que teham ido bem de Festas!
Hoje comearemos talvez o tpico mais importante de programao C. Esse tpico importantssimo e com certeza
provocar dvidas, portanto lembro que os comentrios devero ser usados para tirar dvidas. No deixem nenhuma
dvida passar nesse momento, pois isso poder depois complicar o aprendizado de outros tpicos avanados.
O tema de hoje, e de mais alguns posts Matrizes e Ponteiros.
Antes, porm, de vermos alguma programao, precisamos de alguma teoria:

Memria e Ponteiros:
Quando vimos a criao de variveis, ficou uma espcie de aberto. L foi dito que em C, as variveis representam
espaos de memria que o compilador ir preparar para determinadas funes para uso do programa. Na verdade, isso
t certo, mas no totalmente. Quando declaramos uma varavel, indicamos ao computador que precisamos que uma

derminada posio de memria seja separada para uso do programa e que, toda vez que o compilador achar o nome da
varivel, ele aponte o local em questo para onde a varivel foi encontrada. Desse modo podemos dizer que o nome de
uma varivel a representao do endereo onde fica o contedo da mesma.
Porm, existem situaes onde precisamos utilizar uma posio de memria que no conhecemos previamente. Na
realidade, isso mais comum do que se imagina: no fosse assim, todo programa deveria ser artificialmente limitado em
suas capacidades baseado em nmeros arbitrrios de informao a ser processada. Por exemplo: um programa teria que
criar previamente 500 variveis de notas para processar o boletim escolar de uma classe de 20 alunos, sendo intil para
uma classe de 501 alunos. Isso tambm aumentaria o custo de desenvolvimento e manuteno de um programa de
computador, alm de utilizar os recursos de um computador de maneira pouco eficiente.
O C nos oferece um mecanismo muito importante para apontar-se para um local de memria previamente desconhecido,
ao qual chamamos de ponteiro.
Uma varivel de ponteiro (que chamaremos de ponteiro, ou pointer em ingls) uma varivel que armazena o
endereo de memria onde o contedo que desejamos est. Ela em si no o contedo (embora possamos manipular o
ponteiro de modo a mudar o local de memria indicado conforme a necessidade), mas indica onde esse contedo t
armazenado. Usando esse ponteiro, podemos chegar ao contedo e o trabalhar.
Imagine o ponteiro como a agncia de correio. Ela no as pessoas para quem so entregues as mercadorias, mas ele
sabe de alguma forma onde elas moram. O ponteiro funciona de maneira similar.

Matrizes, strings e ponteiros


OK Mas o que os ponteiros tm a ver com matrizes e strings (que, como vimos l no Hello World, uma matriz de
caracteres). Bem. na realidade, podemos dizer que uma matriz um ponteiro.
Como assim Bial?
Quando voc declara, por exemplo c a n m [ 0 , voc est alocando 80 caracteres em uma matriz e apontando para
hr oe8]
o primeiro deles, de 0 a 79. (Veremos isso mais para frente quando falarmos de aritmtica de ponteiros).

Um programa exemplo com ponteiros


OK. Vamos dar um tempo na teoria. Hora de colocar a mo na massa. Digite e compile o programa abaixo:

#nld <ti.>
icue sdoh
itmi (od
n an vi)
{
ca plva8]el Wrd
hr aar[0=Hlo ol!;
ca *aar2
hr plva;
plva=aar;
aar2plva
pit(Otxo% et amznd n edrc %\plvaplva;
rnf et s sa raeao o neeo pn,aar,aar)

wie(plva)
hl *aar2
{
pit(Ocrce % d plva% et n edrc
rnf aatr c a aar s sa o neeo
%\*aar2plvaplva)
pn,plva,aar,aar2;
plva+;
aar2+
}
rtr()
eun0;
}

Bem, o incio cansamos de ver, mas vamos ver as declaraes que temos coisas interessantes nelas:

ca plva8]el Wrd
hr aar[0=Hlo ol!;
ca *aar2
hr plva;

Na primeira linha, declaramos uma matriz de 80 caracteres na qual armazenaremos a string Hello World!. O C possui uma
conveno bastante prtica para strings: sempre que voc coloca uma string entre aspas duplas (), o compilador j sabe
que dever colocar ao final da string em questo o caracter terminador nulo (\ 0 j vimos ele l atrs, lembra?). No
momento em que o programa carregado, o prprio sistema aloca o equivalente a 80 caracteres e dentro deles coloca
a string Hello World!. Em seguida, declaramos uma varavel ponteiro de caracter (c a * ocasionalmente lendo-se
hr
char pointer) chamada p l v a . O asterisco o smbolo que indica que a varivel em questo um ponteiro para o
aar2
tipo de dado desejado, no o prprio dado. Embora todos os ponteiros tenham o mesmo tamanho, importante indicar
o tipo de dados ao qual aquele ponteiro aponta, pois o uso de um ponteiro de um tipo de dados errado pode acarretar
problemas serssimos (a m interpretao e uso dos dados pelo programa sendo o menor deles).
A linha seguinte:

plva=aar;
aar2plva

Tem que ser pensada calmamente. No caso de matrizes , existe uma coisa a ser mencionada: quando voc utiliza o
nome da varivel sem um [ (indicador de posio a referenciar), o C entende que voc deseja utilizar ou manipular a o
]
endereo que indica o priemiro item da matriz. Ao mesmo tempo, quando utilizamos apenas o nome da varavel ponteiro
sem o indicador * indica a mesma coisa. No caso, essa linha pode ser traduzida como:
,
Pegue o endereo do primeiro item da matriz p l v ae coloque-o como o endereo a ser apontado por p l v a
aar
aar2
Ponteiros em C podem ter a posio qual eles apontam modificadas em tempo de execuo. Na realidade, os ponteiros
so feitos justamente para terem esse comportamento: veremos a importncia desse comportamento mais adiante,

quando falarmos de alocao dinmica de memria.


Em seguida temos um p i t :
rnf

pit(Otxo% et amznd n edrc %\plvaplva;


rnf et s sa raeao o neeo pn,aar,aar)

A ideia aqui mostra onde que est, na memria, a string Hello World!. Em seguida temos um loop:
wie(plva)
hl *aar2
{
pit(Ocrce % d plva% et n edrc %\*aar2plvaplva)
rnf aatr c a aar s sa o neeo pn,plva,aar,aar2;
plva+;
aar2+

Que ir deslocar o ponteiro p l v a e mostrar em que lugar da memria cada um dos elementos de p l v a est
aar2
aar2
armazenado. Para isso, em ambos os caso, utilizamos o modificador de formato % que faz com que o p i t imprima na
p
rnf
tela o local na memria onde o ponteiro est apontando, e no o seu valor.
Agora, uma coisa pode ficar confusa no p i t dentro do loop: por que quando queremos mostrar o caracter, temos
rnf
que usar o smbolo *e quando queremos mostrar o string e o endereo apontado no? Isso acontece porque, tanto o
modificador %s quanto o %p esperam um endereo de memria, enquanto o modificador %c (para caracteres) espera
um contedo real (no caso, um caracter). Basicamente essa a diferena e uma diferena importante em C: em
muitos casos, o C espera contedos reais, discretos, como um nmero ou um caracter. Nos demais casos,
normalmente se trabalhar com ponteiros. No existem em C conceitos como strings, filas e listas como o de
linguagens de maior nvel, como PHP, Java ou Python. Na realidade, o C oferece mecanismos para criar-se e manipular-se
esses tipos de dados, em especial por meio dos ponteiros, mas a linguagem em si no possui tratativa para tais estruturas
de dados mais amplas. Veremos no futuro como criar algumas dessas estruturas de dados.
Vamos destrinchar um pouco mais esse w i e pois ele nos oferece dicas interessantes e maiores informaes sobre
hl,
como lidar com ponteiros em geral e com uma string em C em particular. Primeiro, vamos ver a condio de sada do
wie
hl:

wie(plva)
hl *aar2

Ou seja, enquanto o valor apontado por p l v a (* a a r 2 for considerado verdadeiro, o lao segue adiante.
aar2 plva)
Agora, a pergunta que deve estar passando na cabea : como ele vai saber se o valor apontado verdadeiro? Quem

ficou atento ao que dissemos quando falamos sobre os operadores lgicos deve ter se lembrado de que falamos que
para o C qualquer valor verdadeiro, exceo do nmero 0 do caracter terminador nulo \ 0e do valor pr-definido
,
n l . Se lembrarmos como as strings so compostas em C, elas so seqncias de caracteres terminadas com o caracter
ul
terminador nulo \ 0 Portanto, uma vez que o deslocamento do ponteiro leve-o para o caracter terminador nulo, o valor
.
do ponteiro ser falso e o programa sair do loop que criamos.
Mas como fazemos o ponteiro avanar nos da string?. Para isso, usamos um pouco de aritmtica de ponteiro. No C, se
usarmos os operandos aritmticos mais rudimentares + e - alm do incremento e decremento unitrios + e
,
+
,
podemos fazer o ponteiro avanar ou recuar, ou ento indicar elementos adiante e anteriores posio indicada pelo
ponteiro. No nosso caso, utilizamos o incremento unitrio:

plva+;
aar2+

No endereo em p l v a (perceba a ausncia do asterisco). Nesse caso, estamos indicando que queremos passar para
aar2
o prximo elemento de p l v a . Quando o C executar essa operao, ele ir somar o equivalente ao nmero de bytes
aar2
do tipo apontado por palavra2 ao valor de palavra2. Essa No nosso exemplo, no muda muita coisa, pois em quase todas
as plataformas, um char tem o tamanho de 1 byte, mas esse um conceito que importante ficar claro: quando
usamos operadores aritmticos para modificar o endereo apontado por um ponteiro, ele sempre trabalha somando ou
subtraindo em bytes o nmero de elementos do mesmo tipo vezes o tamanho do tipo. Isso ficar mais claro no prximo
post, quando iremos usar ponteiros para valores inteiros.
Isso nos d como o programa funcionar conceitualmente, mas para melhor entendermos o que aconteceu, vamos
analisar a sada do mesmo:

Analisando a sada do programa


OK, e como ser a sada disso tudo?
O que iremos ter de sada do programa aparentar ser algo como a seguir:

OtxoHloWrd et amznd n edrc 0bf5b


et el ol! sa raeao o neeo xfce0
Ocrce Hd plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce0
Ocrce ed plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce1
Ocrce ld plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce2
Ocrce ld plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce3
Ocrce od plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce4
Ocrce
aatr

d plvaHloWrd et n edrc 0bf5b


a aar el ol! sa o neeo xfce5

Ocrce Wd plvaHloWrd et n edrc 0bf5b


aatr
a aar el ol! sa o neeo xfce6
Ocrce od plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce7
Ocrce rd plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce8
Ocrce ld plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfce9
Ocrce dd plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfcea
Ocrce !d plvaHloWrd et n edrc 0bf5b
aatr
a aar el ol! sa o neeo xfceb

Os valores ao final de cada linha so os endereos onde esto armazenados os contedos em questo. Com certeze eles
iro aparecer diferentes para voc no momento em que voc executar o programa, mas o importante entend-lo.
A primeira coisa que todos os endereos so indicados no formato numrico de base 16, chamado hexadecimal. Essa
uma conveno antiga adotada no mundo da informtica para indicar endereos de memria. No importa para ns as
posies em questo, pois esses valores podero (e provavelmente iro) mudar de execuo em execuo.
Na primeira linha, indicado o endereo da string Hello World!, pego pelo endereo do incio da matriz que armazena o
vetor de dados. Repare bem que o endereo de Hello World! e do caracter H so os mesmos, e depois o endereo
onde ficam armazenados cara caracter aumenta de um em um byte (que o tamanho do tipo c a ).
hr
Como brincadeira, uma sugesto tentar fazer com que a string seja corrida ao contrrio. Isso pode ser feito
utilizando o decremento unitrio na aritmtica de ponteiros e usando os endereos para comparar o momento em que o
ponteiro p l v a chegar no incio da string p l v a (lembre-se que dever fazer comparaes com o endereo
aar2
aar
armazenado em ambos os casos, e no com seus contedos). algo mais difcil, e portanto ressalto que qualquer dvida
os comentrios esto abertos para que elas sejam tiradas.
Bem, esse apenas o incio do caminho nos ponteiros em C. Na prxima aula, veremos mais sobre a aritmtica de
ponteiros e alocao dinmica de memria, um tpico muito importante em C.
At l, bom estudo!

Bsico, Ponteiros

Funes Parte 2
Ol a todos!
Bem, primeiramente desculpem a demora, pois tive muitas atividades de trabalho que me frearam um pouco. Mas no
perdi a vontade de passar o que sei de C. E vocs, ainda esto aqui aprendendo?
Bem, ento vamos continuar o tpico da aula anterior: Funes.
Na aula anterior, vimos como construir uma funo, porque devemos us-las, e como passar parmetros e receber seus
retornos. Com isso, podemos dizer que sabemos construir funes. Porm, ainda no sabemos como aproveitar ao
mximo as funes, uma vez que vimos regras que amarram a construo de funes, tornando-as complexas. Em
especial a regra de criar-se a funo antes do uso (ou seja, colocar o cdigo da funo antes de qualquer chamada que
seja feita a ela) muito estranha. Na aula de hoje, iremos ver como escapar dessa amarra de programao. Tambm
veremos uma caracterstica das funes em C chamada recursividade, que a capacidade de uma funo chamar a s
prpria, o que torna mais simples construir-se determinados algoritmos e programas.
Bem, vamos ao que interessa:

Prottipos de Funo:
Bem, vamos comear com o primeiro tpico, que o de prottipos. No caso, teremos um programa em cada tpico.
Para o nosso tpico, usaremos o programa abaixo:

#nld <ti.>
icue sdoh
itsm(n a itb;
n oait , n )
itsbrcoita itb;
n utaa(n , n )
itmlilccoita itb;
n utpiaa(n , n )
itdvsoita itb;
n iia(n , n )
vi mi(od
od anvi)
{
/*
*
*N GC es cbclopr mi rtraosgit wrig
o C, se aeah aa an eon
eune ann:
*
*wrig rtr tp o an i ntn
ann: eun ye f mi s o it
*
/
itvl=,vl=,ot0 rs0
n a10 a20 p=, e=;
d
o
{
pit(Dgt u vlr;
rnfiie m ao:)
safd,vl)
cn(%&a1;
pit(Dgt otovlr;
rnfiie ur ao:)
safd,vl)
cn(%&a2;
pit(Eclaaoea asrraiaa\\;
rnfsoh
pro
e elzdnn)
pit(1Sm\;
rnf
oan)
pit(2Sbrcon)
rnf
utaa\;
pit(3Mlilccon)
rnf
utpiaa\;
pit(4Dvso)
rnf
iia;
pit(0Si d Porm\\Dgt o oeaoe eaoeaa sprds
rnf
ar o rgaanniie s prdrs
prco eaao
prepc:)
o sao;
safd,ot;
cn(%&p)
sic(p)
wthot
{
cs 1
ae :
rssm(a1vl)
e=oavl,a2;
bek
ra;
cs 2
ae :
rssbrcovl,a2;
e=utaa(a1vl)
bek
ra;
cs 3
ae :
rsmlilccovl,a2;
e=utpiaa(a1vl)
bek
ra;

cs 4
ae :
rsdvsovl,a2;
e=iia(a1vl)
bek
ra;
cs 0
ae :
bek
ra;
dfut
eal:
pit(Ocoivld\;
rnfpa naian)
cniu;
otne
}
i (p!0 pit (O rslao d sa oeaa pr % e % e
f ot=) rnf
eutd
a u
prco aa d
d
%\\vl,a2rs;
dnn,a1vl,e)
}wie(p!0;
hl ot=)
/*
*
*N GC alnaaax pooaosgit wrig
o C,
ih bio rvc
eune ann:
*
*wrig eun wt avle i fnto rtrigvi
ann: rtr ih
au, n ucin eunn od
*
/
rtr()
eun0;
}
itsm(n a itb
n oait , n )
{
rtr ab
eun +;
}
itsbrcoita itb
n utaa(n , n )
{
rtr ab
eun -;
}
itmlilccoita itb
n utpiaa(n , n )
{
rtr ab
eun *;
}
itdvsoita itb
n iia(n , n )
{
rtr ab
eun /;
}

Primeira coisa: perceba que usamos novamente um outro tipo de prottipo para m i ( , v i m i ( o d . Como
an) od anvi)
dissemos nos comentrios do programa, o uso desse prottipo no padro e ir retornar alertas (Warnings) pelo

compilador. Se preferir fazer um programa pedante (ou seja, sem erros ou alertas), voc pode trocar o prottipo do
m i ( de volta para o velho e bom i t m i ( n a g , c a * a g ) De qualquer forma, apenas fizemos isso
an)
n anit rc hr* rv.
para demonstrar o que acontece quando tenta-se utilizar um desses prottipos no-padro.
Agora, vamos a algo importante antes de entrarmos no nosso tpico. Olhe o cdigo em verde:

sic(p)
wthot
{
cs 1
ae :
rssm(a1vl)
e=oavl,a2;
bek
ra;
cs 2
ae :
rssbrcovl,a2;
e=utaa(a1vl)
bek
ra;
cs 3
ae :
rsmlilccovl,a2;
e=utpiaa(a1vl)
bek
ra;
cs 4
ae :
rsdvsovl,a2;
e=iia(a1vl)
bek
ra;
cs 0
ae :
bek
ra;
dfut
eal:
pit(Ocoivld\;
rnfpa naian)
cniu;
otne
}

Esse comando de controle de fluxo, o s i c , muito usado como substituto para cadeias monstruosas de i
wth
f
e s i l e em especial quando existem cdigos que sero usados em uma ou mais opes. O s i c a e
lefes,
wthcs
compara o valor da varivel dada como entrada (no nosso caso, o t com o valor inserido em cada uma das linhas c s .
p)
ae
Caso o valor da varivel em questo seja igual ao valor de um dos c s , o programa ir seguir a execuo desse ponto
ae
at o final do bloco s i c a eou at encontrar um comando b e k o que acontecer primeiro. No caso de nenhum
wthcs
ra,
dos c s case com o valor da varivel a ser comparada, nada ser feito, a no ser que exista uma clusula d f u t
ae
eal
estipulada no bloco. Nesse caso, o programa ir continuar a execuo a partir desse ponto, valendo as mesmas regras
para os demais c s . No nosso caso, por exemplo, se opt for igual a 7, o d f u t ser executado e exibir na tela
ae
eal
Opcao Invalida, e retornar ao incio do lao d h l . Caso, por exemplo, o testivesse em 2, o resultado da funo
owie
p
s b r c o v l , a 2 seria associado variavel r se em seguida o s i c a eseria interrompido.
utaa(a1vl)
e
wthcs
Bem, agora que falamos desse comando de controle de fluxo que passou batido at agora, vamos falar sobre o nosso
tpico atual.
Como vimos na aula anterior, as funes devem, na teoria, vir antes de serem usadas por um programa. Na realidade,

isso no bem verdade. O que o compilador normalmente precisa saber como trabalhar com uma funo, ou seja, os
valores que ele precisa passar para a mesma como parmetros e o tipo de retorno da mesma. O cdigo em si no precisa
sequer ser descrito como parte do seu programa, podendo estar (o cdigo em si) em qualquer outro lugar. (lembram
das bibliotecas, como s d o h s r n . e s d i . ? Na realidade eles so teis para o compilador saber como usar
ti., tigh
tlbh
as funes que eles representam. Os cdigos esto armazenados em outras bibliotecas e arquivos dentro do sistema
operacional ou do compilador). Como exemplo, podemos pensar em um conector para celular. Cada celular usa um
conector especfico e, desde que o conector siga o tipo de conexo que o celular exige, pode ser usado conectores de
quaisquer marcas (vide a quantidade de carregadores genricos que existem por a) e com qualquer tipo de entrada de
energia (no importa se de tomada ou de carro, por exemplo).
No C, chamamos esses padres de prottipos de funo. Um prottipo de funo nada mais que um informativo que
o compilador usa para saber como chamar uma funo. A idia que os prottipos representam a seqncia de
parmetros e o tipo de retorno das funes a serem usadas no nosso cdigo. Lembram-se de quando falamos que existe
a idia de caixa preta no cdigo? Os prottipos so o que permitem a existncia dessa caixa preta: o que o
programador e o compilador precisa saber o que a funo precisa de entrada e o que ela devolve como resultado. O
programador no precisa como saber como a funo foi construda (imaginando que no tenha sido ele que a construiu)
e o compilador s precisa saber se as chamadas de funo so encaixveis corretamente ao cdigo.
Quando o programa compilado, as funes que no fazem parte do programa e que esto em biblotecas so
encaixadas ao programa de vrias formas em um passo chamado de linkedio (em alguns livros mais atuais, usa-se o
termo ligao). Um binrio sem ser linkeditado chamado ocasionalmente de cdigo-objeto e, embora no seja til para
ser executado, eles so muito teis (veremos no futuro compilao de programas com mltiplos fontes), inclusive
podendo conter funes que possam ser ligadas a posteriori a outros programas (nessa situao, o cdigo-objeto
chamado tambm de biblioteca). Uma vez que o ou os programas-fontes sejam compilados e linkeditados, o binrio
executvel est pronto.
Voltando ao assunto, por meio dos prottipos que o compilador sabe como encaixar cada funo nas partes onde as
funes so chamadas (na realidade, so colocados endereos para onde o programa vai e segue a execuo). Alm
disso, por meio dos prottipos que o compilador, at certo ponto, consegue perceber se o cdigo est corretamente
construdo, pois ele tem todas as informaes da entrada de dados (parmetros) e da sada (retorno).
Para o prottipo da funo, a construo igual ao do nome da funo no incio da mesma (que alguns chamam de
cabealhos), com a diferena do ;no fim do prottipo. Na realidade, para o prottipo, voc no precisa colocar nenhum
nome de varivel. uma vez que nesse momento, o que ele precisa saber quais os tipos de parmetro a serem
recebidos, e no seu nome. No caso, embora tenhamos usado:

itsm(n a itb;
n oait , n )

para uma maior legibilidade do cdigo, poderamos usar simplesmente:

itsm(n,it;
n oait n)

que seria to til quanto o prottipo que o colocamos. De qualquer forma, aconselho que mantenha as declaraes com

nomes de variveis como uma boa prtica, para aumentar a legibilidade sobre quais so os parmetros a serem
passadas.
No existe o que falar mais: uma vez que o prottipo tenha sido colocado de alguma forma disposio, o compilador
pode buscar funes em qualquer lugar, seja dentro do cdigo objeto equivalente ao fonte compilado, em um arquivo
de biblioteca ou no prprio sistema operacional e encaix-las ou lig-las ao programa do usurio.
De resto, existe pouco o que falar desse programa, pois ele no tem mistrios quanto ao que cada funo faz. Para as
brincadeiras, sugerimos:
1. Tente remover os prottipos (comentando-os, por exemplo) e compilar os programas. Alguns compiladores iro
dar alertas mas iro compilar o seu fonte, enquanto outros simplesmente se recusaro a compilar o fonte;
2. Para comprovar que o nome de varivel no prottipo no faz diferena, modifique o nome de varivel de
alguma das funes no prottipo, mas no na funo;
3. Para entender bem a idia dos prottipos de funo, uma boa forma ler a documentao da biblioteca-padro
do C. Existem muitas funes interessantes nela, em especial em bibliotecas como s d i . , s r n . ,
tlbh tigh
s d o h t m . e m t . . Nesse link voc encontra a documentao completa das bibliotecas-padro (em
ti., ieh
ahh
ingls). Procure ler com calma e tentar entender o que cada funo faz. Obviamente voc no compreendar
tudo no presente momento, pois muitas funes lidam com conceitos avanados que ainda falaremos, mas com
calma voc ver algumas funes interessantes. Se possvel, tente construir seus prprios programas e funes a
partir dos cdigos que mencionamos no momento;
Bem, com isso terminamos a parte de prottipos de funo. Vamos a um tpico mais interessante: recursividade.

Recursividade Chamando a si prprio:


Existem certos algoritmos (formas de descrever-se algo para um computador) que so mais facilmente representveis
quando eles usam de algum modo a si prprio. Dois exemplos clssicos so os clculos de Fatorial e do nmero Fibonacci.
Para relembrar, um nmero N fatorial (representado N!) representado pela multiplicao de todos os inteiros at N
(sendo que os fatoriais de 0 e 1 so definidos como 1). No caso, esse o algoritmo que iremos ver em C, pois pe um
exerccio clssico de programao recursiva.

#nld <ti.>
icue sdoh
usge itftra (nindita
nind n aoil usge n )
{
i (a=)| (=1)
f (=0 | a=)
rtr 1
eun ;
es
le
rtr aftra(-)
eun *aoila1;
}
itmi(od
n anvi)
{
usge itnmrFtra=;
nind n ueoaoil0
pit(Dgt o nmr a qa dsj-e otr ftra (pns pstvs:
rnfiie
ueo o ul eeas
be
aoil aea
oiio)
;
)
safd,&ueoaoil;
cn(% nmrFtra)

pit(%!=%\nmrFtra,aoilnmrFtra);
rnfd
dn,ueoaoilftra(ueoaoil)
rtr()
eun0;
}

Esse cdigo bem bsico e tem pouco mistrios. O importante atentar ao cdigo da funo fatorial:

usge itftra (nindita


nind n aoil usge n )
{
i (a=)| (=1)
f (=0 | a=)
rtr 1
eun ;
es
le
rtr aftra(-)
eun *aoila1;
}

A primeira coisa que ele define que, caso o valor passado na execuo da funo seja 0 ou 1, o valor a ser devolvido
pela execuo 1. Caso contrrio, ele ir devolver o valor passado vezes o valor devolvido pela execuo da mesma
funo com um valor igual ao valor passado-1.
Como funcionaria ento, por exemplo, para o fatorial 5? Vejamos em um teste de mesa:
m i comea executando f t r a ( )
an
aoil5;
Como 5 no igual a 0 ou 1, ele deveria retornar 5*o resultado de f t r a ( - ) ou seja, f t r a ( )
aoil51,
aoil4.
Como no sabe o valor de f t r a ( ) ele executa f t r a ( )
aoil4,
aoil4;
Como 4 tambm no igual a 0 ou 1, ele deveria retornar 4*o resultado de f t r a ( - ) ou seja,
aoil41,
f t r a ( ) Como no sabe o valor de f t r a ( ) ele executa f t r a ( )
aoil3.
aoil3,
aoil3;
Como 3 tambm no igual a 0 ou 1, ele deveria retornar 3*o resultado de f t r a ( - ) ou seja,
aoil31,
f t r a ( ) Como no sabe o valor de f t r a ( ) ele executa f t r a ( )
aoil2.
aoil2,
aoil2;
Como 2 tambm no igual a 0 ou 1, ele deveria retornar 2*o resultado de f t r a ( - ) ou seja,
aoil21,
f t r a ( ) Como no sabe o valor de f t r a ( ) ele executa f t r a ( )
aoil1.
aoil1,
aoil1;
Como 1 igual a 1, a funo fatorial retorna 1;
Agora ele volta para a execuo de f t r a ( ) pois obteve o valor de f t r a ( ) que ele precisava. Ele faz
aoil2,
aoil1,
2*fatorial(1), ou seja, 2*1, retornando 2;
Em seguida, retoma a execuo de f t r a ( ) pois obteve o valor de f t r a ( ) que ele precisava. Ele faz
aoil3,
aoil2,
3*fatorial(2), ou seja, 3*2, retornando 6;
Em seguida, retoma a execuo de f t r a ( ) pois obteve o valor de f t r a ( ) que ele precisava. Ele faz
aoil4,
aoil3,
4*fatorial(3), ou seja, 4*6, retornando 24;
Por fim, retoma a execuo de f t r a ( ) pois obteve o valor de f t r a ( ) que ele precisava. Ele faz
aoil5,
aoil4,
5*fatorial(4), ou seja, 5*24, retornando 120 para m i ;
an
Ateno para a questo de:

i (a=)| (=1)
f (=0 | a=)

rtr 1
eun ;

Todo algoritmo recursivo deve ter uma situao de escape, caso contrrio provocar um loop infinito. No caso do
fatorial, o fato que os fatoriais de 0 e 1 so definidos por padro como 1 (no link da Wikipedia mostrado anteriormente
h uma explicao dos motivos desses valores serem pr-definidos). Somando-se isso e o fato de que a chamada
recursiva sempre equivalente ao valor da chamada atual-1, o resultado que cedo ou tarde, o valor vai ser 0 ou 1
(valores negativos so negados j na tipagem u s g e i t ou seja, a escada de chamadas ir ser desfeita, com
n i n d n ),
cada chamada devolvendo os valores esperados pela anterior. Caso isso no ocorra, haver um loop infinito que se
encerrar com um estouro de memria (uma vez que cada chamada de funo armazena localmente valores e portanto
precisa de espao de memria).
Bem, no existe mais o que se falar sobre recursividade. Como brincadeiras quanto recursividade, sugiro:
1. Uma circunstncia a
operadores unrios
aftra(-)
*aoila1,
operadores unrios
atribuio;

ser levada em considerao ao se construir algoritmos com recursividade sobre o uso dos
de incremento e decremento (+ e Para observar seu impacto, no r t r
+
).
eun
tente substituir por r t r a ( e verifique o que acontece. Lembre-se que os
eun *a)
de incremento e decremento atuam como operadores de incremento/decremento e

2. Edite o cdigo da chamada recursiva e elimine a condio de escape da recurso. Coloque algum cdigo que
permita voc visualizar os valores recebidos a cada chamada recursiva e seus impactos e analise o resultado final;
3. Tente implementar o algoritmo para determinar-se um nmero Fibonacci. Lembrando que um nmero de
Fibonacci equivale soma de todos os nmeros naturais antecessores a ele, predefinido que o 0 Fibonacci 0 e
o 1 Fibonacci 1. Se voc reparar bem, no muito diferente do clculo de um nmero Fatorial;
Com isso, acabamos o bsico de Funes. Ainda existem tpicos a serem cobertos. Em especial, um tpico importante
que estamos deixando para trs o de tipos de passagem de parmetro, um tpico importante que cobriremos quando
falarmos de ponteiros, nosso prximo assunto.
Vamos ter algum tempo at comearmos o assunto de ponteiros. Enquanto isso, existem muitos sites e apostilas na
internet com exerccios de programao em C que podero ajudar voc a fixar o contedo que vimos at agora.
Enquanto a mim, vou ficar um tempo sem uma Internet de boa qualidade, mas prometo que, assim que voltar estarei
postando o incio do tpico de ponteiros, com a parte de matrizes, ponteiros e a correlao entre os dois. Esse ser um
tpico bastante complexo, mas que se lido com calma ir ser bem fixado.
Ento, nos vemos em 2011, pessoal. At l, boas festas e muita programao C para todo mundo!

Bsico, Controle de Fluxo, Funes

Funes Parte 1
Ol a todos!
Bem, agora j estamos comeando a pensar em programas ns mesmos, no ?
Agora, vamos pensar um pouco no programas que fizemos l atrs, quando falamos de Entrada de Dados e Variveis.
Aquele foi um programa razoavelmente grande. Agora imagine que voc crie um programa realmente complexo, que
realize atividades similares em diversos pontos do mesmo. Se voc escrever esse programa como criamos o programa de

exemplo de Entrada de Dados e Variveis, voc teria um grande programa com vrios pontos repetidos. Desse modo,
caso precisasse alterar o modo como essas atividades similares seriam executadas, voc teria que mexer em vrios pontos
similiares, o que mesmo o melhor dos programadores no conseguir com facilidade e sem a possibilidade de provocar
erros.
Por isso, o C (como toda boa linguagem de programao) prev formas de dividir o programa em pedaos que
executem a mesma tarefa. Chamamos esses pedaos de funes.
Na realidade, j usamos muitas funes at aqui. Todo comando que mostramos at agora, exceo de palavras
chaves como i ou d h l , so funes. A vantagem de dividir-se o programa em funes que podemos isolar
f
owie
determinadas atividades nelas, o que permite:
1. Programas escritos de maneira mais legvel;
2. Melhor manuteno do cdigo, em especial em projetos complexos; voc foca s no que est dando errado e
uma vez que tudo esteja OK as melhorias se refletem apenas no que est dando errado;
3. Reutilizao de cdigo: por meio das funes podemos criar bibliotecas de funes (lembra do que falamos
anteriormente sobre isso?) que englobem funes que usamos constantemente em um (ou mesmo em vrios
programas) e com isso reaproveitar esse cdigo em muitos casos;
Claro que uma funo deve ser criada para ser suficientemente genrica, mas feito isso ela pode ser aproveitada nos mais
diversos momentos.
Bem, dito essa teoria, vamos ao nosso programa exemplo: um programa de mdias escolares.

#nld <ti.>
icue sdoh

/*
*
*Es fn d mdaiarsetro vlrsaiindsmda
sa uo e i r cecna s aoe dcoao
i
*atro edvleamdan mmno
neir
eovr
i o oet
*
/
fotmdafotnt)
la ei(la oa
{
sai fotmdata=.;
ttc la eiAul00
mdata=mdata=0?oa(eiAulnt)2
eiAul(eiAul=)nt:mdata+oa/;
rtr mdata;
eun eiAul
}

itmi(od
n anvi)
{
fotnt=.;
la oa00
itnts0
n oa=;

d
o
{
pit(Dgt apiant o - pr si:)
rnfiie
rxm oa u 1 aa ar;
saff,nt)
cn(%&oa;
i (oa=1
f nt!-)

{
nts+
oa+;
pit(Cmes nt,amdattld %.fn,ei(oa)
rnfo sa oa
i oa
e 92\mdant);
}
}wie(oa=1;
hl nt!-)
rtr 0
eun ;
}

Na parte do m i ( fica a ressalva de que mudamos um pouco o incio: de i t m i ( n a g , c a * a g )


an)
n an it rc hr* rv,
estamos usando i t m i ( o d . Como dissemos no Hello World, essa construo (sobre a qual aproveitaremos para
n anvi)
falar a seguir) pode ser mudada, embora o compilador possa gerar um aviso de que voc est fugindo do padro do C.
No nosso caso, colocamos v i nos parmetros para indicar que no receberemos parmetros (v i uma palavra
od
od
reservada do C que indica algo vazio veremos mais sobre isso adiante), ou melhor, que no utilizaremos parmetros
que sejam passados. De resto, o nosso m i ( engloba coisas que j vimos nas ltimas semanas e que voc deve estar
an)
afiado caso tenha seguido as sugestes que fizemos para mexer no cdigo e compreendido o que fizemos at agora. Na
verdade, tem algo que iremos falar, mas apenas depois que vermos nossa funo:

fotmdafotnt)
la ei(la oa
{
sai fotmdata=.;
ttc la eiAul00
mdata=mdata=0?oa(eiAulnt)2
eiAul(eiAul=)nt:mdata+oa/;
rtr mdata;
eun eiAul
}

J falamos anteriormente sobre o conceito de blocos de cdigo. Basicamente, um bloco de cdigo uma parte do
programa que isolada logicamente e considerada como um comando nico. Para isolar-se um bloco de cdigo em C,
usa-se as chaves ({ ). De maneira rpida e suja, podemos definir uma funo como um bloco de cdigo com nome.
}
Na realidade, uma funo pode estar em um outro ponto do programa ou at mesmo em um arquivo totalmente
isolado. A nica regra para uma funo que ela tem que vir de alguma forma antes de qualquer ponto do programa
onde ele seja usado (na prxima semana, quando encerrarmos o assunto Funes, veremos que no bem assim e
existem tcnicas simples que permitem ao programador colocar sua funo onde deseejar).
Uma funo na realidade caracterizada por realizar alguma tarefa e retornar algum valor. Para facilitar a vida do
programador e desobrig-lo de saber o que a funo realmente faz, toda linguagem de programao parte do princpio
de que uma funo uma caixa preta: voc coloca determinados parmetros na entrada da mesma, ele realiza algum
processamento (que o programador no precisa realmente saber do que se trata) e devolve ao usurio alguma sada.
Porm, embora seja uma caixa preta, sempre necessrio a uma funo indicar o que ela espera receber de
parmetros para trabalhar e o que o usurio ir receber de volta. No C isso feito no momento em que se nomeia a
funo.
Como dissemos acima, podemos pensar em uma funo como um bloco de cdigo com nome. Em C, chamamos o

nome da funo de prottipo ou assinatura (esse ltimo mais usado em documentos focando C++ e tem a ver com
certas propriedades da mesma). O prottipo de uma funo costuma seguir o formato:
rtronm (ioa1pr[ii1,ioa2pr[ii2,,ioaNpr[iiN)
eon oe tpPr a1=nt]tpPr a2=nt]tpPr aN=nt]
Onde:
r t r oindica o tipo de varavel que a funo retorna. Esse tipo pode ser qualquer tipo bsico do C, ponteiros
eon
(veremos isso quando entrarmos nesse assunto) ou v i : v i pode ser entendido como um nulo, ou seja,
od od
quando C executar uma funo cujo retorno seja void ele no deve esperar nenhum retorno do mesmo (na
realidade, alguns compiladores costumam provocar erros de compilao);
n m o nome pelo qual a funo chamada. Os nomes de funo seguem as mesmas regras que vimos quanto
oe
aos nomes de varivel que vimos quando falamos de Entrada de Dados e Variveis. Alm disso, no podem ter o
mesmo nome de funes que tenham sido importadas por meio de # n l d s. Uma coisa: mesmo para as funes
icue
da biblioteca padro vale a mesma regra. Por exemplo: se eu no importar a s d o h posso incluir minha prpria
ti.,
verso de p i t sem problemas. Veremos o motivo disso adiante;
rnf
Dentro dos parnteses inclumos uma srie de indicaes sobre os parmetros da funo, t p P r
ioa1
p r [ i i 1 , i o a 2 p r [ i i 2 , , i o a N p r [ i i N . Elas so distrinchadas assim: t p P r
a1=nt]tpPr a2=nt]tpPr aN=nt]
ioa
o tipo da varivel em questo, que pode ser de qualquer tipo bsico do C, tipos do usurio ou ponteiros (veremos
os dois ltimos no futuro). A ele pode ser agregado um modificador c n t que indica que, no importa o que
os,
acontea, esse valor no deve ser modificado (esse modificador s til quando usamos ponteiros explicaremos
o porque quando alcanarmos esse tpico); p r o nome do parmetro. Opcionalmente, voc tem i i , que
a
nt
permite que voc defina um valor default para inicializao, que ser colocado caso esse parmetro no seja
passado (isso feito ao deixar o espao desse parmetro vazio, sem nenhuma vriavel ou valor, mesmo n l
ul
para o compilador, passar n l passar um valor, ainda que nulo).
ul
Uma ressalva sobre parmetros: se voc no esperar nenhum parmetro em uma funo, uma boa prtica inserir
v i dentro dos parnteses, ainda que parnteses em branco (( ) seja igualmente suficiente para indicar uma
od
)
funo sem parmetros. Essa boa prtica ajuda na leitura do que a funo faz e em muitas documentaes voc
ver ela sendo adotada;
Bom, aps vermos como nomeada uma funo, vamos ver o nome de nossa funo e destrinch-la:
fotmdafotnt)
la ei(la oa
Primeiro, indicamos que ela uma funo que ir devolver um valor de ponto flutuante (f o t
l a );
Depois, informamos que o seu nome m d a
ei;
E na parte de parmetros, indicamos que ela recebe apenas um parmetro, do tipo flutuante e chamado nota
(f o t n t ). Tambm, pela ausncia de um igual, indicamos que ela no tem um valor default. Portanto, a
la oa
ausncia desse parmetro ir provocar um erro. Se tivssemos indicado um default e no passssemos um valor, o
compilador poderia devolver um alerta, mas ainda assim o programa iria compilar normalmente;

Escopo de varvel e o modificador s a i :


ttc
Dito isso, vamos falar sobre o cdigo. Na nossa primeira linha, temos uma declarao especial:
sai fotmdata=.;
ttc la eiAul00
Essa nica linha vai nos levar a todo um tpico de explicaes. Na realidade, para entendermos ela totalmente,
precisaremos falar sobre escopo de varivel e sobre como funciona a declarao de variveis dentro de uma funo.
Como j fizemos no m i ( quando falamos de Entrada de Dados e Variveis, podemos declarar variveis internas em
an)
uma funo Na realidade, voc pode declarar variveis dentro de qualquer bloco de cdigo. Como uma funo um
bloco de cdigo nomeado, podemos definir variveis dentro delas. Alm dos parmetros (que so variveis dentro da
funo), podemos declarar quantas variveis que acharmos necessrias. No caso, da mesma forma que uma varivel no
m i ( representa o local onde um contedo fica dentro do programa principal, uma varivel dentro de uma funo
an)
representa o local onde um contedo ficar armazenado dentro da funo. Importante, porm, notar que uma varivel

dentro de uma funo pode ter um nome que j esteja sendo usado fora da funo. Isso possvel pois, embora os
nomes das variveis sejam iguais, seus escopos so diferentes: uma vale para a funo m i ( e outra para a funo que
an)
o usurio criou, e o compilador, ao gerar o programa, tratar tais variveis como variveis diferentes e portanto com locais
em memria diferentes. Existem algumas formas de mudar esse comportamento que veremos adiante.
Normalmente, ao chamar-se uma funo, todas as suas variveis so reinicializadas com os valores que o usurio definiu
(ou com valores arbitrrios, caso no o tenha feito), ou seja, podemos dizer que a cada chamada de uma funo o valor
de suas variveis resetado. Esse o comportamento esperado, pois parte-se do princpio que cada chamada de uma
funo ir processaar valores no exatamente iguais.
Porm, existem casos onde podemos precisar que um ou mais valores permaneam salvos entre chamadas de uma
mesma funo. Para garantir que isso acontea, utilizamos um modificador na declarao da varivel chamado s a i
ttc
(esttico). O que ele faz garantir que a varivel em questo tenha seu valor mantido entre as vrias chamadas
funo. Ou seja, aps a funo terminar sua execuo, seu valor no resetado como normalmente acontece. Uma
situao onde isso pode ser til quando, por exemplo, desejamos saber o nmero de pessoas que executou uma
determinada transao bancria: uma forma bruta seria colocar uma varivel somando o nmero de requisies feitas
funo de transao e depois definir uma forma de obter-se esse valor. (Embora normalmente s um valor possa ser
retornado por funo, existem truques que permitem obter-se mais de um valor veremos tais truques quando
falarmos sobre passagem de valor para funes, no prximo post).
No caso, voltando ao nosso programa, o que fazemos declarar nossa varivel m d a t a como tipo flutuante (f o t
eiAul
la)
e esttica (s a i ), inicializando ela como 0 0(zero flutuante). Essa inicializao ser feita apenas na primeira chamada
ttc
.
funo dentro do programa. Uma vez que essa chamada tenha se encerrado, na entrada seguinte o sistema ir manter o
valor com o qual a varivel encerrou a chamada anterior. Ou seja, caso o valor final de m d a t a seja 2, esse ser o
eiAul
valor de m d a t a na chamada seguinte.
eiAul
Voc deve estar se perguntando agora: qual a diferena entre usar s a i e c n t em uma funo?.
ttc
os
Aparentemente seria nenhuma, mas na verdade ENORME:
c n t usado quando voc no quer que, durante a execuo da funo, o valor da varavel seja alterado.
os
Porm, uma vez que uma c n t uma varivel como outra qualquer at ser inicializada, ela tambm tem valores
os
arbitrrios preenchidos nela at ser inicializada (lembre-se que uma c n ts pode ser inicializada, ou seja, receber
os
um valor, UMA NICA VEZ). No caso de c n t aps o trmino da execuo da funo, o valor definido na
os,
inicializao perdido e poder receber um valor completamente diferente na prxima execuo;
s a i deve ser usado quando voc no quer que, entre execues da funo, o valor da varivel em questo
ttc
seja perdido. Dentro da funo, durante a execuo da mesma, voc poder alterar normalmente, como qualquer
outra varivel. Porm, seu valor no ser resetado aps o trmino da execuo da funo.
Aqui cabe dizer ainda que possvel criar-se uma varivel s a i c n t Esse pequeno monstrinho seria uma
ttc os.
varivel dentro de uma funo cujo valor permaneceria sempre o mesmo entre todas as chamadas da mesma,
definido na inicializao da varivel na primeira chamada. CUIDADO: esse tipo de monstrinho pode gerar dor de
cabea sria na programao e normalmente no far nada de mais interessante que no possa ser feito, por
exemplo, com smbolos # e i e
dfn;
Bem, no temos muito o que dizer aqui mais sobre o escopo. Veremos um pouco mais sobre escopo de variveis no
futuro. Agora, vamos continuar analizando nosso cdigo.

Retorno a palavra chave r t r :


eun
Continuando nosso cdigo, aps a declarao de varivel, temos o seguinte cdigo:

mdata=mdata=0?oa(eiAulnt)2
eiAul(eiAul=)nt:mdata+oa/;
rtr mdata;
eun eiAul

A primeira linha representa uma atribuio condicional que vimos na ltima aula ao aprofundarmos operadores e lgica.
No caso, quando m d a t a for 0 (no caso, na primeira chamada de funo), ele receber o valor de n t (parmetro
eiAul
oa
passado pelo usurio). Caso contrrio, ir manter em mediaAtual o valor mdio entre m d a t a e n t . No h muito
eiAul oa
mistrio aqui e, embora a construo possa ser confusa, s ler com ateno que fica claro o que estamos fazendo. Em
seguida, usamos a palavra chave r t r para devolver a main (que chamou essa funo) o valor calculado.
eun
Se lembrarmos bem, temos visto r t r desde nosso primeiro Hello World. Isso porque, como dissemos na poca,
eun
mesmo main() uma funo para o C, ainda que especial. E a ltima coisa que qualquer funo precisa fazer devolver
o controle da execuo para a funo que a chamou. Para isso, utilizamos r t r para indicar que terminamos de
eun
processar tudo o que devamos e que o processador pode voltar para onde ele tinha parado antes de comear a
executar nossa funo.
Como assim?, voc deve se perguntar. Bem, ao darmos incio ao programa, o mesmo carregado na memria por um
processo de todo sistema operacional chamado loader e sua execuo iniciada. Conforme os comandos so
executados, o sistema vai respondendo adequadamente, modificando espaos de memria (representados no programa
pelas variveis) e seguindo adiante de maneira sequencial (isso tambm considerando os comandos de controle de
fluxo). Quando uma funo chamada, o programa principal passa o controle da execuo para outro ponto
completamente arbitrrio dentro do espao que o programa ocupa na memria e executa os comandos informados na
funo. Uma vez que termine, ele tem que devolver o controle para que o programa principal volte a ser executado, o
que em C indicamos com r t r , e assim sucessivamente at que o programa chegue ao fim do programa principal e
eun
seja encerrado.
O que precisamos saber ento sobre o funcionamento de uma funo:
1. Um programa pode requisitar a qualquer momento a execuo de uma funo;
2. Funes so usadas para tornar o programa mais reutilizvel, mais fcil de manter-se e mais legvel;
3. Ao requisitar a execuo de uma funo, o programa pode precisar passar parmetros em uma ordem
determinada, indicando o que a funo precisa ter de entrada para trabalhar;
4. A funo ir trabalhar como foi estipulado pelo criador da funo: para o programa, uma funo atua como uma
caixa-preta;
5. A funo devolve o controle de execuo ao programa principal ao encerrar-se, devolvendo valores determinados
pelo tipo de retorno da funo como sada;
Uma coisa antes de encerrarmos: ao devolver dados para o programa principal, devolvemos ele segundo o tipo estipulado
l no prottipo da funo. Se for necessrio por qualquer motivo, a funo pode fazer typecast do valor devolvido antes
de o retornar. Porm, adequado que no seja usado tal expediente: use uma varavel do mesmo tipo de retorno
estipulado no prottipo e faa as operaes necessrias a usando e use ela como valor para r t r . Assim evitar dores
eun
de cabeas e bugs difceis de depurar-se.
Bem, no temos mais o que falar sobre a funo em questo, ento vamos voltar ao programa principal.
No programa principal no h mistrios:

itmi(od
n anvi)

{
fotnt=.;
la oa00
itnts0
n oa=;
d
o
{
pit(Dgt apiant o - pr si:)
rnfiie
rxm oa u 1 aa ar;
saff,nt)
cn(%&oa;
i (oa=1
f nt!-)
{
nts+
oa+;
pit(Cmes nt,amdattld %.fn,ei(oa)
rnfo sa oa
i oa
e 92\mdant);
}
}wie(oa=1;
hl nt!-)
pit(Vc isru% nts\ nts;
rnfo nei d oa.n, oa)
rtr 0
eun ;
}

Inicializamos duas variveis, uma f o tchamada n t (que receber a nota a ser adicionada mdia) e uma inteira
la
oa
chamada n t s(que usamos como um contador do nmero de notas adicionadas). Um lao dowhile usado para
oa
que novas notas sejam inseridas uma aps a outra at que o usurio entre com -1 (que considerado valor de sada).
A nica coisa que precisamos aqui a linha:
pit(Cmes nt,amdattld %.fn,ei(oa)
rnfo sa oa
i oa
e 92\mdant);
A pergunta essa uma entrada vlida? A resposta : SIM.
No nosso caso, o p i t espera um valor de tipo flutuante (repare no formato % . f o que oferecido por nossa
rnf
9 2 ),
funo m d a(que retorna um tipo flutuante). E se o programa esperasse, por exemplo, um i t ou recebesse um
ei
n,
i t No caso, ocorreriam typecasts, mas o C sempre far o possvel para oferecer um retorno ao usurio, no importa
n ?
se os valores saiam esprios (ele parte do princpio de que o programador mantenha seus dados de maneira correta, no
fazendo muitas checagens).
Bem Vamos encerrar por agora nessa semana. Na prxima, iremos falar mais sobre funes, incluindo uma aprofundada
nos tipos de escopo de variveis e algumas dicas teis (e IMPORTANTSSIMAS) sobre funes e seus prottipos.
Para essa semana, umas brincadeiras:
1. Renomeie m d a t a para outros nomes de variveis que ocorram e veja o que ir acontecer;
eiAul
2. Remova s a i da declarao de varivel da funo m d a
ttc
ei;
3. Tente reescrever media de modo que voc no precise usar uma varivel s a i nela;
ttc
4. Escolha um segundo valor (por exemplo, -2) para resetar os valores de m d a
ei;
5. Tente imprimir o valor retornado por m d acomo inteiro e veja o que acontecer;
ei
6. Tente deslocar o cdigo da funo m d a para abaixo da funo m i e veja as mensagens de erro. Procure
ei
an
entender os motivos das mensagens;
Bem, at semana que vem, quando veremos mais sobre funes, praticamente fechando o assunto.

Bsico, Funes

Uma aprofundada em operadores e lgica em C


OK.
Vamos dar uma pequena pausa para aprofudar uma teoria que necessria para seguirmos em frente: os operadores.
Na nossa ltima aula, vimos um pouco sobre os operadores, em especial operadores matemticos, lgicos e relacionais.
Mas na verdade o C composto por uma enorme gama de operadores, capazes de realizar muitas atividades. Aprender
bem como usar operadores algo importantssimo para construir bons programas em C. Portanto, vamos dar uma pausa
e reforar essa teoria antes de seguirmos em frente.
A primeira coisa a entender que temos vrios tipos de operadores em C, que podemos dividir em grupos para facilitar
a compreeno:
Operadores de atribuio so operadores usados para atribuir-se valores a variveis;
Operadores aritmticos com ele realizamos operaes matemticas simples usando dois valores, os operandos;
Operadores relacionais comparam os valores de dois operandos, verificando se eles representam verdades ou
falsidades lgicas naquele momento;
Operadores lgicos comparam os valores de dois operandos em termos lgicos, ou seja, comparaes baseadas
na lgica booleana (E, OU e NO);
Operadores de bits Permitem realizar operaes diretamente nos bits de um determinado valor (em geral,
inteiro);
Operadores compostos so operadores complexos que podem combinar as funes de dois operadores do tipos
acima;
Operadores especiais -so operadores usados no desenvolvimento da aplicao;
Alm disso, existe uma ordem de prioridade dos operadores que iremos mostrar no final desse post. Voc no precisa
decorar essa tabela: voc pode utilizar esse post como referncia ou ento facilmente pegar uma cola dessa tabela na
Internet em vrios sites de referncia.
Bem, vamos comear ento com o Operador de Atribuio.

Operador de atribuio =
O operador de atribuio =serve para indicar ao C que desejamos armazenar (ou atribuir) a uma determinada varivel um
valor. J vimos nos programas anteriores vrios exemplos de atribuies, em especial quando inicializamos variveis como
fizemos no programa de Entrada de Dados. Uma coisa, porm, que importante dizer que o C permite fazer-se
mltiplas atribuies, desde que os tipos sejam compatveis. Por exemplo, o trecho de cdigo abaixo:

itac
n ,;
fotbd
la ,;
a=b=c=d=0
;

vlido, uma vez que be d ainda que sejam do tipo f o t podem receber valores inteiros (por causa do autocast
,
la,
lembra que vimos quando mostramos o programa de Entrada de Dados?). Basicamente, esse comando faz a seguinte
salada na atribuio:
Atribui 0 a d como d f o t d o autocast do 0(que nesse caso tratado como inteiro) para f o t
la,
la;
Em seguida, atribui a co valor de d dest inicializado como 0 0(0 no tipo f o t Ao receber o valor para c o
.
l a ).
,
sistema faz o autocast do valor, truncando-o para 0(inteiro);
Aps isso, atribui a bo valor de c repete-se o caso da atribuio para d
;
E por fim, atribui a ao valor de b repetindo o que aconteceu quando atribui-se a co valor de d
;
Importante notar que os autocasts so sempre executados no momento da atribuio do valor a uma varivel. Por isso
que, se dividimos dois inteiros e precisamos de um valor f o t mesmo atribuindo o resultado, precisamos forar o cast
la,
de um dos inteiros para f o tantes: se usarmos a diviso normalmente, ele ir trat-la como diviso entre dois inteiros e
la
dar um resultado inteiro, que ser convertido depois da operao em f o t Ao forar um deles como f o t
la.
la,
indicamos que precisamos de um resultado em ponto flutuante e, dando o autocast no outro operando, o programa
realizar uma diviso em ponto flutuante e retornar um valor f o t
la.
Aproveitando que entramos no assunto typecast, existe uma prioridade nos autocasts: isso feito com o objetivo de
impedir que os resultados percam valor (por exemplo, ao converter-se um valor de um tipo para um outro tipo cujo
tamanho seja menor em bits e, portanto, capaz de representar uma gama inferior de valores). Tenha isso em mente ao
recorrer ao autocast: prefira, se possvel, fazer voc mesmo o typecast, pois isso ir garantir que voc sabe qual ser o
tipo resultante.
Dito isso, vamos para o prximo conjunto de operadores.

Operadores aritmticos
J falamos sobre eles antes: so operadores que realizam contas matemticas simples entre dois operandos (valores),
devolvendo um deles como valor da expresso aritmtica (em C, uma expresso aritmtica tem o mesmo significado
que na matemtica, e o termo expresso extrapolado a partir da como a representao de uma operao lgicomatemtica que ser resolvida em um determinado momento). Esse valor pode ser usado em uma atribuio ou em
qualquer lugar onde exija-se um valor. Isso interessante pois pode-se executar operaes matemticas, por exemplo,
antes de operaes lgicas, obviamente respeitando as prioridades determinadas pelo C (veremos abaixo as prioridades e
como alterar a ordem de execuo das operaes).
Basicamente, as operaes matemticas so:

Operador

Operao

Prioridade

Multiplicao

Diviso

Resto da Diviso Inteira (mod)

Subtrao

Soma

Na coluna Prioridade, fazemos uma referncia a como o C prioriza as operaes aritmticas entre si. No caso, primeiro o C
executa divises, multiplicaes e resto de divises conforme apaream da esquerda para direita (como feito na
matemtica). Em seguida, o C executa somas e subtraes (tambm da esquerda para a direita, conforme a

matemtica). Existem formas de alterar a prioridade da execuo de frmulas complexas, que veremos mais abaixo. Mas,
basicamente, essas so as operaes matemticas possveis de serem feitas com o C. Uma ressalva: diferentemente de
outras linguagens de programao, C no possui um operador matemtico para potenciao.
Em C, a diviso palco de uma controversa. Normalmente em C, feita diviso inteira entre valores se ambos forem do
tipo i t mesmo que a atribuio do resultado seja feita para uma varivel de valor flutuante. Isso deve-se ao fato de
n,
que o C considera dois momentos: (1) quando a operao de diviso feita e (2) quando o valor resultante atribuido
varivel de ponto flutuante. Porm, se no momento da execuo da diviso, um dos valores for de tipo flutuante (seja
por ser uma varavel f o tou por um cast), o C ir pegar o termo restante, forar o cast (autocasting) do mesmo para
la
um tipo flutuante e retornar o resultado da operao com um valor de tipo flutuante. Isso algo a manter-se em
mente.
O operando % (resto da diviso inteira) um caso a parte: ele vai transformar ambos os termos em inteiros, uma vez
que o conceito de resto s existe em matemtica quando falamos de nmero inteiros (mesmo nos casos de dzimas
peridicas ou no peridicas, a matemtica parte do princpio que, cedo ou tarde podemos descobrir o ltimo dgito de
um valor). Essa converso feita truncando-se o valor decimal do nmero com tipo flutuante, tornando-o inteiro.
Porm, isso pode provocar inexatido em resultados, podendo afetar os resultados finais do programa. Para isso, o C
oferece uma biblioteca especializada em funes matemticas, m t . , sobre a qual ainda iremos falar de maneira mais
ahh
aprofundada.
Bem, dito isso, vamos seguir falando dos operadores. No caso, vamos falar dos operadores relacionais.

Operadores relacionais
O nome meio que diz tudo: operadores relacionais envolvem a relao entre dois valores especficos de maneira lgica,
comparando-os entre si e vendo se a relao em questo verdadeira ou no. Basicamente eles devolvem 1 caso a
relao seja verdadeira, e 0 caso a mesma seja falsa. (Lembrando que, como vimos na nossa ltima aula no existe no C
um tipo booleano, ento ele adota 0 (string vazia) ou o smbolo n l como convenes para falso e quaisquer
,
ul
outros valores como verdadeiro). Para ter uma noo do funcionamento do mesmo, rode o seguinte programa (retirado
do material do Curso de C da UFMG):

#nld <ti.>
icue sdoh
itmi(
n an)
{
iti j
n , ;
pit(\Etecmdi nmrsitio:";
rnf"nnr o os ueo ners )
saf"dd,&,&)
cn(%%" i j;
pit(\% = % %\" i j i=)
rnf"nd = d
dn, , , =j;
pit(\% ! % %\" i j i=)
rnf"nd = d
dn, , , !j;
pit(\% < % %\" i j i=)
rnf"nd = d
dn, , , <j;
pit(\% > % %\" i j i=)
rnf"nd = d
dn, , , >j;
pit(\% <% %\" i j ij;
rnf"nd
d
dn, , , <)
pit(\% >% %\" i j ij;
rnf"nd
d
dn, , , >)
rtr()
eun0;
}

No existe muito a ser dito sobre novos comandos como fizemos em outros casos. O importante aqui estudar o

comportamento dos operadores relacionais, que estamos listando abaixo, j indicando tambm sua prioridade entre si:

Operador
>

Operao

Prioridade

Maior do que

Maior ou igual a

<

Menor do que

<=

Menor ou igual a

==

Igual a

Diferente de

>=

!=

Aqui voltaremos a enfatizar:

NO EXISTE TIPO BOOLEANO EM C! EM C, QUALQUER VALOR DIFERENTE DE 0 OU DA STRING


VAZIA OU DE NULL CONSIDERADO VERDADEIRO!

Alm disso, vamos reenfatizar outra coisa: nunca confunda o operador relacional de igualdade (= ) com o de atribuio
=
(= Isso ir provocar erros de lgica de aplicao que so de difcil depurao.
).
Pois bem, esses trs primeiros tpicos, falando de operadores de atribuio, aritmticos e relacionais so apenas uma
reviso do que vimos anteriormente. A partir de agora iremos ver operadores que vimos por alto ou no vimos
anteriormente, a comear pelos

Operadores lgicos
Operadores lgicos so basicamente operadores que realizam comparaes. De certa forma, so um subgrupo dos
operadores relacionais, mas que trabalham apenas com valores segundo a lgica booleana. De uma forma rpida,
podemos dizer que eles podem comparar se dois valores so verdadeiros ao mesmo tempo (E/AND), se pelo menos um
entre dois valores pode ser considerado verdadeiro (OU/OR) e se um determinado valor falso naquele momento
(NO/NOT). A vantagem de usar-se operadores lgicos que, combinando-os com os operadores relacionais, podemos
construir condies complexas, em especial em situaes de controle de fluxo. Por exemplo, vamos relembrar um trecho
de cdigo de nossa ltima aula:
d
o
{
pit(Qatstnaia vc ah qepeiapr dsorree ;
rnfuna ettvs o ca u rcs aa ecbi l? )
safd,mxmTnaia)
cn(%&aioettvs;

i (aioettvs1
f mxmTnaia<)
pit(Vc peiatna a mnsuavz
rnfo rcs etr o eo m e!

\;
n)

i (aioettvslmtTnaia)
f mxmTnaia>iieettvs
pit(% tnaia?T qeed dmi tm!
rnfd ettvs urno eas abm

\mxmTnaia)
n,aioettvs;

}wie(aioettvs1| mxmTnaia>iieettvs;
hl mxmTnaia< | aioettvslmtTnaia)
Relembrando: o d h l executa enquanto a condio indicada no final do bloco de cdigo for considerada verdadeira.
owie

No caso, nossa inteno foi garantir que o usurio no entrasse com nenhum nmero negativo e nem fosse alm de um
limite de tentativas estipulado previamente. Para isso, utilizamos duas condies relacionais (m x m T n a i a < e
aioettvs1
m x m T n a i a > i i e e t t v s cada uma atuando em uma das situaes que determinamos. Para unirmos
a i o e t t v s l m t T n a i a ),
as duas situaes, usamos o operador lgico OU (| em C) que indica que, enquanto pelo menos uma dessas condies
|
relacionais for verdadeira, a condio lgica ser verdadeira e, portanto, o programa permanecer executando esse bloco
de cdigo.
C implementa os principais operadores relacionais, que so o E (AND), OU (OR), e NO (NOT), conforme representado
abaixo:
Operador

Operao

Prioridade

NO Lgico

&
&

E Lgico

|
|

OU Lgico

O programa abaixo, tambm retirado do material do Curso de C da UFMG poder ilustrar melhor o comportamento de E,
OU e NO:

#nld <ti.><r/itmi(<r/{b /
icue sdoh b >n an)b ><r >

iti j<r/
n , ;b >

pit(ifredi
rnf"nom os

Na verdade, com o tempo esse conceito ficar bem arraigado na cabea. Ento vamos para os prximos operadores.
Antes, um parnteses: em vrios momentos, e em especial quando falamos dos tipos de dados em C, mencionamos que
em C, as strings de caracteres possuem um comportamento diferenciado em relao ao que vemos em outras linguagens
de programao. Por isso, voc NO DEVE utilizar os operadores lgicos e relacionais apresentados anteriormente com
strings. Isso ir provocar certamente resultados esprios. Para comparaes envolvendo strings, a biblioteca padro
s r n . oferece uma srie de funes teis, sobre as quais falaremos no futuro.
tigh

Operadores de bit (bitwise)


O C oferece um conjunto de operadores lgicos voltados exclusivamente para o acesso direto aos bits. Mais
exatamente, para realizar operaes lgicas com bits. Chamamos esses operadores de operadores de bit, bit-a-bit ou
bitwise, dependendo do autor do livro.
C foi uma linguagem originalmente projetada para facilitar o desenvolvimento de aplicaes em baixo nvel, como sistemas
operacionais, firmware (software de baixo nvel para dispositivos eletrnicos, como celulares e players de DVD),
compiladores, com o objetivo de substituir o Assembler e oferecer um mnimo de portabilidade de cdigo, ainda que
mantendo os benefcios do Assembler de acesso direto ao hardware. Como o software de um appliance, o firmware, se
comunica com o hardware por pulsos eletrnicos interpretados pela CPU como bits 0s e 1s especficos em determinados
locais de memria, interessante que C possua comandos capazes de trabalhar com esses bits, que chamamos em
programao de flags. Para isso, utiliza-se os operadores de bit.
Os operadores de bit lembram um pouco os operadores aritmticos e um pouco os operadores lgicos, pois eles iro
realizar a operao lgica em cada bit de um determinado nmero, usando outro, e retornando um terceiro. Por

exemplo, imaginemos que precisamos obter de um conjunto de flags (normalmente representados por um int) se o 5
bit dessa conjunto de flags est ativo. Uma forma aplicar o bitwise AND (E bit-a-bit) contra o nmero 8 (representado
00001000). Esse valor (que alguns autores chamam de bitmask mscara de bits) atua de tal forma que zera todos os
outros bits do i tem questo: lembrando que ambos os bit tem que ser 1 verdadeiro para que o bit na sada seja
n
1. Como os demais bits no bitmask so 0, o valor de tais bits zerado. Se o bit na flag estiver zerado, o resultado ser 0,
caso contrrio o valor de sada ser 8.
Existem tambm operadores de movimentao de bits: algumas operaes matemticas e lgicas so resolvidas
facilmente quando utilizamos essa movimentao de bits, empurrando os bits esquerda ou direita, com isso
aumentando e diminuindo seus valores. Como exemplo: uma forma rpida e suja de fazer-se a potncia de 2 em um
nmero empurrando seus bits uma casa para a direita. Como em numerao binria um bit sempre uma potncia
de 2 acima do bit direita ao seu, ao empurrar-se os bits direita temos o efeito da potncia de 2. (PS: esse mtodo
no faz as checagens matemticas de casos especiais como potncias de 0 e 1 e nem verificam estouros do tamanho de
varivel). Ao empurrar-se os bits, o ltimo bit na direo para a qual os bits foram empurrados eliminado e o primeiro
bit da direo oposta preenchido com 0.
Os operadores de bit so:

Operador
!

Operao

Prioridade

NO por bit a bit

<<

Deslocamento de bits esquerda

>>

Deslocamento de bits direita

&

E bit a bit

OU Exclusivo (um e apena um bit)

OU bit a bit

Uma coisa a salientar de diferente entre os operadores bit-a-bit e os operadores lgicos a existncia do OU-Exclusivo
(eXclusive OR, XOR) bit-a-bit. Nessa situao, ao comparar-se os dois nmeros bit a bit, um bit na sada s ser um se UM
E APENAS UM dos bits comparados for 1. Caso contrrio (ambos 0 ou ambos 1) o bit ser zero.
Bem, esse um tema complexo e pouco til para ns. Vamos falar agora de

Operadores Compostos
Operadores Compostos englobam operadores que atuam ao mesmo tempo como dois tipos diferentes de operadores,
em geral uma operao bit-a-bit ou aritmtica e uma atribuio. Alm desses, existe o operador de atribuio condicional
?.
Vamos englobar em trs subgrupos os operadores compostos: os de atribuio aditiva, os de incremento e
decremento em um e o de atribuio condicional.
Os de atribuio aditiva so operadores que realizam uma determinada operao envolvendo um parmetro com uma
varivel e atribuem mesma varivel o resultado da operao. Por exemplo, a expresso a = equivalente em termos
+4
de programao a a a 4 Esses operadores existem para ambientes onde o espao para a leitura do cdigo fonte
=+.
pequeno e determinadas redundncias de digitao comprometeriam a compilao. Alguns autores consideram que o
uso desse tipo de operador torna o programa mais legvel, enquanto outros defendem que esse tipo de operador
provoca a ofuscao do cdigo (ou seja, torna a leitura do cdigo complexa). A lista dos operandos compostos segue
abaixo:

Operador

Operao

Prioridade

/=

Diviso com atribuio

*=

Multiplicao com atribuio

%=

Resto da diviso inteira com atribuio

+=

Soma com atribuio

-=

Subtrao com atribuio

<<

Deslocamento de bits esquerda com atribuio

>>

Deslocamento de bits direita com atribuio

&

E bit a bit com atribuio

OU Exclusivo (um e apena um bit) com atribuio

OU bit a bit com atribuio

Esse um subtipo complexo, e o prximo tambm demanda ateno, que so os operadores de incremento e
decremento em um. Na verdade, esses operadores so indicados por + e e indicam que a varivel onde eles esto
+
sendo usados ter seu valor somado ou subtrado em 1. Porm, existe um comportamento que deve ser levado em
conta ao usar-se tais operadores, que o de prefixao e ps-fixao. Para usar-se os operadores desse tipo, voc pode
colocar o operador antes ou depois da varavel. A posio onde o colocar, porm, ir afetar o comportamento sobre qual
ser o valor realmente usado.
Ao prefixar o operador antes da varivel (ou seja, colocar + ou antes da varivel), o valor ser modificando antes de
+
qualquer uso. Por exemplo, se voc usar a + b e bfor 4, acontecer o seguinte: (1) bser incrementada em 1, para 5
=+,
e (2) areceber o novo valor de b 5. Ao ps-fixar o operador, acontecer o contrrio: o valor ser usada para qualquer
,
outro fim antes de ser modificado. Mantendo o exemplo anterior, imagine que voc, ao invs de usar a + b utilizou
=+,
a b + Nesse caso (1) a receber o valor atual de b, 4 e depois que (2) b ser incrementado em 1. Essa diferena
=+.
importante de ter-se em mente, pois ela pode provocar bugs (em especial quando utiliza-se esse operador em conjutnto
com comparaes relacionais), mas uma ferramenta muito poderosa.
Por exemplo: nas brincadeiras da nossa ltima aula sugeri:

Da mesma forma que existe o operador + , existe o operador que subtrai um da varivel que o
+
,
antecede. Considerando isso e o funcionamento do lao f r tente reconstruir o lao para no ter
o,
uma condio no sentido exato da palavra. Dica: lembre-se que C trata 0como falso;

A forma para fazer isso lembrar que o f r na comparao, exige um booleano verdadeiro (ou seja, qualquer valor
o,
diferente de 0). Portanto, se usarmos o operador de decremento em 1 ps-fixado, podemos fazer com que o nmero
de tentativas v se reduzindo at que, na ltima tentativa, ele chegue na condio em 0 e, sendo falso, saia do lao.
Portanto, basta substituir a linha:

fr(=;i=aioettvsi+
o i1 <mxmTnaia;+)

no programa da nossa ltima aula por:

fr(=;mxmTnaia i+
o i1 aioettvs; +)

e teremos feito a mgica. Na realidade, se no usarmos a varivel i para exibir o nmero da tentativa, podemos
simplesmente reduzir o comando para:

fr( mxmTnaia)
o ; aioettvs;

e o cdigo ter o mesmo comportamento, sem precisar de uma varivel de controle.


Mas, ATENO: isso s funcionar bem se voc usar o decremento ps-fixado. Se tentar prefixar o decremento, ele
ir, na prtica, tirar uma tentativa do usurio, ao ponto de, se o usurio pedir apenas uma tentativa o sistema nem pedir
o nmero: no caso, ele ir subtrair 1 de m x m T n a i a (que ser 1), reduzindo-o para 0 e tornando a condio
aioettvs
falsa.
Bem, aqui podemos dizer que terminamos de falar do incremento e decremento de um. Vamos falar, para acabar com os
atributos compostos, da atribuio condicional.
Chamamos de atribuio condicional um operador que funciona como o seguinte tipo de lgica:

i (odco
f cnia)
vrx
a=;
es
le
vry
a=;

O C oferece um operador que permite-nos fazer esse tipo de condio diretamente na atribuio, o operador ? Esse
.
um operador ternrio (ou seja, exige trs operandos), sendo composto por < o d c o ? a r v r a e r >
cnia><t_eddio:
< t _ a s > Nesse caso, caso condicao seja verdadeira (diferente de 0), o operador ir devolver a r v r a e r ,
arflo.
t_eddio
enquanto que, caso condicao seja falsa (igual a zero), ele ir devolver o valor de a r f l o Por exemplo, imagine que
t_as.
voc ir precisa do mdulo de um nmero, ou seja, do valor sem sinal do mesmo. Ao invs de construir um if s para
isso, voc pode usar uma atribuio condicional:

mdl=nmr<)nmr*1nmr;
ouo(ueo0?ueo-:ueo

O que quer dizer que m d l ir receber o valor de n m r vezes -1 caso n m r seja menor que 0; caso contrrio, ele
ouo
ueo
ueo
ir receber o valor normal de n m r .
ueo

Bsico, Operadores

Controle de Fluxo e Operandos Lgicos


Ol!
Vamos continuar ento a programar em C.
Os dois primeiros programas, Hello World e o de Entrada de Dados, tinham como ponto comum o fato de serem, vamos
dizer assim, diretos. Eles eram executados, realizavam cada instruo em sequencia e se encerravam.
Isso em si no ruim: muitos programas simples e interessantes podem ser feitos assim. Porm, perceptvel que eles
no possuem nenhuma inteligncia: eles no conseguem executar instrues dependendo das entradas que lhe so
oferecidas. Isso se deve ao fato de eles no possurem nenhum controle de fluxo. Em programao, esse termo
adotado para definir situaes onde o programa pode mudar a sequencia de execuo do programa conforme condies
estipuladas no momento do desenvolvimento da aplicao. Por exemplo: no programa de Entrada de Dados, poderamos
estipular valores limite para p i e r e s g n oque no os limites do tipo i t
rmio eud
n.
Pois bem, veremos agora um programa com um pouco mais de inteligncia, pois iremos falar mais sobre os comandos
de controle de fluxo em C e sobre os operadores lgicos, alm de questes sobre o tipo booleano (ou melhor, sobre a
ausncia de um tipo booleano) em C.
No caso, vamos escrever um outro programa comum, o adivinhe o nmero. Mas vamos adicionar alguma inteligncia a
ele e tornar ele um pouco mais desafiador:

#nld <ti.>
icue sdoh
#nld <tlbh
icue sdi.>
#nld <ieh
icue tm.>
/ Dfnnoonmr lmt pr apso tna dsorr*
* eiid
eo iie aa
esa etr ecbi /
#eieLMT 1
dfn IIE 0
#eie
dfn

ECRA
NER

pit(Pesoe
rnfrsin

qaqe
ulur

tca
el

pr
aa

cniur\;
otna!n)

wie!eca()
hl(gthr);
itmi (n ag,ca* ag)
n an it rc hr* rv
{
cntitlmtTnaia=LMT/)1
os n iieettvs(IIE2-;
itnmrPnao0 nmrDUuro0 mxmTnaia=,;
n ueoesd=, ueoosai=, aioettvs0i

sadtm(UL) / Sre pr mdfcr a tbl d nmrs pedrn(ieNL); / ev


aa oiia
aea e eo
suo
aetro
lais

nmrPnaorn(%IIE1
ueoesd=ad)LMT+;
pit(O! Pne e u nmr ete 1 e % e t dsfo a ahr ee\
rnfK
esi m m eo nr
d
e eai
ca
l!n,
LMT)
IIE;
d
o
{
pit(Qatstnaia vc ah qepeiapr dsorree ;
rnfuna ettvs o ca u rcs aa ecbi l? )
safd,mxmTnaia)
cn(%&aioettvs;
i (aioettvs1
f mxmTnaia<)
pit(Vc peiatna a mnsuavz :\;
rnfo rcs etr o eo m e! Pn)
i (aioettvslmtTnaia)
f mxmTnaia>iieettvs
pit(%
rnfd

tnaia?
ettvs

qeed
urno

dmi
eas

tm!
abm

:\mxmTnaia)
Pn,aioettvs;
}wie(aioettvs1| mxmTnaia>iieettvs;
hl mxmTnaia< | aioettvslmtTnaia)
fr(=;i=aioettvsi+
o i1 <mxmTnaia;+)
{
pit(Vmsl!%a tnaia i;
rnfao d. ettv! ,)

safd,nmrDUuro;
cn(%&ueoosai)
i (ueoosai=nmrPnao
f nmrDUuro=ueoesd)
{
pit(Prbn!Vc aetu\;
rnfaas o cro!n)
ECRA
NER
rtr()
eun0;
}
es
le
{
pit(Nopne e %. nmrDUuro;
rnf esi m d , ueoosai)
i (ueoosai<ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mir\;
rnfesi m m eo ao.n)
i (ueoosai>ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mnr\;
rnfesi m m eo eo.n)
}
}
pit(Qe pn! E pne e %! Mlo sre d pia vz
rnfu
ea
u esi m d
ehr ot
a rxm
e!
\nmrPnao;
n,ueoesd)
ECRA
NER
rtr()
eun0;
}

Como de costume, iremos ignorar algumas coisas que j vimos anteriormente. Portanto, se voc ficar com alguma
dvida, d uma relida nos posts anteriores para fixar bem o contedo que j lidamos. No caso, vamos falar sobre os
trechos de cdigo que esto com destaque colorido no fonte cima.

# e i ee os nmeros mgicos
dfn
Logo de cara inclumos duas novas bibliotecas em C: s d i . STanDard Library (biblioteca padro), que contm uma
tlbh
srie de funes cotidianas em C e t m . , que contem funes para lidar com tempo. Dessas, a s d i . em
ieh
tlbh
especial muito importante, pois uma grande quantidade de comandos C muito usados est includa nela. No caso,
estamos muito interessados nas funes relacionadas gerao de nmeros randmicos, mas existe muito mais nela e no
futuro falaremos muito sobre essa biblioteca.
Logo aps, vemos dois novos comandos do pr-processador (ou macros, como so chamados tecnicamente):

#eieLMT 1
dfn IIE 0
#eie
dfn

ECRA
NER

pit(Pesoe
rnfrsin

qaqe
ulur

tca
el

pr
aa

cniur\;
otna!n)

wie!eca()
hl(gthr);

A macro # e i epermite ao programador definir um smbolo que o pr-processador ir substituir por uma informao
dfn
qualquer no momento da compilao. No caso, definimos dois smbolos:
LIMITE um nmero com valor 10;
ENCERRA uma sequencia de comandos C;
No caso, voc pode se perguntar sobre a utilidade de definir-se smbolos. Esse sistema de smbolos permite:
1. Criar comandos simples (caso do ENCERRA);
2. Criar situaes de compilao condicional (um tpico mais avanado, veremos no futuro);
3. Evitar os chamados nmeros mgicos;
Nmeros mgicos?! Virou Hogwarts agora?
Na verdade no. Em programao, chamamos de nmeros mgicos determinados nmeros ou expresses que so
colocadas no cdigo para realizar determinadas funes. No nosso caso, precisamos limitar o maior nmero no qual nosso
programa ir pensar. Poderamos simplesmente escrever ele no nosso programa de maneira direta, mas como esse
nmero iria se repetir vrias vezes em vrios trechos de cdigo aparentemente sem relao um com o outro, com
certeza a leitura e anlise de erros do programa (que em programao chamada de depurao) seria complicada. Por
isso chamamos tais nmeros de nmeros mgicos: uma vez que ele entra, temos dificuldades para entender o que eles
fazem.
No caso, ao criarmos um smbolo (LIMITE), podemos usar ele no nosso cdigo que o prprio compilador (atravs do prprocessador) ir substituir todas as entradas do smbolo LIMITE pelo valor desejado (no caso, 10). Isso torna o programa
mais legvel e mais simples de ser modificado.
O mesmo vale para ENCERRA. Com ENCERRA, criamos um pseudo-comando, um smbolo que ser substitudo por uma
srie de comandos C. No caso, o comando escrito em ENCERRA ajuda alguns usurios de IDEs (em especial no Windows)
a visualizarem os resultados dos seus programas.
Existe mais sobre # e i eque ainda iremos ver, em especial na questo dos pseudo-comandos, mas esse bsico deve
dfn

ajudar a compreender melhor as coisas at termos uma oportunidade de nos aprofundarmos nele (em especial, quando
falarmos no futuro de outras macros de pr-processador, quando revisitaremos # n l d e # e i e
i c u e d f n ).

Variveis Constantes:
Aps as macros temos uma declarao de variveis: logo de cara, temos uma que um pouco diferente do que o que
vimos quando falamos dos tipos de variveis e como as declaramos:

cntitlmtTnaia=LMT/)1
os n iieettvs(IIE2-;

A principal diferena est na palavra reservada c n tantes da declarao do tipo i tda varivel l m t T n a i a . O
os
n
iieettvs
que essa palavra faz indicar ao compilador que essa varivel na verdade uma constante do tipo desejado, portanto
no poder mais ser modificada uma vez que seja inicializada, o que acontece em seguida. No caso, a varivel constante
l m t T n a i a tem seu valor inicializado com o resultado de ( I I E 2 - , ou seja, na diviso do valor do nosso
iieettvs
LMT/)1
smbolo LIMITE por 2, menos 1 (no caso atual, o valor final 4).
Voc deve estar se perguntando: usar um smbolo com # e i eno seria mais interessante?
dfn
Bem, nesse caso no. Perceba que o limite de tentativas determinado baseando-se no valor de LIMITE, e o # e i e
dfn
no realiza per se contas ou qualquer processamento: tudo o que ele faz dizer que (1) existe um smbolo e (2) qual
seu valor. Se modificarmos o valor de LIMITE, teramos que modificar o valor desse novo # e i emanualmente.
dfn
Voc pode pensar ento: por que no tornar a frmula um smbolo via # e i e A ideia parece boa, mas ela cai em
d f n ?.
um problema: quando voc substituir o smbolo, voc obrigaria o sistema a repetir vrias vezes uma determinada conta.
Podemos pensar em desktops com gigas de poder de processamento e isso aparentemente ser uma boa ideia, mas
pense em um sistema embarcado e voc ver que uma situao como essa iria provocar reduo na velocidade do
programa.
J com a varivel constante, temos uma situao onde a varivel no poder ser modificada mas ainda assim ser
calculada apenas uma vez, apenas exigindo uma recompilao no caso de uma mudana de LIMITE (o que iria acontecer
de qualquer maneira), pois ao compilar, LIMITE seria substitudo pelo seu valor.
Eu tenho que OBRIGATORIAMENTE inicializar uma varivel constante no momento em que a declaro?, voc deve estar
se perguntando. Na realidade, da mesma forma que ocorre com as variveis comuns, as variveis constantes PODEM ser
inicializadas aps a declarao. O que impedido que uma varivel constante receba OUTROS VALORES aps a
inicializao. Como constantes so normalmente usadas para delimitar de alguma forma o comportamento do programa
(no nosso caso, utilizaremos l m t T n a i a para delimitar o mximo de tentativas que uma pessoa ter de
iieettvs
adivinhar o nmero pensado pelo computador), uma boa prtica inicializ-la antes de qualquer entrada de dados.
Bem, acho que fechamos aqui a questo das variveis constantes, exceo de uma coisa: por que logo abaixo vem
uma segunda linha de declaraes i t
n?
Quando voc coloca vrias declaraes e/ou inicializaes de variveis em uma mesma linha, o compilador ir entender
que todas elas tem o mesmo comportamento. Se colocssemos as i tem questo junto com a declarao c n t i t
n
os n
de limiteTentativas, essas variveis tambm seriam declaradas constantes, o que no o comportamento desejado.
Nesse caso, a regra clara: declaraes de constantes devem ser separadas das declaraes de variveis comuns.
Bem, agora que vimos o bastante sobre constantes para entendermos o programa, sigamos em frente.

r n ( , s a d )e o gerador de nmeros pseudo-aleatrios:


ad) rn(
Vamos dar uma olhada nesse bloco de cdigo:

sadtm(UL) / Srepr mdfcratbl d nmrsped-lais


rn(ieNL); / ev aa oiia
aea e eo suoaetro
nmrPnaorn(%IIE1
ueoesd=ad)LMT+;

A primeira linha utiliza duas funes: a primeira s a d ) da s d i . , que recebe um l n i tcomo entrada. Essa
rn(,
tlbh
og n
funo serve para modificar a tabela de nmeros pseudo-aleatrios do sistema, usando o l n i t de entrada como
og n
parmetro para essa modificao. No caso, usamos a funo t m ( U L , da biblioteca t m . , que devolve o horrio
ieNL)
ieh
do sistema atual em UNIX TimeStamp (o nmero de segundos passados desde as 0:00 do dia 01/01/1970 at o
momento atual). Na verdade, ela retorna o epoch time (outra forma pela qual pode ser chamado o UNIX TimeStamp)
para qualquer data passada de maneira correta (no entraremos nesse detalhe aqui).
Por que chamamos os nmeros do sistema de pseudo-aleatrios?
Porque o computador no consegue, pela prpria natureza lgica, gerar um padro 100% aleatrio. O que pode ser
feito utilizar-se algoritmos que gerem nmeros, considerando-se a probabilidade de sua ocorrncia em sequencia, que
se aproximem da aleatoriedade e montar uma tabela com ela. O problema que, ao se pegar um nmero nessa tabela
que, caso ela no seja inicializada de maneiras diferentes entre cada execuo, o valor deixar de ser aleatrio, ainda
que continue sendo no-determinstico (ou seja, voc no consegue, a partir das sequencias de nmeros, determinar
com preciso as frmulas matemticas que as geraram). Para evitar esse problema, utilizamos um comando, o s a d )
rn(,
que ir modificar a semente da tabela de nmeros pseudo-aleatrios, o que ir garantir que, a cada execuo, como o
Timestamp ser diferente, a semente da tabela ser diferente e, por sua vez, a sada da mesma ser diferente (se quiser
saber mais sobre a idia de gerao de nmeros pseudo-aleatrios, d uma olhada nesse artigo da Wikipedia). De
qualquer modo, sabemos que o s a d )ir garantir que os nmeros aleatrios tenham esse comportamento o mais
rn(
prximo possvel da realidade.
Na linha seguinte, utilizamos r n ( para obter um nmero aleatrio. Porm, esse comando no nos permite definir qual
ad)
o valor mximo a ser obtido, normalmente obtendo qualquer valor entre 0 e uma definio chamada R N _ A
ADMX
(normalmente o limite de um u s g e i t A opo padro obter o resto da diviso inteira entre o maior nmero
n i n d n ).
que desejamos e o valor retornado. Nesse caso, obtemos um nmero que vai de 0 ao limite estipulado menos 1. No
nosso caso, queremos um nmero entre 1 e o L M T estipulado, por isso adicionamos 1 ao valor obtido (lembre-se da
IIE
prioridade de execuo da operao de resto em relao soma).
Bem, acho que j falamos demais sobre r n ( , at porque no tem como aprofundar nesse caso sem entrar uma
ad)
teoria extremamente complexa e totalmente fora do nosso escopo atual (e muito provavelmente futuro). Tudo que
preciso saber que precisaos usar s a d ) antes de r n ( para garantir o comportamento aleatrio e que
rn(
ad)
precisamos utilizar a operao resto para limitar o valor de r n ( .
ad)

Lao de repetio condicional d { } w i e e operaes relacionais e


o
hl
lgicas:
Em seguida, vemos que ele ir apresentar o desafio ao usurio e entrar no bloco de cdigo abaixo:

d
o
{
pit(Qatstnaia vc ah qepeiapr dsorree ;
rnfuna ettvs o ca u rcs aa ecbi l? )
safd,mxmTnaia)
cn(%&aioettvs;
i (aioettvs1
f mxmTnaia<)
pit(Vc peiatna a mnsuavz :\;
rnfo rcs etr o eo m e! Pn)
i (aioettvslmtTnaia)
f mxmTnaia>iieettvs
pit(%
rnfd

tnaia?
ettvs

qeed
urno

dmi
eas

tm!
abm

:\mxmTnaia)
Pn,aioettvs;
}wie(aioettvs1| mxmTnaia>iieettvs;
hl mxmTnaia< | aioettvslmtTnaia)

No nosso caso, nos focaremos nos comandos em ciano. Eles representam um tipo de controle de fluxo que chamamos
de repetio (ou iterao, em alguns lugares) condicional. O nome pomposo apenas quer dizer que o lao em questo
vai ser executado enquanto uma determinada condio for cumprida. No nosso caso, esse comando funciona assim:

d
o
{
<>
}wie(cnia>;
hl <odco)

O que fazemos aqui indicar que, enquanto a c n i a estipulada for verdadeira, o programa continuar no lao.
odco
Mas como construir uma condio lgica? Para isso, C nos fornece os operadores lgicos, que comparam valores e
retornam verdadeiro ou falso conforme a situao.
Aqui cabe um parenteses antes de entrarmos nos operadores: o C no possui um tipo booleano (ou seja, verdadeiro ou
falso) especfico. Para o C, um valor 0 N L (que definido por padro como 0 ou (string vazia) como falso e
, UL
)

qualquer outro valor como verdadeiro. Isso importante pois existe um bug em C muito comum que iremos discutir
adiante.
Bem, dito isso, os operadores lgicos em C padro so:

Operador

Ao

>

Maior do que

>
=

Maior ou igual a

<

Menor do que

<
=

Menor ou igual a

=
=

Igual a

!
=
&
&

Diferente de
E lgico

|
|

OU lgico

NO lgico

(fonte: curso de C da UFMG)


Os operadores lgicos so escritos de maneira similar aos matemticos, sempre comparando um valor a outro. No caso,
por exemplo, do comando acima, temos trs expresses:
primeiro, uma comparao m x m T n a i a < ;
aioettvs1
depois, uma comparao m x m T n a i a > i i e e t t v s
aioettvslmtTnaia;
e por fim, uma operao lgica ou entre os dois;
Como isso resolvido no C? Primeiro, o sistema ir analisar se o m x m T n a i a < . Caso seja, ele nem ir fazer a
aioettvs1
segunda comparao m x m T n a i a > i i e e t t v s pois o fato de a comparao lgica ser OU vai fazer o
aioettvslmtTnaia,
programa aceitar como verdadeira a condio e continuar no lao (chama-se a isso short-circuit logical analysis anlise
lgica de curto-circuito, e parte do princpio que uma vez que tenhamos garantido que uma condio sabidamente
verdadeira ou falsa, no h necessidade de continuar-se processando a lgica em questo). Caso contrrio, ele ir tentar
a segunda comparao, m x m T n a i a > i i e e t t v s para verificar se ela verdadeira ou falsa e, portanto,
aioettvslmtTnaia,
a condio o ser.
Parece meio confuso, mas s pensar com calma que voc ir entender. Procure imaginar valores para
m x m T n a i a e pense em qual ser a resposta (lembre-se: no caso atual, l m t T n a i a 4).
aioettvs
iieettvs
O que acontecer se o valor no fizer nenhuma das duas condies estipuladas ser verdadeira? Simples: a expresso
como um todo ser falsa (0) e o lao d h l ir se encerrar normalmente, com o programa seguindo adiante. Ou
owie
seja, somente quanto o usurio entrar com um nmero mximo de tentativas maior que 1 e menor ou igual que o limite
de tentativas estipulado (no caso, 4), o lao ser interrompido.
Temos uns i s engraadinhos dentro desse lao, mas no falaremos sobre ele agora. Vamos ento seguir em frente.
f
Para fecharmos esse tpico, existe uma verso do d h l chamada simplesmente w i e Ela representada assim:
owie
hl.

wie<odco)
hl(cnia>
{
<>

e sua nica diferena em relao ao seu irmo que, caso ao chegar na entrada do lao a condio for satisfeita, o
sistema sequer ir entrar no bloco de cdigo em questo. O d h l ir executar ao menos uma vez, uma vez que a
owie
condio testada no momento da sada do lao, diferentemente do w i e que executada quando da entrada no
hl,
lao.

Iteraes o comando f r
o:
Aps nosso usurio ter determinado quantas tentativas ele quer ter para acertar o nmero mgico, vamos ento dar a
chance a ele.

fr(=;i=aioettvsi+
o i1 <mxmTnaia;+)
{
pit(Vmsl!%a tnaia i;
rnfao d. ettv! ,)

safd,nmrDUuro;
cn(%&ueoosai)
i (ueoosai=nmrPnao
f nmrDUuro=ueoesd)
{
pit(Prbn!Vc aetu\;
rnfaas o cro!n)
ECRA
NER
rtr()
eun0;
}
es
le
{
pit(Nopne e %. nmrDUuro;
rnf esi m d , ueoosai)
i (ueoosai<ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mir\;
rnfesi m m eo ao.n)
i (ueoosai>ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mnr\;
rnfesi m m eo eo.n)
}
}

Aqui temos um nmero delimitado de tentativas por mximo de tentativas. Desse modo, podemos utilizar um outro
comando de lao, o f r que um outro caso de lao de repetio ou iterao. Diferentemente do d h l , porm,
o,
owie
o f r uma iterao no-condicional. Na verdade, ele condicional, mas ele mais usado em situaes na qual se
o
espera que ele se execute um determinado nmero de vezes (pode-se usar ele at como um w i ediferente, mas no
hl
boa prtica e no falaremos sobre isso aqui).
O for tem como estrutura a seguinte:
fr(iiilzco;<odco;<trco)
o <ncaiaa> cnia> ieaa>
{
<>

}
No caso, ele um pouquinho complexo, portanto vamos dar uma olhada no seu comportamento:
no momento em que o programa chega no f r a primeira coisa que feita executar os comandos em
o,
< n c a i a a > No nosso caso, ele inicializa a varivel iem 1. Essa inicializao pode ser feita como desejado,
iiilzco.
mas restringindo-se a um comando (ou bloco de cdigo);
Em seguida, o bloco de cdigo do for ser executado;
Ao terminar de executar-se o bloco de cdigo do f r ele executa o comando que est em < t r c o . No
o,
ieaa>
nosso caso, utilizamos um operador matemtico + , que utilizado em C para adicionar-se um ao valor da varivel
+
ao qual ela sucede. Na verdade ele se comporta de uma maneira mais complexa, mas para o momento basta
entender que i +seria o equivalente a i i 1
+
=+;
Depois de executar a < t r c o , o sistema ir verificar se a < o d c o colocada verdadeira. Caso o seja, ele
ieaa>
cnia>
ir interromper o lao f r da mesma forma que o w i e caso contrrio ele entrar e executar uma nova
o
hl,

iterao. No nosso caso, testamos se i ainda menor ou igual ao nmero de l m t T n a i a . Lembrando


iieettvs
que C no possui tipo booleano e que basta que o valor seja 0ou para ser considerado falso e, portanto, o lao

f rseja interrompido;
o
Bem, acho que isso deve ter deixado claro como o f r funciona. O primeiro p i t do programa deve deixar claro que
o
rnf
voc vai passando por vrias iteraes at acertar o nmero mgico pensado pelo nosso programa (o do r n ( do
ad)
incio do programa). Mas como o programa saber quando fomos bem-sucedidos?
Para isso existe o nosso prximo comando.

Execuo condicional o comando i :


f
OK Paramos falando sobre como o nosso programa saber que fomos bem sucedido. Veja que temos um s a flendo
cn
o que o personagem entrou naquela tentativa. Precisamos de um comando para decidir fomos bem sucedidos ou no
em acertar a descoberta do nmero mgico pensado pelo programa. Quem cuida disso comando i do bloco
f
abaixo:

i (ueoosai=nmrPnao
f nmrDUuro=ueoesd)
{
pit(Prbn!Vc aetu\;
rnfaas o cro!n)
ECRA
NER
rtr()
eun0;
}
es
le
{
pit(Nopne e %. nmrDUuro;
rnf esi m d , ueoosai)
i (ueoosai<ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mir\;
rnfesi m m eo ao.n)
i (ueoosai>ueoesd)
f nmrDUuronmrPnao
pit(Pne e u nmr mnr\;
rnfesi m m eo eo.n)
}

O i uma estrutura importante de programao, pois ele executa blocos de cdigo caso a condio determinada seja
f
verdadeira:

i (cnia>
f <odco)
{
<>

}
es i (otacnia>
le f <ur_odco)
{

<>

}
es
le
{
<>

O i ir executar o bloco de cdigo abaixo da instruo caso c n i a seja verdadeira. Caso contrrio ele pode:
f
odco
1. Testar outras condies. Para isso, utiliza-se e s i , com uma nova condio e um bloco de cdigo adequado;
le f
2. Executar um cdigo para exceo. Para isso, utiliza-se e s e o bloco de cdigo adequado;
le
3. No fazer nada;
Agora,

vamos

tentar

ler

esse

trecho

de

cdigo.

primeira

coisa

que

ele

testa

se

n m r D U u r o = u e o e s d , ou seja, se o nmero que o usurio entrou igual ao nmero pensado pelo


ueoosai=nmrPnao
sistema. Caso seja, ele indica que foi bem sucedido e encerra o mesmo (perceba que temos um r t r ( )dentro do
eun0
i . Isso permitido pelo C). Perceba que o operador de relao de igualdade = . NUNCA CONFUNDA COM O
f
=
OPERADOR DE ATRIBUIO, = Esse um bug (falha de programao) muito comum que provocado por falta de
.
ateno na programao. No caso de colocar =, o valor de n m r D U u r oseria substitudo pelo de numeroPensado.
ueoosai
A no ser que n m r P n a ofosse 0(o que impossvel, devido lgica do programa) o programa acusaria verdadeiro
ueoesd
independentemente do valor realmente ser (lembre-se, qualquer valor diferente de 0ou ou N L considerado pelo

UL
C como verdadeiro em relaes lgicas).
Bem, dito isso, vamos continuar a analisar nosso programa. Caso a condio acima no seja verdadeira, ele ir ignorar o
bloco abaixo de i e ir ver que temos um e s com um bloco de cdigo. Ele ento entrar nesse bloco de cdigo. A
f
le
primeira

coisa

que

ele

ir

fazer

dizer

que

no

pensou

no

nmrDUuro e
ueoosai

testar

se

n m r D U u r o n m r P n a o Caso seja verdadeiro, ele ir imprimir na tela uma mensagem dizendo que pensou
ueoosai<ueoesd.
em um nmero maior que o que o usurio entrou. Caso contrrio, ir verificar se n m r D U u r o n m r P n a o
ueoosai>ueoesd.
Se verdadeiro (provavelmente ser, se tudo mais deu errado), ir imprimir que pensou em um nmero menor que o que
o usurio pensou.
Os i s dessas linhas possuem uma caracterstica interessante que o fato de terem comandos diretamente abaixo
f
deles, e no dentro de blocos de cdigo. Na realidade isso deve-se ao fato que i , w i e f r e afins, todos eles
f hl, o
possuem uma caracterstica de executar na realidade apenas um comando. A diferena que, para o compilador C, um
bloco de cdigo (lembre-se: blocos de cdigo so comandos isolados pelos colchetes { ) so considerados comandos
}
isolados. Portanto, um bloco de cdigo == um comando.
Lendo com calma o cdigo voc ir compreender muito bem o mesmo. Leia e releia o cdigoo e faa exerccios mentais
para entender o cdigo do i .
f

Quando tudo d errado consideraes:


OK Mas e caso o usurio no acerte mesmo depois de ter tentado o nmero de vezes que ele desejou
(m x m T n a i a ).
aioettvs
Isso ocorre quando a varivel i aps a iterao do f r ficar com um valor maior que m x m T n a i a , tornando a
,
o,
aioettvs
condio i = a i o e t t v sfalsa e saindo do lao f r(ao atingir o limite de iteraes estipulado). Para encerrar o
<mxmTnaia
o

programa, ns fazemos o sistema imprimir uma mensagem dizendo qual o nmero no qual ele pensou e saindo do
mesmo.
Antes de irmos para as brincadeiras finais, algumas consideraes:
Os operadores relacionais que mostramos anteriormente s servem para valores numricos ou para o tipo c a . Em
hr
especial para strings, a biblioteca s r n . traz funes que nos permite testar igualdade entre textos. Veremos
tigh
eles melhor no futuro;
Ainda no esgotamos o assunto controle de fluxo. Em especial falaremos ainda sobre um tipo de execuo
condicional para mltiplos valores e sobre como quebrar laos como o f re o w i e
o
hl;
possvel aninhar i s ccom mltiplos e s i e afins. Porm, tome cuidado ao us-los: alm de tornar o cdigo
f
le f
de difcil leitura, voc pode ter problemas dependendo do compilador (normalmente o C exige no mnimo 8 nveis
de i s aninhados, mas esse valor no obrigatrio);
f
Bem, dito isso, vamos fazer algumas sugestes para brincadeiras com o nosso cdigo:
Comente ou remova o s a d )e veja o comportamento do gerador de nmeros pseudo-aleatrios;
rn(
Tente reescrever o cdigo para tornar maximoTentativas uma constante. Lembre-se que no necessrio inicializar
o valor imediatamente, podendo ser inicializado em um momento futuro;
No caso de quando o usurio erra o nmero pensado, voc pode ter percebido que os dois i s so
f
redundantes. Tente reescrever o cdigo usando apenas um i ;
f
Da mesma forma que existe o operador + , existe o operador que subtrai um da varivel que o antecede.
+
,
Considerando isso e o funcionamento do lao f r tente reconstruir o lao para no ter uma condio no sentido
o,
exato da palavra. Dica: lembre-se que C trata 0como falso;
Para ter uma idia do problema que pode ocorrer quando se confunde o operador de igualdade com o de
atribuio, modifique o cdigo removendo um dos sinais de igual de i ( u e o o s a i = n m r P n a o e
f nmrDUuro=ueoesd)
veja o que acontece com o programa. Para maior clareza, coloque um p i t que apresente o nmero pensado
rnf
antes do usurio entrar o seu nmero e digite outro completamente diferente. Coloque tambm um p i t
rnf
mostrando o n m r D U u r oaps o bug;
ueoosai
Para entender a questo das definies, altere o valor de LIMITE e veja como o programa se comporta. Como
dica, o clculo do l m t T n a i a baseia-se na idia de buscar-se o nmero sempre indo na metade do que
iieettvs
vlido. Por exemplo: computador escolhe 6. Na primeira interao, tento 5, e ele me fala que pensou um maior.
Tento 7 (metade do bloco acima de cinco) e ele me diz que pensou um menor. Tento 6 e acerto;
Bem, semana que vem iremos reeforar a teoria dos operadores (lista completa e exemplos) e daremos uma terminada
na questo dos controles de fluxo. At l, divirtam-se!

Bsico, Controle de Fluxo, Operadores

Posts Anteriores

Blog no WordPress.com. O tema zBench.

Popular