You are on page 1of 221

Conceitos e Exerccios de Programao

Utilizando Linguagem C

Este texto destina-se a todos quanto se queiram iniciar na programao, desde as mais modestas s mais elevadas ambies. O mtodo de aprendizagem consiste em explicar um conjunto de conceitos, suportados em exemplos simples, aps o qual o leitor ter ao seu dispor um leque de exerccios desde simples a mais avanados, que lhe permitam interiorizar os conceitos com base na sua experincia de programao.

Jos Coelho 2010

PREFCIO
O presente texto destina-se a todos quantos queiram iniciar-se na programao. A linguagem de programao adoptada a linguagem C, mas este texto no se centra na linguagem de programao mas sim nos conceitos, sendo a linguagem de programao apenas uma ferramenta para o ensino da programao. Este texto tem origem num conjunto de explicaes fornecidas no mbito da Unidade Curricular (UC) de Programao da Universidade Aberta, de forma a responder s dificuldades sentidas pelos estudantes. As explicaes foram organizadas em conceitos essenciais, que so introduzidos com uma necessidade que os conceitos introduzidos anteriormente no satisfazem, e constitudos por uma explicao do conceito, seguida de um ou mais exemplos. Os exemplos so to simples quanto possvel, de forma a ser clara a aplicao do conceito. A compreenso completa dos exemplos passa por analisar a execuo passo a passo de forma a remover qualquer dvida que o leitor tenha sobre a forma como executado o exemplo que exemplifica o conceito em causa, e desta forma facilmente interiorizar os conceitos essenciais. Cada conceito termina com um conjunto de erros comuns mais directamente ligados ao conceito, e respectivos exemplos, para que ao serem compreendidos no sejam cometidos. No anexo est a lista completa de erros comuns. A estes conceitos juntou-se o pacote de actividades formativas a realizar na UC, aqui chamados de exerccios, de nvel de dificuldade desde o mais simples (exerccios verdes), ao mais desafiante (exerccios vermelhos), passando pela dificuldade mdia (exerccios azuis). Foi tido o maior cuidado na escolha dos exerccios, de forma a pedir apenas exerccios no abstractos, que possam ser reutilizados, e inspirados em matrias mais avanadas a abordar em outras UCs, para assim tirar o maior partido possvel do trabalho do leitor. Aos enunciados dos exerccios foi dada a maior ateno, tendo sido j utilizados e revistos com o decorrer da UC, para que o leitor no sinta que a principal dificuldade compreender o enunciado do exerccio mas sim na sua realizao. No anexo encontram-se mais dicas sobre os exerccios, uma forma de verificar se o exerccio est correcto sem ver uma resoluo, e as resolues dos exerccios para comparao, aps resolv-los. Os conceitos foram organizados primeiramente em pginas isoladas, e posteriormente ordenadas por ordem de importncia, o mais importante primeiro. Os conceitos passaram a captulos neste texto, que esto agrupados em trs partes, a que correspondem os mdulos na UC, e em que so pontos apropriados para uma paragem mais longa no estudo, ou para a realizao de actividades de avaliao. Os exerccios esto associados s partes, e no aos captulos. A metodologia de estudo proposta assenta no princpio que programar aprende-se programando, pelo que no se apresentam captulos com muitos exerccios resolvidos, mas apenas os conceitos essenciais para que o leitor possa resolver por si os exerccios e adquirir a experincia de programao que apenas sua e intransmissvel. Se o leitor tiver aspiraes modestas e disponha de pouco tempo, deve no final de cada parte fazer apenas os exerccios verdes. Caso no consiga realizar algum exerccio, deve reler os captulos anteriores. Aconselha-se a planear um estudo regular, cada sesso de estudo com 1 a 2 horas, e uma periodicidade entre 1 dia a 1 semana. No deve ler mais que um captulo na mesma sesso de estudo, e deve fazer os exerccios em sesses distintas da leitura dos captulos. Caso o leitor tenha aspiraes mais elevadas e disponha de mais tempo, deve fazer tudo o que atrs se aconselhou, mais a realizao dos exerccios azuis e vermelhos. Se o leitor tiver oportunidade de estudar em grupo, aconselha-se a que aplique em estudo isolado a metodologia atrs referida, e partilhe ideias e exerccios aps os resolver, comentando as opes

tomadas. O estudo deve estar sincronizado para poder haver alguma vantagem na troca de ideias aps a leitura de cada captulo e anlise dos exerccios realizados. No deve no entanto cair no erro de aguardar pela resoluo dos exerccios, ou pelas explicaes do colega sobre uma parte do texto. Ningum pode aprender por outra pessoa. Deve tirar partido de haver outras pessoas a estudar o mesmo assunto, e no prescindir de aprender. A escolha da linguagem de programao C deve-se a essencialmente dois motivos. Primeiro trata-se de uma linguagem estruturada imperativa, que se considera ideal para aprender a programar. Numa linguagem orientada por objectos tem que se de utilizar muitas caixas pretas, e dificilmente se consegue explicar alguns conceitos que apenas fazem sentido em programas de maior dimenso, algo que no deve ser prioridade de quem se inicia na programao. As linguagens funcionais e declarativas, atendendo a que pertencem a paradigmas computacionais alternativos, embora possveis na iniciao programao, no se consideram apropriadas uma vez que os ganhos seriam muito limitados devido s suas fracas utilizaes reais. Segundo, a linguagem C uma linguagem de alto nvel, bastante antiga, amplamente divulgada e em utilizao, implementada e disponvel em praticamente todas as plataformas. Com a sua simplicidade permite compiladores que tirem o melhor partido da mquina, resultando normalmente em binrios mais eficientes que em outras linguagens. Todos os conceitos aqui introduzidos sero teis no s em outras linguagens estruturadas, como na programao orientada por objectos.

NDICE PARTE I VARIVEIS E ESTRUTURAS DE CONTROLO .............................................................. 6


1. 2. 3. 4. 1) Primeiro Programa..................................................................................................................................... 7 Variveis.................................................................................................................................................... 9 Condicinais ...............................................................................................................................................16 Ciclos ........................................................................................................................................................23 Exerccios .................................................................................................................................................29

PARTE II FUNES, VECTORES E RECURSO .......................................................................... 39


5. 6. 7. 8. 9. 2) Funes ....................................................................................................................................................40 Mais Ciclos e Condicionais.........................................................................................................................49 Vectores ...................................................................................................................................................57 Procedimentos .........................................................................................................................................69 Recurso...................................................................................................................................................78 Exerccios .................................................................................................................................................83

PARTE III MEMRIA, ESTRUTURAS E FICHEIROS ................................................................. 99


10. 11. 12. 13. 3) Memria ............................................................................................................................................ 100 Estruturas ........................................................................................................................................... 113 Ficheiros ............................................................................................................................................. 130 Truques .............................................................................................................................................. 143 Exerccios ............................................................................................................................................... 151

PARTE IV ANEXOS ......................................................................................................................... 160


14. 15. 16. 17. Compilador e Editor de C .................................................................................................................... 161 No consigo perceber o resultado do programa .................................................................................. 164 Funes standard mais utilizadas ........................................................................................................ 169 Erros Comuns ..................................................................................................................................... 170

18.

Exerccios: Dicas, Respostas e Resolues............................................................................................ 176

PARTE I VARIVEIS E ESTRUTURAS DE CONTROLO


Na Parte I so introduzidos os primeiros conceitos de programao, existentes em todas as linguagens de programao modernas. A leitura atenta e pausada essencial para a compreenso destes conceitos que embora aparentemente simples, a sua no compreenso tem consequncias em todo o resto do texto, mas que poder detectar de imediato no final da Parte I ao no conseguir resolver alguns dos exerccios propostos. Aps a realizao desta parte, fica com as bases para programar em qualquer linguagem de programao simples, para implementao de pequenas funcionalidades, como Javascript para adicionar dinamismo a uma pgina Web, VBA para programar macros no Microsoft Excel, ou Linguagem R para realizar clculos estatsticos.

1. Primeiro Programa

1. PRIMEIRO PROGRAMA
Um programa uma sequncia de instrues, dirigidas a um computador, para que este execute uma determinada funo pretendida. A actividade de programar consiste na elaborao dessa sequncia de instrues numa determinada linguagem de programao, que satisfaz o que pretendido. A linguagem de programao tem que seguir uma sintaxe rgida, de forma a poder ser convertida em instrues que o computador possa compreender, mas por outro lado deve ser facilmente escrita e lida por uma pessoa. H diferentes linguagens de programao, utilizaremos a linguagem C, no entanto no ser a sintaxe da linguagem o mais relevante a aprender, mas sim competncias que so independentes da linguagem. A linguagem C uma linguagem imperativa, cada instruo uma ordem para o computador. Existem outros paradigmas de programao, mas fora do mbito deste texto. Manda a tradio que o primeiro programa a desenvolver numa linguagem, seja o ol mundo. No a vamos quebrar, este o programa mais curto possvel, apenas escreve um texto e termina.
1 2 3 4 5 6 #include <stdio.h> int main() { printf("Ola Mundo!"); } Programa 1-1 Ol Mundo

O programa acima est escrito em linguagem C, e pode ser executado pelo computador. No entanto o computador no entende directamente a linguagem C, apenas ficheiros executveis (no Windows com extenso exe). No entanto, atravs de um programa chamado de Compilador, podemos converter o cdigo C acima num executvel. Esse executvel contm j instrues em cdigo mquina, cujo computador pode executar. Vamos ver em que consiste o cdigo acima: Existe numerao das linhas e cores, que so apenas para nossa convenincia. A numerao das linhas no faz parte do ficheiro C, e as cores so colocadas pelo editor. Um programa em C um ficheiro de texto plano, com a extenso .c e pode ser editado num editor de texto plano como o Notepad, sem qualquer cor ou numerao das linhas. Linha 1: comea por um # (cardinal), pelo que esta linha uma instruo no para o computador mas sim para o compilador. Essas instrues chamam-se de comandos de prprocessamento, dado que so executadas antes do processamento. O comando #include instrui o compilador a incluir o ficheiro indicado de seguida. Esse ficheiro uma biblioteca standard do C (existe em todos os compiladores de C), stdio.h, e consiste na declarao de funes de entrada e sada. Estas funes podem ser utilizadas de seguida no programa, sendo utilizada neste caso a funo printf. Linha 2: esta linha no tem nada. Podem existir quantas linhas em branco o programador quiser, o compilador no ficar atrapalhado, nem far nada com elas, mas tal pode ser til

Parte I Variveis e Estruturas de Controlo para espaar os comandos e tornar mais legvel o programa. O mesmo vlido para os espaos ou tabs numa linha de cdigo, como por exemplo os espaos da linha 5 que antecedem o printf. Linha 3: comea a implementao da funo main. O programa comea sempre na funo main. Para j, vamos deixar por explicar o int, e os parnteses vazios. Realar que as instrues que dizem respeito funo main, e portanto ao que o programa tem de fazer, esto limitadas por duas chavetas a abrir na linha 4 e a fechar na linha 6. Todas as instrues entre estas chavetas, pertencem funo main. Linha 5: nesta linha est a nica instruo. Existe uma chamada funo printf, e sabemos que uma funo porque aps o seu nome tem uns parnteses a abrir. Dentro dos parnteses colocam-se os argumentos a passar funo. Neste caso colocamos o texto que queremos imprimir: "Ola mundo!". O texto coloca-se entre aspas, para no se confundir com instrues.

Se no o fez j, crie e execute o programa acima. Veja as instrues para instalar um compilador no anexo Compilador e Editor de C. tudo. Acabou de dar o primeiro passo no mundo da programao, editou e compilou o primeiro programa. Houve conceitos referidos aqui que no compreende agora completamente: biblioteca; funo; argumentos. Para j no lhes d muito importncia.

2. Variveis

2. VARIVEIS
Uma varivel num programa uma entidade que tem um valor a cada instante, podendo esse valor ao longo do programa ser utilizado e/ou alterado. nas variveis que se guarda a informao necessria para realizar a funo pretendida pelo programa. Podem existir quantas variveis forem necessrias, mas cada uma utiliza memria do computador. O programa seguinte tem por objectivo trocar o valor de duas variveis:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> int main() { /* declarao de trs variveis inteiras */ int x=3; int y=7; int aux; /* trocar o valor de x com y */ aux=x; x=y; y=aux; /* mostrar os valores em x e em y */ printf("x: %d, y: %d",x,y); } Programa 2-1 Troca o valor de duas variveis

Comentrios: Este programa tem uma estrutura inicial idntica ao Programa 1-1, nomeadamente nas linhas 1 a 4, e linha 17. A linha 5, 10 e 15 tm comentrios ao cdigo. Os comentrios so todo o texto entre /* e */, sendo ignorados pelo compilador. O objectivo que seja possvel ao programador colocar texto explicativo de partes mais complexas do programa, no para que o compilador o melhor entenda, mas para que um outro programador que leia o cdigo, ou o prprio programador mais tarde, compreenda mais facilmente o cdigo. Os comentrios ficam a cor verde, dado que o editor identifica que o contedo dessas linhas ser ignorado pelo compilador1. Nas linhas 6 a 8, esto declaradas as variveis x, y e aux. As variveis x e y so no s criadas, como tambm inicializadas. Ao ficarem com um valor inicial, podem desde j ser utilizadas. Na linguagem C, todas as variveis utilizadas tm de ser declaradas no incio da funo2. Nas linhas 11 a 13, esto comandos que so atribuies. Numa atribuio, coloca-se uma varivel do lado esquerdo da igualdade, e uma expresso do lado direito. Neste caso, todas as atribuies tm expresses com uma s varivel.

1 A linguagem C++ tem os comentrios at ao final da linha, aps duas barras: //. A maior parte dos compiladores de C tambm permite este tipo de comentrios. 2 A linguagem C++ bem como a generalidade dos compiladores de C permitem a declarao fora do incio das funes, mas pelos motivos referidos no erro Declarao de variveis fora do incio das funes, do anexo Erros Comuns esta restrio ser mantida.

10

Parte I Variveis e Estruturas de Controlo Na linha 16, mostrado o valor das variveis. Notar que a funo printf tem 3 argumentos separados por vrgulas. O primeiro uma string, em que comea com aspas e acaba nas aspas seguintes, o segundo uma expresso com a varivel x, seguido de uma expresso com a varivel y. Na string existem dois %d. Em cada %d o printf em vez de mostrar no ecr %d coloca o valor da varivel seguinte na sua lista de argumentos. No primeiro %d vai colocar x, e no segundo %d vai colocar o y. Notar que cada instruo acaba com um ponto e vrgula, sendo esta uma caracterstica do C.

Execuo do programa:
C:\>troca x: 7, y: 3

Os valores esto correctamente trocados, mas vamos ver com mais detalhe qual o valor das variveis a cada momento, na execuo passo-a-passo, direita. A execuo passo-a-passo tem em cada linha da tabela um passo. Em cada passo indicada a linha de cdigo correspondente, e a respectiva instruo por vezes encurtada como no passo 7. A coluna resultado mostra o resultado da instruo, excepto se tratar de uma declarao, em que uma ou mais variveis ocupam a coluna seguinte disponvel, ou uma atribuio, em que a respectiva varivel fica com o valor atribudo. Para poder-se escrever a linha de um determinado passo, suficiente a informao da linha do passo anterior. A pergunta "Qual o valor de x?" no tem muito sentido, a no ser que se identifique o instante. No passo 3 ou no final do programa? Uma varivel tem um valor at que lhe seja atribudo outro. Vamos agora resolver o mesmo problema, troca dos valores de duas variveis, mas sem utilizar uma varivel auxiliar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main() { /* declarao de duas variveis inteiras */ int x=3, y=7; /* trocar o valor de x com y */ x=x+y; y=x-y; x=x-y; /* mostrar os valores em x e em y */ printf("x: %d, y: %d",x,y); } Programa 2-2 Troca o valor de duas variveis sem varivel auxiliar

A diferena para o exemplo anterior que no se declarou uma varivel aux, e declarou-se ambas as variveis x e y num s comando. As atribuies utilizam agora expresses com duas variveis, incluindo a prpria varivel que est a ser atribuda.

2. Variveis

11

Embora este programa parea confuso, o certo que tem a mesma funcionalidade que o anterior como se comprova de seguida, vendo a execuo passo-a-passo. No passo 2, o valor de x utilizado na expresso o que existia antes do passo 2, o valor 3, passando a ser o valor 10 aps a execuo do passo 2. No h qualquer problema em colocar a varivel x de um lado e do outro de uma atribuio. A varivel x esquerda, significa que ficar com o resultado da expresso, e a varivel x direita, significa que se deve utilizar o valor da varivel x antes desta instruo. Os tipos de dados utilizados podem no ser apenas variveis inteiras. Os tipos elementares da linguagem C so os seguintes:

char - um carcter, ou um inteiro muito pequeno (guarda apenas 256 valores distintos) short - inteiro pequeno (pouco utilizado) int - tipo standard para inteiros long - inteiro longo float - nmero real (pode ter casas decimais) double - nmero real com preciso dupla (igual ao float mas com mais preciso)

Pode-se nos tipos inteiros (char a long), anteceder com as palavras unsigned ou signed, para indicar que o nmero inteiro tem ou no sinal. Se omitir, considerado que tm sinal. Pode-se tambm considerar o tipo long long para um inteiro muito longo3, ou o long double, mas esses tipos nem sempre esto implementados. Quais os valores mximos/mnimos que podemos atribuir s variveis de cada tipo? Isso depende do espao reservado para cada tipo de varivel, que por sua vez depende tanto do compilador, como do computador em que o programa for compilado e executado. Para saber quanto espao necessrio para guardar cada varivel, pode-se utilizar o operador sizeof. O resultado de sizeof(int) retorna o nmero de bytes que uma varivel do tipo inteiro ocupa na memria.
1 2 3 4 5 6 #include <stdio.h> int main() { printf("sizeof(int): %d",sizeof(int)); } Programa 2-3 Imprime o nmero de bytes que um inteiro ocupa

Execuo do programa:
C:\>sizeofint sizeof(int): 4

Execute este programa para ver quantos bytes ocupa um inteiro no seu compilador e computador. Para que os resultados sejam idnticos na resoluo dos exerccios, importante que todos utilizem inteiros do mesmo tamanho. Se o resultado no for no seu caso 4, experimente o sizeof(short) ou sizeof(long), e utilize o tipo que tiver tamanho 4 para todas as variveis inteiras nos exerccios das actividades formativas. As variveis do tipo char e float/double, no utilizam a mesma string de formatao no printf da que os inteiros. Para os inteiros, utiliza-se o %d na string do primeiro argumento, mas esse
3

O tipo long long no faz parte da norma inicial da linguagem C, actualmente a generalidade dos compiladores implementa-o com um inteiro de 64 bits. A string de formatao varia: %I64d; %lld. Para uma soluo mais portvel, ver inttypes.h e utilizar PRId64.

12

Parte I Variveis e Estruturas de Controlo

cdigo significa que se pretende colocar l um valor inteiro. Se pretendermos colocar um carcter ou um valor real, tem que se utilizar o %c (carcter) ou %f (float), ou ainda %g (para mostrar o real na notao cientfica). Note-se no entanto que o carcter tanto pode ser considerado como um carcter ou como com um inteiro pequeno.
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { char c='a'; float x=12354234.2346; double y=12354234.2346; /* mostrar valores */ printf("c: %c (%d), x: %f, y: %g", c, c, x, y); } Programa 2-4 Imprime diferentes tipos de variveis

Comentrios: O carcter c atribudo com a letra "a". Os caracteres so delimitados entre plicas, em vez de aspas, para no se confundirem com outros identificadores. O printf tem ordem para: o No %c ir buscar a primeira varivel e colocar um carcter (a varivel c) o No %d ir buscar a segunda varivel (c novamente), e colocar o seu valor numrico o No %f ir buscar a terceira varivel (x), e coloca o nmero real com parte decimal o No %g ir buscar a quarta varivel (y), e coloca o nmero real em notao cientfica

Execuo do programa:
C:\>charfloat c: a (97), x: 12354234.000000, y: 1.23542e+007

Note que o valor numrico da letra 'a' o 97. Existe uma correspondncia entre as letras e os nmeros pequenos, mas esta questo ser tratada mais frente. O valor de x no o que est no programa. Isto acontece porque o tipo float tem uma preciso no muito grande (ocupa 4 bytes). O tipo double tem uma preciso maior, mas em qualquer caso no se pode contar com a preciso infinita do valor l colocado. Uma varivel real fica sempre com o valor mais prximo que a representao interna do nmero permite, e no com o valor exacto. A funo scanf a funo inversa ao printf, e serve para introduzir valores em variveis, valores introduzidos pelo utilizador, em vez de os mostrar como no printf.
1 #include <stdio.h> 2 3 int main() 4 { 5 char c; 6 int x; 7 double d; 8 9 printf("Introduza um caracter: "); 10 scanf("%c", &c); 11 printf("Introduza um inteiro: "); 12 scanf("%d", &x); 13 printf("Introduza um real: "); 14 scanf("%lf", &d); 15

2. Variveis
16 17 } printf("Valores introduzidos: %c %d %f",c,x,d); Programa 2-5 Pede valores de variveis de diferentes tipos que depois imprime

13

No cdigo acima pode-se ver parecenas do scanf com o printf, mas h um & antes de cada varivel. Porqu? Esta questo apenas pode ser respondida completamente na Parte III, por agora explica-se apenas que se destina a poder fazer uma atribuio e no uma leitura s variveis. Notar que foi utilizado para ler o valor para double, %lf, e no %f, dado que o real um double, e no um float. Pela mesma razo que deve utilizar inteiros de 4 bytes, deve utilizar para os nmeros reais o tipo double. Execuo do programa:
C:\>scanf Introduza um caracter: c Introduza um inteiro: 123 Introduza um real: 142323.2435 Valores introduzidos: c 123 142323.243500

Na execuo do programa o texto introduzido pelo utilizador vem a bold e itlico. Como o programa mostra o contedo das variveis que leu na ltima linha, est verificado que as variveis foram lidas correctamente. Consegue agora criar variveis dos tipos bsicos na linguagem C, ler e mostrar os valores das variveis, efectuar atribuies e se necessrio fazer uma execuo detalhada e ver o valor das variveis em cada instante. No anexo est listado um conjunto de erros comuns para o ajudar a melhorar a qualidade do seu cdigo. No entanto, muitos dos erros s os compreender aps o estudo da matria correspondente, e outros apenas quando lhe for apontado no seu cdigo uma ocorrncia do erro. No final de cada captulo so introduzidos os erros comuns, que esto mais directamente relacionados com o captulo. Neste captulo temos quatro erros: o Declaraes ou atribuies a variveis nunca utilizadas o Forma: Por vezes declaram-se variveis, ou fazem-se atribuies a variveis, um pouco para ver se o programa passa a funcionar. No entanto, aps muito corte e costura, por vezes ficam atribuies a variveis que na verdade nunca so utilizadas, embora tal no afecte o bom funcionamento do programa. o Problema: Se h uma varivel declarada que no utilizada, essa varivel pode ser removida e o cdigo fica igual. Se h uma atribuio a uma varivel que depois no utilizada em nenhuma expresso, ento a atribuio desnecessria. Ter variveis, expresses, atribuies a mais, uma situao indesejvel, dado que compromete a leitura do cdigo que realmente funciona. o Resoluo: Verificar se h variveis declaradas que no so utilizadas, e apag-las. Para cada atribuio, seguir o fluxo do cdigo at uma possvel utilizao. Se no existir, apagar a atribuio, reinicializando o processo de verificao. No final o cdigo real poder ser muito mais reduzido, do que o gerado em situaes de stress em modo de tentativa/erro. Uma atribuio, uma leitura

14
o

Parte I Variveis e Estruturas de Controlo


Forma: Uma varivel atribuda com um valor, para depois utiliz-lo na instruo seguinte, no sendo mais necessrio o valor da varivel. Por exemplo, para reunir os valores necessrios chamada de uma funo. o Problema: Esta situao pode indicar a existncia de uma varivel desnecessria, e quantas mais variveis desnecessrias o cdigo tiver, mais complexo fica, para no falar que ocupam memria desnecessariamente. apenas justificvel no caso de a expresso ser muito complexa, de forma a clarificar o cdigo. o Resoluo: Ao fazer a atribuio do valor varivel, utiliza uma expresso. Essa varivel depois utilizada de seguida tambm uma s vez, pelo que mais vale colocar a prpria expresso no local onde a varivel utilizada. Variveis desnecessrias o Forma: Ao criar uma varivel por cada necessidade de registo de valores, pode acontecer haver variveis sempre com o mesmo valor, mas tal no impede que o programa funcione correctamente. o Problema: Ter muitas variveis com a mesma funo tem o mesmo problema que uma atribuio, uma leitura. Fica o cdigo mais complexo e difcil de ler sem qualquer ganho. Pode acontecer que este problema se reflicta no apenas por ter duas variveis sempre com os mesmos valores, mas terem valores com uma relao simples (o simtrico, por exemplo). o Resoluo: Deve identificar as variveis com atribuies e utilizaes perto uma da outra, eventualmente os nomes, e verificar se pode a cada momento utilizar o valor de uma varivel em vez da outra. Por vezes no simples a identificao/remoo destas situaes quando h muitas variveis. prefervel aplicar primeiramente a remoo de declaraes/atribuies desnecessrias, e aplicar a resoluo do erro uma atribuio, uma leitura, antes deste erro. Comentrios explicam a linguagem C o Forma: De forma a explicar tudo o que se passa no programa, e no ser penalizado, por vezes desce-se to baixo que se explica inclusive linguagem C. o Problema: Esta situao um claro excesso de comentrios, que prejudica a legibilidade. Quem l cdigo C sabe escrever cdigo C, pelo que qualquer comentrio do tipo atribuir o valor X varivel Y, desrespeitoso para quem l, fazendo apenas perder tempo. Ao comentar instrues da linguagem e deixar de fora comentrios mais relevantes, pode indicar que o cdigo foi reformulado por quem na verdade no sabe o que este realmente faz. o Resoluo: Escreva como se estivesse a escrever a si prprio, supondo que no se lembrava de nada daqui a uns 6 meses.

No Programa 2-4 podemos ver uma ocorrncia do erro uma atribuio, uma leitura, dado que todas as variveis so atribudas na declarao e utilizadas uma s vez. Neste caso pretende-se mostrar a string de formatao para os diversos tipos, mas poder-se-ia igualmente ter colocado os prprios valores no printf. O mesmo programa tem ainda uma ocorrncia do erro comentrios explicam a linguagem C, e este erro ser cometido ao longo dos programas apresentados neste texto, dado que o cdigo se destina a quem est a aprender, mas num programa C no faz sentido colocar um comentrio mostrar valores antes do printf. No h ocorrncias dos outros dois erros nos programas deste captulo, e a sua exemplificao seria forada. A melhor fonte de exemplos tem de ser o seu cdigo, aps resolver os exerccios, procure identificar ocorrncias desses erros.

2. Variveis

15

O conceito de varivel, aparentemente simples, no no entanto to facilmente interiorizado como possa pensar. Saber no primeiro exerccio que seja necessria uma varivel auxiliar no declarada no enunciado, se consegue ou no identificar a varivel e seu modo de utilizao, ou se por outro lado considera o enunciado complexo e confuso. Se for o segundo caso, antes de passar para o exerccio seguinte, imperativo que olhe para a soluo que acabar por obter, com a sua mxima ateno. A experincia levar a que identifique imediatamente as variveis que necessita para resolver cada problema, sendo esta uma das principais caractersticas de um programador experiente.

16

Parte I Variveis e Estruturas de Controlo

3. CONDICINAIS
As instrues dadas nos programas at aqui so sequenciais, isto , existindo 4 instrues estas vo sendo executadas sem sequncia, numa determinada ordem fixa. No entanto, o que fazer se pretendermos ter um programa que tenha mais que uma execuo alternativa? Por exemplo, estamos interessados em determinar se um determinado nmero inteiro introduzido pelo utilizador, par ou mpar. Como h duas possibilidades para apresentar ao utilizador, tem de existir duas instrues alternativas, uma para o caso do nmero ser par, e outra para o caso de ser mpar. Para alm disso, temos de saber determinar se o nmero par ou mpar. A linguagem C permite que um determinado cdigo seja executado dependente do valor de uma expresso lgica. Se a expresso for verdadeira, executa uma instruo, caso contrrio executa a outra instruo.
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main() { int numero; printf("Indique um numero: "); scanf("%d",&numero); if(numero%2==0) printf("par"); else printf("impar"); } Programa 3-1 Determina se um nmero par ou impar

Comentrios: Linha 8: a instruo if tem entre parnteses uma expresso lgica. Essa expresso utiliza o operador lgico == (no confundir com a atribuio), e do lado esquerdo utiliza o operador % (resto da diviso). Se o resto da diviso do nmero por 2 for zero, ento a expresso lgica verdadeira e executa a instruo da linha 9 Linha 10: existe uma instruo else. Essa instruo est associada ao if, mas o if pode ser utilizado sem o else. Se existir indica uma instruo a executar no caso da expresso lgica ser falsa

Execuo do programa:
C:\>paridade Indique um numero: 234 par C:\>paridade Indique um numero: 23 impar

O resultado foi o pretendido, vejamos agora a execuo passo-a-passo. Nesta execuo o nmero introduzido foi o "234", levando a que o condicional fosse verdadeiro e que no passo 5 a linha 9 fosse executada.

3. Condicionais Na segunda execuo, o valor introduzido foi 23, levando a que o condicional fosse falso e que a linha 11 fosse executada no passo 5, em vez da linha 9. Podem ser utilizados os operadores lgicos normais em expresses lgicas, e os parnteses curvos que forem necessrios:

17

A e B so expresses lgicas: o A||B A ou B o A&&B AeB o ! A no A A e B expresses numricas: o A==B A igual a B o A!= B A diferente de B o A>B A maior que B o A>=B A maior ou igual a B o A<B A menor que B o A<=B A menor ou igual a B Nas expresses numricas: o A+B A mais B o A-B A menos B o A*B A vezes B o A/B A a dividir por B o A%B resto da diviso de A por B

Vamos aplicar estes operadores num problema mais complexo. Pretende-se saber se um ano ou no bissexto. Um ano bissexto se for mltiplo de 4, mas de 100 em 100 anos esta regra no vlida, excepto de 400 em 400 anos. Temos um problema que tambm requer apenas duas execues alternativas, mas com uma expresso lgica mais complexa. O programa vai ter a mesma estrutura que o anterior, mas tem que se construir a expresso lgica. Vejamos:

Mltiplo de 4: o ano%4==0 resto da diviso com 4 nula, portanto o ano mltiplo de 4 Excepto de 100 em 100 anos: o ano%4==0 && ano%100!=0 retorna verdadeiro para mltiplos de 4, mas se for mltiplo de 100 j no Excepto de 400 em 400 anos: o (ano%4==0 && ano%100!=0) || ano%400==0 como a excepo positiva, coloca-se uma disjuno e no uma conjuno como no caso anterior

18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h>

Parte I Variveis e Estruturas de Controlo

int main() { int ano; printf("Indique ano: "); scanf("%d", &ano); /* teste de ano bissexto */ if(ano%4==0 && ano%100!=0 || ano%400==0) printf("Bissexto"); else printf("Normal"); } Programa 3-2 Determina se um ano normal ou bissexto

Notar que no foram colocados parnteses para a conjuno. No necessrio dado que tem sempre prioridade relativamente disjuno. Execuo do programa:
C:\>bissexto Indique ano: 2345 Normal C:\>bissexto Indique ano: 2344 Bissexto

Na execuo passo-a-passo pode-se ver que na expresso lgica do passo 4, o segundo argumento do operador && (AND) no foi avaliado porque o primeiro argumento j era falso. Desta vez o segundo argumento do operador || (OR) no foi avaliado porque o primeiro argumento j era verdadeiro. Pode acontecer que seja necessrio mais que uma instruo dentro do if, ou que sejam necessrias vrias instrues if encadeadas. o caso do programa seguinte, em que est implementado um algoritmo para determinar o nmero de dias do ms, dado o nmero do ano e ms.

3. Condicionais
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); if(mes==2) { /* teste de ano bissexto */ if(ano%400==0 || ano%4==0 && ano%100!=0) printf("29"); else printf("28"); } else if(mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12) { printf("31"); } else { printf("30"); } } Programa 3-3 Determina o nmero de dias de um ms/ano

19

Comentrios: O primeiro if testa se o ms 2 (Fevereiro). Neste caso tem que se testar se o ano ou no bissexto de forma a retornar 29 ou 28. Utilizamos o cdigo do exerccio anterior, mas teve de ser colocado dentro do if. Para no haver confuses com o else a que pertence este if, colocou-se o if num bloco (dentro de chavetas). Um bloco de cdigo pode ser colocado em qualquer lado em que se coloca um s comando, como o caso do if. Assim possvel executar quantas instrues se quiser utilizando o mesmo condicional. Aps se saber que o ms no 2, tem que se testar se um ms de 31 dias, ou se um ms de 30 dias. Tem de portanto existir outro if logo a seguir ao else. Embora seja um if distinto, est em sequncia do primeiro if e na verdade permite que o programa siga um de trs caminhos possveis (Fevereiro / ms 31 / ms 30), e no apenas 2 como aconteceria se utilizar apenas um s if. A expresso lgica para determinar que o ms tem 31 dias longa, pelo que muda de linha. No h problema com isso, mas neste caso foi uma m opo, podendo-se ter testado se o ms tinha 30 dias, que so menos meses (4, 6, 9 e 11).

Execuo do programa:
C:\>diasdomes Indique ano: 2344 Indique mes: 2 29 C:\>diasdomes Indique ano: 2342 Indique mes: 4 30

Esta execuo tem dois condicionais encadeados, o passo 7

20

Parte I Variveis e Estruturas de Controlo

est um segundo condicional que apenas executado porque no passo 6 o primeiro condicional retornou verdadeiro. Notar que h uma varivel dias que afinal no necessria. Neste caso nem sequer atribuda, pelo que pode ser apagada do cdigo. Na segunda execuo o resultado no passo 6 falso, pelo que a execuo segue no passo 7 para um outro condicional sobre o respectivo else, no confundindo o else do condicional interior, dado que est dentro de um bloco de cdigo. No se pode deixar de referir que as expresses lgicas em C so valores inteiros, ou se quiser, um inteiro o tipo de dados utilizado nas expresses lgicas, em que o valor zero significa o falso, e o valor no zero significa o verdadeiro. Com este conhecimento pode-se em vez do teste utilizado no primeiro exemplo:
8 9 10 11 if(numero%2==0) printf("par"); else printf("impar");

Fazer simplesmente:
8 9 10 11 if(numero%2) printf("impar"); else printf("par");

No entanto no se aconselha a fazer utilizao abusiva deste conhecimento, dado que torna mais pesada a leitura do cdigo. Cada expresso lgica deve ter pelo menos um operador lgico, de forma a facilitar a leitura do cdigo e identificar a expresso como uma expresso lgica, e no confundir com uma expresso numrica, embora em C qualquer expresso numrica uma expresso lgica. Os erros comuns mais directamente associados a este captulo so: Indentao4 varivel o Forma: Como a indentao ao gosto do programador, cada qual coloca a indentao como lhe d mais jeito ao escrever. o Problema: A leitura fica prejudicada se a indentao no for constante. Por um lado no fcil identificar se h ou no algum erro do programador em abrir/fechar chavetas, dado que a indentao pode ser sua opo e no esquecimento de abrir/fechar chavetas. Por outro lado, no possvel a visualizao rpida das instrues num determinado nvel, dado que o nvel varivel, sendo necessrio para compreender algo, de analisar o cdigo por completo. Com o cdigo indentado, pode-se ver o correcto funcionamento de um ciclo externo, por exemplo, sem ligar ao cdigo interno do ciclo, e assim sucessivamente.

A indentao de uma instruo consiste em anteced-la de um nmero de espaos constante (so utilizados 4 espaos neste texto), por cada bloco em que a instruo est inserida

3. Condicionais o

21

Resoluo: Indente o cdigo com 2 a 8 espaos, mas com o mesmo valor ao longo de todo o cdigo. Se utilizar tabs, deve indicar o nmero de espaos equivalente, no cabealho do cdigo fonte, caso contrrio considera-se que o cdigo est mal indentado. Se pretender subir a indentao com a abertura de uma chaveta, pode faz-lo mas tem de utilizar sempre chavetas, caso contrrio ficam instrues que esto no mesmo nvel mas no cdigo ficam e indentaoes distintas. Qualquer que seja as opes, no pode existir instrues no mesmo alinhamento que pertenam a nveis distintos (ou vice-versa). A posio das chavetas indiferente, mas tem que se apresentar um estilo coerente ao longo do cdigo. Duas instrues na mesma linha o Forma: Quando as instrues so pequenas e esto relacionadas, mais simples coloc-las todas na mesma linha. Como uma questo de estilo, no afecta o funcionamento. o Problema: Se h instrues relacionadas, estas devem ser colocadas na mesma funo, se tal se justificar. Colocar duas ou mais instrues na mesma linha vai prejudicar a legibilidade e no s. O compilador quando encontra algum problema refere a linha do cdigo, e existindo mais que uma instruo nessa linha, a informao menos precisa. Deixa de ser possvel tambm seguir um nvel de indentao para passar por todas as instrues nesse nvel, tem que se fazer uma leitura atenta por todo o cdigo do bloco o Resoluo: Em todas as linhas com mais que uma instruo, colocar uma instruo por linha Linhas de cdigo nunca executadas o Forma: Por vezes em situaes de stress tentam-se vrias alternativas, e fica algum cdigo que na verdade nunca tem hiptese de ser executado, mas que tambm no atrapalha. o Problema: Se h cdigo a mais, este deve ser removido sobre pena de perda de legibilidade e manuteno. Esta situao ampliada quando h cdigo de diversas provenincias, em que ao integr-lo ningum se atreve a reeditar cdigo que funciona, para seleccionar a parte do cdigo que deve ser integrado. o Resoluo: Identificar as zonas de cdigo que de certeza que no so executadas de modo algum, e apag-las. Tanto pode ser feito por inspeco ao cdigo como atravs de testes, submetendo o cdigo a diversos casos de teste, desde que previamente se tenha colocado informao de debug em cada bloco de cdigo. Os blocos de cdigo em que no tiver sido produzida nenhuma informao de debug, so provavelmente os blocos de cdigo que nunca so executados.

22
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h>

Parte I Variveis e Estruturas de Controlo

int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); if(mes==2) if(ano%400==0 || ano%4==0 && ano%100!=0) printf("29"); else printf("28"); else if(mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12) printf("31"); else if(mes!=2) printf("30"); else printf("erro"); }

O cdigo acima uma rplica do Programa 3-3 com a mesma funcionalidade, no entanto contendo os 3 erros indicados. A indentao na linha 5 a 4 espaos, mas na linha 10 quando o condicional est debaixo do condicional na linha 9, no sobe a indentao. O leitor nessa situao no sabe se o que aconteceu foi um esquecimento da instruo para o primeiro condicional, ou se o que pretendido realmente um condicional sobre o outro. Os outros dois erros, o printf e o scanf esto na mesma linha, e a linha 20 nunca tem hiptese de ser executada, j que o condicional da linha 17 sempre verdadeiro, uma vez que complementar do condicional no mesmo nvel na linha 9. Os condicionais um conceito relativamente simples de interiorizar, muito provavelmente no ter dificuldade em identificar a necessidade de execues alternativas. No entanto, na escrita do cdigo muito frequente cometer erros nas expresses lgicas, e ao ler pensa que a expresso tem um efeito quando na realidade tem outro. A utilizao de expresses simples, e bons nomes nas variveis, o aconselhado para que o cdigo tenha uma boa leitura, e consequentemente tenha menos erros.

4. Ciclos

23

4. CICLOS
Um programa serve para executar um conjunto de instrues. No entanto, com o que aprendemos at agora, cada linha de cdigo executada quanto muito uma vez. Isto limita muito a utilidade de programas. Consideremos que se pretende calcular a soma dos primeiros 4 quadrados, no conhecendo nenhuma expresso matemtica para obter o valor directamente. O programa seguinte resolveria o problema:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { int soma=0; soma=soma+1*1; soma=soma+2*2; soma=soma+3*3; soma=soma+4*4; printf("Soma dos primeiros 4 quadrados: %d", soma); } Programa 4-1 Soma dos primeiros 4 quadrados naturais

Execuo do programa:
C:\>somaquadrados Soma dos primeiros 4 quadrados: 30

Execuo passo-a-passo novidades.

no

apresenta

Problemas com este programa: o Se pretender obter a soma dos primeiros 9 quadrados, temos de refazer o programa. sempre mais interessante ter um programa que pode ser utilizado numa maior gama de situaes, que um programa que serve apenas para uma situao especfica. O cdigo repetido pode at ser facilmente copivel, mas se for necessrio alterar algum pormenor, essa alterao tem de ser reproduzida em todas as cpias realizadas. A varivel soma repetida antes e depois da atribuio. A repetio da mesma entidade ou bloco de cdigo "pesa" no s ao editar e corrigir algum ponto, como tambm na leitura. O tamanho do programa cresce se pretender um clculo com um valor maior.

o o o

Vale a pena ter um programa assim? claro que sim, fazer as contas mo que no. No entanto se no existisse alternativa, um computador teria uma utilidade pouco mais do que se obtm com uma calculadora. A soluo passa por utilizar um ciclo, em que no C o mais simples o ciclo while. muito parecido com o condicional if, mas ao contrrio do if o cdigo repetido enquanto a expresso lgica for verdadeira.

24
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h>

Parte I Variveis e Estruturas de Controlo

int main() { int soma=0, i=1; while(i<=4) { soma+=i*i; i++; } printf("Soma dos primeiros 4 quadrados: %d", soma); } Programa 4-2 Soma dos primeiros 4 quadrados naturais utilizando um ciclo

Comentrios: o o O ciclo while avalia a expresso lgica, i<=4, e enquanto esta for verdadeira soma o quadrado de i e incrementa o valor de i. Utilizou-se o operador +=, que em vez de efectuar uma atribuio soma varivel esquerda o valor da expresso direita. Existem tambm os operadores equivalentes =, *=, /=, %=. Para incrementar o valor de i, existe ainda um operador especial ++, j que se pretende somar apenas o valor 1. Existe tambm o operador inverso -- que subtrai uma unidade. Neste programa, se for necessrio troca-se facilmente a constante 4 por uma varivel que introduzida pelo utilizador, ficando o programa mais genrico. O nmero de instrues no depende agora do argumento pretendido.

o o

A execuo passo-a-passo um pouco mais longa. importante que veja com ateno esta execuo passo-a-passo, para compreender o funcionamento do ciclo. Repare que cada linha no ciclo foi executada vrias vezes, apenas os valores que as variveis tinham quando essas linhas foram executadas que eram diferentes. Como que o ciclo foi construdo? Tnhamos um conjunto de operaes seguidas para realizar, e criou-se uma varivel auxiliar para contar o nmero de vezes que o ciclo foi executado, e tambm para controlar a paragem do ciclo. Pretendamos 4 passos, colocou-se a condio de paragem no 4 e incrementou-se em cada ciclo o valor da varivel. Este tipo de varivel praticamente forosa em todos os ciclos, tendo merecido a distino de se chamar uma varivel iteradora. habitual utilizar-se os nomes i e j para variveis iteradoras. Vamos agora apresentar um outro exemplo.

4. Ciclos Pretende-se saber o nmero associado aos caracteres de 'a' a 'z'.


1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { char c='a'; while(c<='z') { printf("\nLetra %c = %d",c,c); c++; } } Programa 4-3 Imprime o cdigo numrico correspondente a cada letra

25

Comentrios: o Neste caso temos a varivel iteradora como um carcter (tipo char). Como o tipo char tambm um inteiro pequeno, no h problema em utilizar desigualdades com caracteres, incluindo c<='z'. As letras tm o cdigo numrico por ordem. No printf, aps as aspas temos \n. A \ nas strings significa que se segue um carcter especial, neste a mudana de linha. O \n traduzido no cdigo final para um valor que interpretado no sistema operativo como uma instruo para reposicionar o cursor na linha seguinte. Desta forma as letras so colocadas em linhas distintas. O printf utiliza o que j vimos para a letra 'a', colocando a varivel char no printf tanto em formato de carcter como em formato numrico.

Execuo do programa:
C:\>caracteres Letra Letra Letra Letra Letra Letra Letra Letra ... Letra Letra Letra Letra a b c d e f g h w x y z = = = = = = = = = = = = 97 98 99 100 101 102 103 104 119 120 121 122

Apresenta-se a execuo passo-a-passo com os primeiros 7 passos. Estes nmeros correspondem ao cdigo associado a cada letra, que ir ser explicado quando se falar de vectores e strings. Vamos agora dar um exemplo mais complexo. Pretendese saber para um determinado nmero inteiro K, quantos pares de nmeros inteiros (A,B) existem que verifiquem as seguintes condies: A+B<=K, A*B<=K, A>=1 e B>=1.

26

Parte I Variveis e Estruturas de Controlo


1 #include <stdio.h> 2 3 int main() 4 { 5 int A,B,K,contagem; 6 printf("Indique K: "); 7 scanf("%d",&K); 8 contagem=0; 9 A=1; 10 while(A<=K) 11 { 12 B=1; 13 while(B<=K) 14 { 15 if(A+B<=K && A*B<=K) 16 contagem++; 17 B++; 18 } 19 A++; 20 } 21 printf("Total: %d",contagem); 22 } Programa 4-4 Calculo do nmero de pares de inteiros que respeitam uma determinada condio

Comentrios: o o O programa necessita iterar em A e em B. Como A e B tm de ser maior ou igual a 1, e nenhum pode ser maior que K, cada ciclo tem de ser feito entre 1 e K. A expresso lgica colocada conforme a definio. A expresso lgica do condicional poderia ser utilizada directamente no segundo ciclo. No entanto nesse caso seria conveniente um bom comentrio a explicar a mudana, dado que embora mais eficiente o cdigo fica mais difcil de compreender. Com um ciclo dentro do outro, as variveis A e B a comearem em 1 e a acabarem em K, todos os pares (A,B) foram verificados. Repare que a inicializao de B=1; est dentro do ciclo da varivel A. Cada varivel (A e B) so inicializadas antes do ciclo, utilizadas no condicional do ciclo, e actualizadas (incrementadas) no final do seu ciclo.

Execuo do programa:
C:\>somamul Indique K: 10 Total: 25

Vamos ver a execuo passo-a-passo para K=3. Este um exemplo j bastante longo, mas importante que o veja com algum cuidado, dado que o primeiro ciclo dentro de outro ciclo. Escolha um passo aleatoriamente, e verifique se consegue construir o passo seguinte. Se compreender esta execuo passo-apasso, muito provavelmente no ter problemas em construir ciclos. Os ciclos so fceis de identificar, tal

4. Ciclos como os condicionais, mas quando necessrio um ciclo dentro do outro, ou mesmo ainda um terceiro ciclo, a compreenso do cdigo baixa dado que necessrio em cada ciclo considerar os ciclos sobre o qual est a correr. Cada ciclo tem uma expresso lgica, que por si s pode ser complexa tal como nos condicionais, mas como tem uma varivel iteradora para fazer variar a expresso lgica, leva a que deva ser dada a mxima ateno nos ciclos. Erros comuns mais associados a este captulo: o directamente

27

Utilizao do goto o Forma: Numa dada instruo, sabe-se a condio pretendida para voltar para cima ou saltar para baixo. suficiente colocar um label no local desejado, e colocar o goto dentro do condicional. Este tipo de instruo pode ser motivado pelo uso de fluxogramas o Problema: Perde-se nada mais nada menos que a estrutura das instrues. A utilizao ou no desta instruo, que define se a linguagem estruturada, ou no estruturada (por exemplo o Assembly). Se de uma linha de cdigo se poder saltar para qualquer outra linha de cdigo, ao analisar/escrever cada linha de cdigo, tem que se considerar no apenas as linhas que a antecedem dentro do bloco actual, como todas as linhas de cdigo, dado que de qualquer parte do programa pode haver um salto para esse local. Para compreender 1 linha de cdigo, necessrio considerar todas as instrues no programa. A complexidade do programa nesta situao cresce de forma quadrtica com o nmero de linhas. o Resoluo: Utilizar as estruturas de ciclos disponveis, bem como condicionais, e funes5. Se o salto para trs dentro da mesma funo certamente um ciclo o que pretendido, mas se para a frente, provavelmente um condicional. Se um salto para uma zona muito distante, ento provavelmente uma funo que falta. No caso de utilizar fluxogramas, deve considerar primeiro utilizar o seu tempo de arranque vendo execues passo-a-passo de forma a compreender o que realmente um programa, sem segredos, e resolver exerccios Instrues parecidas seguidas o Forma: Quando necessrio uma instruo que parecida com a anterior, basta seleccion-la e fazer uso de uma das principais vantagens dos documentos digitais: copy/paste

O conceito de funo introduzido no captulo seguinte

28 o

Parte I Variveis e Estruturas de Controlo Problema: O cdigo ficar muito pesado de ler, podendo tambm prejudicar a escrita. sinal que est a falhar um ciclo, em que as pequenas mudanas entre instrues, em vez de serem editadas e alteradas, essa alterao colocada dependente da varivel iteradora, fazendo numa s instruo a chamada a todas as instrues parecidas, debaixo de um ciclo. O cdigo no s fica mais simples de ler, como se for necessria outra instruo basta alterar a expresso lgica de paragem do ciclo, em vez de um copy/paste e edio das alteraes Resoluo: Se encontra situaes destas no seu cdigo, estude alternativas para utilizar ciclos

O segundo erro foi cometido no Programa 4-1, de forma a motivar os ciclos. O primeiro erro est cometido no programa seguinte, que ser tambm o nico programa neste texto a utilizar a instruo goto.
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main() { int soma=0, i=1; loop: soma+=i*i; i++; if(i<=4) goto loop; printf("Soma dos primeiros 4 quadrados: %d", soma); }

Tem que existir um label na linha 6 de forma a utiliz-lo na instruo goto da linha 10. Os labels so identificadores a terminarem com dois pontos. Cdigo deste tipo nem sequer pode ter uma indentao com algum significado, j que as instrues nas linhas 7 e 8 no podem estar indentadas, no entanto esto sobre um ciclo feito com if/goto. imperativo que realize agora actividades formativas para consolidar os conceitos introduzidos. Nesta altura est em condies de fazer todos os exerccios, no entanto ir sentir dificuldades nos azuis e vermelhos, o que normal, dado que fazem uso mais abusivamente dos conceitos introduzidos, que necessitam tempo de maturao e consolidao.

1) Exerccios

29

1) Exerccios
Notas gerais a ter em ateno em todos os exerccios:

Escreva sem acentos; Utilize para as variveis inteiras, o tipo int, confirmando que tem 4 bytes no exerccio olamundosizeof.c, caso contrrio utilize o tipo equivalente; Utilize para as variveis reais a preciso dupla, o tipo double; Durante o desenvolvimento do programa, imprima resultados parciais, de forma a garantir o que o programa faz, eventualmente comentando esse cdigo na verso final; O separador decimal da linguagem C o ponto final, pelo que ser utilizado nos resultados dos exerccios (12.435 correcto, 12,435 incorrecto), mas na introduo dos dados e impresso dos resultados, poder variar conforme as configuraes no computador; Se no consegue resolver um exerccio, pense numa varivel auxiliar que lhe d jeito; Existem dicas adicionais sobre alguns exerccios no anexo Exerccios: Dicas, Respostas e Resolues, bem como forma de validar a resposta de um exerccio sem ver uma resoluo, e resolues dos exerccios para comparar aps resolv-los.

olamundosizeof.c
Faa um programa que coloque Ol Mundo! em bom portugus (com acentos), e que indique o tamanho em bytes (operador sizeof) dos seguintes tipos de dados: char; short; int; long; long long; float; double; long double. Notas: Se os caracteres no lhe aparecerem bem na linha de comando, antes de executar o programa execute o comando C:\>chcp 1252

Execuo de exemplo:
C:\>chcp 1252 Active code page: 1252 C:\>olamundosizeof Ol Mundo! sizeof(char): xx sizeof(short): xx sizeof(int): xx sizeof(long): xx sizeof(long long): xx sizeof(float): xx sizeof(double): xx sizeof(long double): xx

Pergunta: qual a soma dos tamanhos dos tipos pedidos compilado no tcc?

30

Parte I Variveis e Estruturas de Controlo

soma.c
Somar os primeiros N nmeros inteiros, sendo N definido pelo utilizador:

i
i =1

Notas: Escreva sem acentos neste e nos restantes exerccios, para que o cdigo funcione sempre correctamente mesmo sem mudar o cdigo de pgina para 1252. Durante o desenvolvimento do programa, imprima resultados parciais, de forma a garantir o que o programa faz, eventualmente comentando esse cdigo na verso final, dado que esse texto poder fazer sentido apenas para o programador e no para o utilizador.

Execuo de exemplo:
C:\>soma Calculo da soma dos primeiros N numeros. Indique N:10 adicionar adicionar adicionar adicionar adicionar adicionar adicionar adicionar adicionar adicionar Total: 55 1, parcial 1 2, parcial 3 3, parcial 6 4, parcial 10 5, parcial 15 6, parcial 21 7, parcial 28 8, parcial 36 9, parcial 45 10, parcial 55

Pergunta: qual a soma dos primeiros 21090 nmeros inteiros?

hms.c
Faa um programa que leia as horas, minutos e segundos, e calcule o nmero de segundos que passaram desde o incio do dia. Notas: No faa verificao da validade dos parmetros de entrada

Execuo de exemplo:
C:\>hms Calculo do numero de segundos desde o inicio do dia. Hora: 2 Minuto: 15 Segundos: 30 Numero de segundos desde o inicio do dia: 8130

1) Exerccios Pergunta: qual a resposta no caso de se colocar a 25 hora, o minuto 100 e o segundo 200?

31

produto.c
Multiplicar os primeiros N nmeros inteiros positivos (factorial de N), sendo N definido pelo utilizador:
N

N!= i
i =1

Execuo de exemplo:
C:\>produto Calculo do produto dos primeiros N numeros. Indique N:5 Factorial(1)=1 Factorial(2)=2 Factorial(3)=6 Factorial(4)=24 Factorial(5)=120 Resultado: 120

Pergunta: qual o factorial de 12?

arranjos.c
Calculo dos arranjos de N, R a R: multiplicar os nmeros de N-R+1 at N:
N N! A( N , R ) = = i (N R)! i=+1 N R

Notas: Ateno que R tem de ser menor que N Os arranjos de 3 elementos {A, B, C}, 2 a 2, so os seguintes 6: (A,B); (A,C); (B,A); (B,C); (C,A); (C,B).

Execuo de exemplo:
C:\>arranjos Calculo dos arranjos de N, R a R: Indique N:5 Indique R:3 i=3; arranjos=3 i=4; arranjos=12 i=5; arranjos=60 Resultado: 60

Pergunta: qual o resultado retornado dos arranjos de 20, 8 a 8?

32

Parte I Variveis e Estruturas de Controlo

somadigitos.c
Calcule a soma dos quadrados dos dgitos de um nmero introduzido pelo utilizador. Notas: Pode obter o valor do dgito mais baixo, calculando o resto da diviso por 10. Mostre o resultado parcial, neste e nos restantes exerccios.

Execuo de exemplo:
C:\>somadigitos Calculo da soma do quadrado dos digitos de um numero: Numero: 1234 n=1234; soma=16 n=123; soma=25 n=12; soma=29 n=1; soma=30 Resultado: 30

Pergunta: se o utilizador introduzir o nmero 856734789, qual o resultado do programa?

fibonacci.c
Calcular o valor da funo fibonacci6, para um dado argumento N. Para N=1 ou 2, deve retornar N, caso contrrio retorna a soma dos dois valores anteriores:

n ,n 2 F (n ) = F (n 1) + F (n 2) , n > 2
Notas: Utilize duas variveis auxiliares.

Execuo de exemplo:
C:\>fibonacci Calculo do valor da funcao Fibonacci: Indique N:6 Fib(3)=3 Fib(4)=5 Fib(5)=8 Fib(6)=13 Resultado: 13

Pergunta: qual o valor do programa para N=40?

definio da funo Fibonacci no padro

1) Exerccios

33

combinacoes.c
Calcule as combinaes de N, R a R. A frmula idntica dos arranjos, multiplicar de N-R+1 at N, mas tem de se dividir pelo factorial de R:

C (N , R ) =
Notas:

N N! = i (N R )!R! i= N R+1

i
i =1

Se efectuar as multiplicaes e s dividir no final, rapidamente ultrapassa o limite do inteiro. Se multiplicar e depois dividir em cada passo, consegue obter valores correctos para uma maior gama de nmeros. As combinaes de 3 elementos {A, B, C}, 2 a 2, so as seguintes 3: {A,B}; {A,C}; {B,C}. Notar que relativamente aos arranjos, neste caso a ordem no interessa.

Execuo de exemplo:
C:\>combinacoes Calculo das combinacoes de N, R a R: Indique N:5 Indique R:3 1*3=3/1=3 3*4=12/2=6 6*5=30/3=10 Resultado: 10

Pergunta: qual o nmero de combinaes de 26, 13 a 13?

euler.c
Calcular o nmero de Euler e , atravs da utilizao da srie de Taylor para e x quando x = 1 (soma do inverso dos factoriais):

e=
Notas:

1 n=0 n!

Considere o factorial de zero como sendo 1 Utilize a preciso dupla para valores reais, neste e em outros exerccios Na funo printf pode imprimir um nmero real em notao cientfica, e especificando a preciso a 16 dgitos utilizando a string de formatao: %.16g

34 Execuo de exemplo:

Parte I Variveis e Estruturas de Controlo

C:\>euler 0: 1 1: 2 ... 19: 2.7183 20: 2.7183 Resultado: 2.71828182845xxxx xxxx

Pergunta: qual o valor com preciso 16, em notao cientfica, da execuo da frmula acima com K=20?

trocos.c
Faa um programa que receba um montante em euros (com cntimos), e que determina o menor nmero de moedas de cada tipo necessrio para perfazer esse montante. Pode utilizar moedas de euros de todos os valores disponveis (2, 1, ...). Notas: Deve efectuar os arredondamentos para a unidade mais prxima, no caso de o utilizador introduzir um valor com preciso abaixo do cntimo.

Execuo de exemplo:
C:\>trocos Introduza um montante em euros, podendo ter centimos: 1.79 1 euro: 1 50 centimos: 1 20 centimos: 1 5 centimos: 1 2 centimos: 2

Pergunta: para devolver 19.99 euros, quantas moedas so retornadas?

primo.c
Faa um programa que verifica se um determinado nmero N um nmero primo. Um nmero primo se divisvel apenas por ele prprio e pela unidade. Se no for primo deve identificar o menor nmero pelo qual divisvel. Notas: suficiente testar at raiz quadrada de N (pode utilizar a funo sqrt da biblioteca math.h) Em vez de calcular a raiz quadrada de N, pode calcular o quadrado do divisor.

1) Exerccios Execuo de exemplo:


C:\>primo Funcao que verifica se um numero N e' primo: Indique N:99 2 Numero divisvel por 3 C:\>primo Funcao que verifica se um numero N e' primo: Indique N:97 2 3 4 5 6 7 8 9 Numero primo!

35

Pergunta: Some os nmeros retornados dos seguintes nmeros (no caso dos nmeros primos no some nada): 241134319; 241234319; 13212311.

triplasoma.c
Dado um inteiro positivo N, escrever todas as decomposies distintas possveis como soma de trs inteiros positivos (considerar iguais as triplas com os mesmos valores mas por outra ordem). Calcular tambm o nmero de somas distintas. Notas: Assumir que os nmeros mais altos aparecem sempre primeiro.

Execuo de exemplo:
C:\>triplasoma Escreva um numero para decompor em somas de tres parcelas. Numero:7 5+1+1 4+2+1 3+3+1 3+2+2 Numero de somas: 4

Pergunta: quantas somas existem para N=1000?

pi.c
Calcular o valor de com base na frmula de Ramanujan:

Notas:

2 2 K (4k )! (1103 + 26390k ) 9801 k =0 (k!)4 396 4 k

Utilize preciso dupla Pode utilizar a funo sqrt para calcular a raiz quadrada, da biblioteca math.h

36 Execuo de exemplo:
C:\>pi

Parte I Variveis e Estruturas de Controlo

Valor de PI (x iteracoes): 3.14159265xxxxxxxx x xxxxxxxx

Pergunta: qual o valor de com preciso 17 em notao cientfica, para K=2?

formularesolvente.c
Faa um programa que pea os coeficientes de um polinmio do segundo grau, e retorna as razes reais, caso existam. Adicionalmente o programa deve retornar todos os conjuntos de coeficientes inteiros, que tm apenas razes inteiras reais. Os coeficientes esto entre -K e K no tendo nenhum coeficiente nulo (K introduzido pelo utilizador e um inteiro pequeno). Relembrase a frmula resolvente:

b b2 4ac ax + bx + c = 0 x = 2a
2

Notas: Para calcular a raiz quadrada, utilize a funo sqrt, disponvel na biblioteca math.h Faa trs ciclos um dentro do outro, um ciclo por cada um dos coeficientes, e varie a varivel iteradora entre -K e K.

Execuo de exemplo:
C:\>formularesolvente Equacao do segundo grau a*x^2+b*x+c=0. Indique a b c: 2 4 2 Delta: 0.000000 A equacao tem uma raiz unica, x=-1.000000 Calculo de coeficientes entre -K e K inteiros nao nulos, com razes inteiras. Introduza K:2 Coeficientes de -2 a 2 inteiros nao nulos, com razes inteiras: [-1 -2 -1] [-1 -1 2] [-1 1 2] [-1 2 -1] [1 -2 1] [1 -1 -2] [1 1 -2] [1 2 1] Total: 8

Pergunta: quantas equaes do segundo grau existem, com coeficientes entre -10 e 10 inteiros no nulos, e com razes inteiras?

PARTE II FUNES, VECTORES E RECURSO


Apenas com a Parte I possvel fazer programas, mas no h nenhuma ferramenta de controlo de complexidade. O cdigo medida que cresce requer cada vez mais ateno para o escrever e manter. Na Parte II vamos dar uma ferramenta da programao que permite controlar a complexidade de um programa: a abstraco funcional. O bom uso desta ferramenta leva a que a nossa ateno apenas tenha de estar a cada momento centrada num reduzido conjunto de linhas de cdigo, e mesmo assim garante que toda a funcionalidade do programa satisfeita. Desta forma possvel escrever programas de qualquer dimenso. Sero apresentados tambm os vectores, de forma a poder lidar com grande volume de variveis, bem como alternativas mais elegantes para ciclos e condicionais. Aps a realizao da Parte II, ficar com bases slidas para escrever programas de qualquer dimenso, sem grandes limitaes.

40

Parte II Funes, Vectores e Recurso

5. FUNES
O termo "funo" em portugus muito lato, aplicvel a diversas entidades na linguagem C sejam instrues sejam variveis. Tudo tem uma funo, caso contrrio no estaria na linguagem. As atribuies tm a funo de atribuir o valor de uma expresso a uma varivel, os condicionais tm a funo de executar uma instruo mediante o resultado de uma expresso lgica, os ciclos tm a funo de executar um conjunto de instrues enquanto uma expresso lgica se mantiver verdadeira, e as variveis tm a funo de manter um valor de forma a este ser utilizado em expresses, ou ser trocado por outro atravs de uma atribuio. Na linguagem C o termo funo tem no entanto um sentido muito concreto, e deve o seu nome precisamente devido importncia que tem. Uma funo um bloco de cdigo que pode ser chamado de qualquer parte do programa, quantas vezes se quiser. Por chamar, significa que se tem uma instruo a ordenar o computador a executar o bloco de cdigo correspondente funo identificada, e s depois continuar com a execuo das restantes linhas de cdigo. Vamos clarificar este ponto com o seguinte exemplo, ainda sem utilizar funes. Pretende-se um programa que oferece um menu ao utilizador de 3 opes, e uma opo de sada do programa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main() { int opcao; /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("\nOpcao: "); scanf("%d",&opcao); while(opo>0) { /* ver qual a opo */ if(opcao==1) printf("Opcao escolhida A"); else if(opcao==2) printf("Opcao escolhida B"); else if(opcao==3) printf("Opcao escolhida C"); else printf("Opcao invalida"); /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("\nOpcao: "); scanf("%d",&opcao); } printf("Fim do programa."); } Programa 5-1 Escolha de opes num menu

Este programa utiliza apenas conceitos j introduzidos. Nada de novo.

5. Funes Execuo do programa:


C:\>menu Menu: 1 - opcao A 2 - opcao B 3 - opcao C 0 - sair Opcao: 2 Opcao escolhida B Menu: 1 - opcao A 2 - opcao B 3 - opcao C 0 - sair Opcao: 5 Opcao invalida Menu: 1 - opcao A 2 - opcao B 3 - opcao C 0 - sair Opcao: 0 Fim do programa.

41

O programa funcionou de acordo com o esperado. No entanto o cdigo correspondente ao menu est repetido tanto no incio do programa, como no ciclo. Isto porque para entrar no ciclo necessrio que o menu j tenha sido mostrado, e o utilizador j tenha introduzido uma opo. O cdigo repetido no teve grande trabalho a ser escrito, foi apenas uma operao de Copy/Paste. Se fosse necessrio em mais partes do programa, fazia-se mais cpias desse cdigo. Esta operao to vulgar em aplicaes informticas, no entanto proibida em programao. Porqu? Para fazer uma alterao numa parte do cdigo duplicada, tem que se conhecer as partes para a qual o cdigo foi copiado para reproduzir a alterao nessas partes; Se o cdigo duplicado sofrer pequenas alteraes no aplicveis ao cdigo original, torna-se complicado manter ambos os cdigos, dado que algumas das futuras correces no sero aplicveis a ambos os cdigos; O custo de leitura poder ser at maior que o custo de escrita, o que dificulta a manuteno do cdigo; Um bloco de cdigo pode funcionar bem numa zona, mas aps ser copiado para outra zona, devido s instrues precedentes ou sucessoras, pode no ter o funcionamento esperado.

Qual a soluo? Fazer uma funo, ou seja, um bloco de cdigo com as instrues correspondente ao menu, e chamar essa funo nos dois locais onde o menu necessrio.

42
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h>

Parte II Funes, Vectores e Recurso

int Menu() { int opcao; /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("nOpcao: "); scanf("%d",&opcao); /* retornar a opo seleccionada */ return opcao; } int main() { int opcao; opcao=Menu(); while(opcao>0) { /* ver qual a opo */ if(opcao==1) printf("Opcao escolhida A"); else if(opcao==2) printf("Opcao escolhida B"); else if(opcao==3) printf("Opcao escolhida C"); else printf("Opcao invalida"); opcao=Menu(); } printf("Fim do programa."); } Programa 5-2 Escolha de opes num menu, utilizando uma funo

Dentro da funo main, chama-se a funo Menu(), e atribui-se o valor retornado varivel opo que j era utilizada para esta funo. Este procedimento repetido nos dois locais onde necessrio o menu. Os parntesis curvos aps Menu, significam que o identificador uma funo. O cdigo correspondente funo est definido em cima, e idntico ao formato da funo main, mas com o nome Menu. Este nome foi dado de forma a ficar o mais perto possvel relativamente funcionalidade que implementa. Dentro da funo Menu no existe a varivel opo, que est declarada na funo main, pelo que necessrio declarar tambm aqui a varivel opo para a poder utilizar. So variveis distintas, embora tenham o mesmo nome, uma est na funo main e outra na funo Menu. A funo Menu na ltima instruo return opcao; retorna o valor da varivel opcao, sendo esse valor utilizado no local onde a funo foi chamada. Na execuo passo-a-passo, a chamada funo Menu leva a uma criao de uma varivel, que o nome da funo chamada, sendo as variveis criadas dentro da funo direita, sendo destrudas quando a funo retorna. Verifique que no passo 6 a varivel opcao da funo Menu ficou com o valor atribudo, mas apenas no passo 8 que este valor passou para a varivel opcao da funo main.

5. Funes A execuo de ambos os programas igual, mas os problemas apontados j no esto presentes no segundo programa: Para fazer uma alterao, basta editar o cdigo da funo e a alterao fica vlida para todos os locais em que a funo utilizada; Se for necessrio utilizar a funo Menu com ligeiras diferenas, pode-se utilizar argumentos e mantm-se o cdigo num s local. O custo de leitura mais baixo, j que no h linhas repetidas; O cdigo da funo Menu funciona independentemente do local onde chamado, j que apenas depende das instrues que esto na funo. Se fosse copiado, necessitava de estar declarada uma varivel opcao do tipo int, que no estivesse a ser utilizada para outros fins.

43

A principal vantagem das funes no no entanto evitar os problemas acima descritos inerentes ao Copy/Paste, mas sim possibilitar a abstraco funcional. No h nenhuma outra ferramenta na informtica, mais poderosa que a abstraco funcional. O termo "abstraco" em portugus significa que nos podemos abstrair de parte do problema. Um problema dividido fica mais simples, num caso extremo fica-se com problemas muito pequenos que so facilmente implementados. precisamente esse caso extremo que se pretende, uma vez que no s a capacidade do programador limitada, como quanto mais simples um programa estiver escrito, mais facilmente lido e menos erros ter. Na programao aplica-se a abstraco s funes, comeando pela funo main, que deve implementar o problema completo. Esta pode ir sendo desagregada em funes mais simples, at se obter dimenses de funes razoveis, facilmente implementveis e de leitura simples. Ao implementar uma funo s interessa saber o que a funo tem de fazer. O resto do cdigo no tem qualquer relevncia, podemos abstrair-nos dele, nem sequer nos interessa saber onde a funo ser chamada. Ao utilizar uma funo s interessa saber o que a funo faz. Como a funo est implementada no tem qualquer relevncia, podemos abstrair-nos disso, nem sequer interessa saber se a funo utiliza condicionais ou ciclos, se tem muitas ou poucas linhas de cdigo. Desta forma, mesmo um programador muito limitado poder implementar um problema muito complexo, aparentemente fora do alcance de alguns. Na verdade, por vezes acontece que um programador sem restries, ao fazer as opes na escolha das funes a utilizar adaptadas para a sua maior capacidade de programao, arrisca-se a ficar com funes com grande

44

Parte II Funes, Vectores e Recurso

nmero de variveis, condicionais e ciclos, levando muito mais tempo a implementar e testar e ficando o cdigo de menor qualidade que o programador mais limitado. Este, ao no conseguir implementar funes acima da sua capacidade, procura sempre dividir o problema at que este tenha uma dimenso apropriada, pelo que encontrar sempre uma tarefa facilitada. Vamos voltar ao programa que calcula se um ano ou no bissexto, mas utilizando funes (ver Programa 3-2).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int Bissexto(int ano) { return ano%400==0 || ano%4==0 && ano%100!=0; } int main() { int ano; printf("Indique ano: "); scanf("%d", &ano); /* teste de ano bissexto */ if(Bissexto(ano)) printf("Bissexto"); else printf("Normal"); } Programa 5-3 Determina se um ano normal ou bissexto utilizando funes

Este programa manteve o funcionamento igual ao original, mas foi feita uma funo Bissexto para obter o resultado da expresso lgica que indica se um ano ou no bissexto. Esta funo recebe no entanto um argumento, a varivel ano. A funo poderia ela prpria pedir o valor do ano, e nesse caso no necessitava do argumento, mas assim no poderia ser utilizada numa parte do cdigo onde no tivesse interesse pedir o ano. Com o argumento o cdigo funciona de acordo com o valor recebido, podendo ser utilizado em maior nmero de situaes. Os argumentos de uma funo so colocados entre os parnteses, tanto na declarao como na utilizao. Se houver mais que um argumento, estes tm de ser separados por vrgulas, tal como j temos vindo a fazer no printf e no scanf. Na funo Bissexto tem uma varivel local ano, uma vez que os argumentos de uma funo so tambm variveis locais funo. Na execuo passo-a-passo pode-se ver no passo 5 a declarao e atribuio do parmetro da funo. Os parmetros das funes tm de ser declaradas como qualquer outra varivel local.

5. Funes

45

Neste caso pode-se considerar que no houve grande vantagem em fazer a funo Bissexto, no entanto, atendendo a que h outro projecto dos dias do ms que necessita desta funo, essa vantagem realmente existe, dado que desta forma possvel passar a funo de um projecto para o outro sem qualquer alterao da funo, nem ser necessrio rever a implementao do cdigo da funo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> int Bissexto(int ano) { return ano%400==0 || ano%4==0 && ano%100!=0; } int DiasDoMes(int mes, int ano) { if(mes==2) { /* teste de ano bissexto */ if(Bissexto(ano)) return 29; else return 28; } else if(mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12) { return 31; } else { return 30; } } int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); printf("%d",DiasDoMes(ano,mes)); } Programa 5-4 Determina o nmero de dias de um ms/ano utilizando funes

Este programa tem a mesma funcionalidade que o Programa 3-3. Execues passo-a-passo:

46

Parte II Funes, Vectores e Recurso

Comecemos pela funo main. Esta funo ficou muito curta quando comparado com o cdigo original, dado que o calculo do dia do ms feito numa funo que retorna o nmero de dias. A funo main apenas tem de ler os argumentos e chamar a funo respectiva. Para chamar a funo DiasDoMes no interessa sequer saber que esta utiliza a funo Bissexto. A funo DiasDoMes tem os testes que estavam na funo main do cdigo original, mas em vez do condicional mais complexo do ano bissexto, tem uma chamada funo Bissexto. Esta funo tem dois parmetros, ao contrrio da funo Bissexto. Desta forma move-se complexidade do problema para outra funo, tornado a leitura mais simples de cada parte do cdigo. A funo Bissexto foi copiada do outro programa, portanto reutilizada. Quanto mais genrica for a funo maior o seu potencial para ser til tanto em vrias zonas do cdigo, como em outros programas. Ao copiar cdigo de um programa para outro pode-se utilizar o cdigo copiado com alguma garantia, dado que j foi testado e utilizado em outro programa, e provvel que contenha poucos erros. Erros comuns mais directamente associados a este captulo: Funes com parmetros no nome o Forma: Para distinguir uma constante importante na funo, basta colocar o valor do parmetro no nome da funo, por exemplo funcao12, para a funo que retorne o resto da diviso por 12. Desta forma evita-se utilizar um argumento o Problema: Se distingue uma constante importante no nome da funo, ento deve colocar a constante como argumento da funo, ficando a funo a funcionar para qualquer constante. Caso no o faa, corre o risco de ao lado de funcao12, ser necessrio a funcao4, funcao10, etc., ficando com instrues parecidas. No poupa sequer na escrita dado que coloca no nome da funo a constante que utiliza na funo o Resoluo: Se tem casos destes no seu cdigo, deve fazer uma troca simples: substituir todas as constantes dependentes de 12 pelo argumento, ou uma expresso dependente do argumento. Instrues parecidas no seguidas o Forma: Quando necessrio uma ou mais instrues que so parecidas com outras que j escritas noutra parte do cdigo, basta seleccionar e fazer uso de uma das principais vantagens dos documentos digitais: copy/paste o Problema: O copy/paste vai dificultar a leitura, e tambm a escrita tem de ser com muito cuidado, para poder editar as diferenas. Se esta situao ocorrer, uma

5. Funes

47

indicao clara que necessria uma funo com as instrues que so parecidas e devem ser reutilizadas. Nos argumentos da funo deve ir informao suficiente para que a funo possa implementar as diferenas o Resoluo: Se aconteceu no seu cdigo, deve estudar quais as diferentes alternativas para utilizao de funes que tem, de forma a garantir que no lhe escapam hipteses antes de optar Funes especficas o Forma: Para uma situao necessria uma funo concreta, pelo que essa funo que definida, utilizando as constantes necessrias dentro dessa funo o Problema: Se as funes so muito especficas, no podem ser reutilizadas, tanto no mesmo programa, como em outros programas. Quanto mais genrica a funo, maior o seu grau de reutilizao, sendo mais simples de manter e detectar algum problema o Resoluo: Reveja todas as constantes que tem numa funo especfica, e passe as constantes que sejam passveis de serem mudadas para argumentos da funo, de forma a aumentar as possibilidades da funo ser til sem alteraes em outras situaes Declaraes de variveis fora do incio das funes o Forma: As variveis devem ser declaradas o mais perto possvel do local onde so utilizadas, e como alguns compiladores de C permitem a declarao fora do incio das funes, h que aproveitar o Problema: verdadeira a frase, mas no para quem se inicie na programao, nem para a linguagem C. A generalidade dos compiladores de C permite declarao das variveis em qualquer parte do cdigo, mas nem todos, pelo que perde compatibilidade. Mais relevante a m influncia que tal situao ter, facilitando a existncia de funes grandes. Com as variveis declaradas em cada bloco, no ir sofrer os efeitos de funes grandes to intensamente, o que o pode levar a no sentir a necessidade de criar funes o Resoluo: Todas as declaraes fora do incio das funes passam para o incio das funes. Reveja os nomes das variveis se necessrio

O Programa 5-1 tem um exemplo de um erro de instrues parecidas no seguidas, de forma a motivar a utilizao de funes. Um exemplo de funes com parmetros no nome no Programa 5-4 existir uma funo DiasDoMes2010, que recebe um ms e retorna o nmero de dias desse ms em 2010. Esta verso tambm um exemplo de uma funo especfica, dado que contm uma constante que poderia estar num argumento, mas a situao da constante ir para o nome da funo de maior gravidade. A existncia de uma constante interna a uma funo, justifica-se se o seu valor raramente fizer sentido ser distinto do utilizado na implementao. Estes exemplos so muito curtos, mas num conceito to importante quanto este, tem que se dar tempo para que entre devagar e seja compreendido parte da sua potencialidade. Com a experincia ficar claro quais as funes que deve criar, de forma a obter cdigo de qualidade, modular, reutilizvel, e de simples leitura. Por agora, duas caractersticas de uma boa funo: Uma funo, uma operao (no juntar alhos com bugalhos); Se no tem uma funcionalidade ou nome claro, ento no uma funo.

48

Parte II Funes, Vectores e Recurso

Reveja cdigo que tenha feito no mdulo anterior, e faa uma verso modular de cada um, utilizando funes. Da boa diviso do problema em funes resultar o menor trabalho necessrio para implement-lo, enquanto uma m diviso resulta no a inviabilizao da implementao, mas sim um aumento de trabalho necessrio. Quanto mais trabalho, provavelmente menor qualidade ter o cdigo.

6. Mais Ciclos e Condicionais

49

6. MAIS CICLOS E CONDICIONAIS


Este captulo comea em "mais", porque na verdade no introduz novos conceitos, mas sim apresenta alternativas que a linguagem C oferece para aplicar esses conceitos: ciclos e condicionais. A primeira situao refere-se constatao que a maior parte dos ciclos tem uma varivel iteradora, que controla o teste de paragem do ciclo. Essa varivel iteradora tem de ser inicializada antes do ciclo, e incrementada no final do ciclo, de forma a ficar com o valor vlido quando a expresso lgica avaliada. Exemplo disso o primeiro programa no captulo Ciclos.
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main() { int soma=0, i=1; while(i<=4) { soma+=i*i; i++; } printf("Soma dos primeiros 4 quadrados: %d", soma); } Programa 6-1 Rplica do Programa 4-2

Note-se na atribuio da varivel i ao valor 1 antes do ciclo, e da operao de incremento no final. No entanto a operao que interessa fazer a operao de soma+=i*i;. Esta situao prejudica a legibilidade do cdigo porque h duas instrues de controlo do ciclo, uma antes do ciclo e outra no ciclo, que nada tm a ver com o que se pretende fazer, apenas esto l para controlar a paragem do ciclo, perturbando a leitura. A linguagem C permite efectuar ciclos iterativos juntando a inicializao e actualizao de variveis, junto com o teste de paragem, podendo ficar tudo numa s linha, de forma a deixar as restantes linhas para instrues no relacionadas com o controlo do ciclo.
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { int soma=0, i; for(i=1; i<=4; i++) soma+=i*i; printf("Soma dos primeiros 4 quadrados: %d", soma); } Programa 6-2 Soma dos primeiros 4 quadrados naturais com o ciclo for

O cdigo acima tem a mesma funcionalidade que o original, mas todas as instrues de controlo do ciclo na linha for. O ciclo for, ao contrrio do ciclo while, aceita trs parmetros entre parnteses, mas separados por ponto e vrgulas. Na primeira zona est a inicializao, na zona central est a expresso lgica tal como no while, e na ltima zona est a actualizao da varivel iteradora. Pode-se ver que na execuo passo-a-passo, o ciclo for fica sempre em dois passos. Na primeira passagem, tem que se executar a inicializao, e num segundo passo mantendo na mesma linha, tem que se avaliar a expresso lgica. Nas restantes passagens, tem que se executar a actualizao, e de

50

Parte II Funes, Vectores e Recurso

seguida a avaliao. Para no haver confuso entre a zona do ciclo for que est a ser executada num dado passo, as zonas no executadas so substitudas por um cardinal (#). Tm que existir sempre trs parmetros no ciclo for, mesmo que um campo seja vazio, tem que se colocar o ponto e vrgula. Se houver lugar a mais que uma instruo de inicializao, estas devem estar separadas por uma vrgula, acontecendo o mesmo para a instruo de actualizao. Por exemplo, pode-se querer inicializar o valor da varivel soma no ciclo.
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main() { int soma, i; for(i=1, soma=0; i<=4; i++) soma+=i*i; printf("Soma dos primeiros 4 quadrados: %d", soma); } Programa 6-3 Utilizao alternativa do ciclo for na soma dos 4 quadrados naturais

Este programa idntico ao anterior, mas no se aconselha efectuar inicializaes nem actualizaes no ciclo for, que no digam respeito varivel iteradora e ao controle do ciclo. Caso contrrio o cdigo torna-se de mais leitura pesada. Em vez de se esperar instrues menores de controlo de ciclo, dentro do ciclo for passa a poder estar apenas instrues com significado mais relevante para o algoritmo a implementar. Quantos mais ciclos, maior so as vantagens do ciclo for relativamente ao ciclo while, como se pode ver na verso mais simples do Programa 4-4.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> int main() { int A,B,K,contagem; printf("Indique K: "); scanf("%d",&K); contagem=0; for(A=1; A<=K; A++) for(B=1; B<=K; B++) if(A+B<=K && A*B<=K) contagem++; printf("Total: %d",contagem); } Programa 6-4 Verso com ciclos for do Programa 4-4

Nesta verso do cdigo observa-se uma clara diminuio do nmero de instrues e aumento de legibilidade, dado que tudo o que trata de cada ciclo est numa s linha, e no em diversas linhas e alternado linhas de controlo de um ciclo com as do outro.

6. Mais Ciclos e Condicionais Mostra-se a execuo passo-a-passo dos primeiros 20 passos. Esta execuo, quando comparada com a verso utilizando while, pode-se verificar que no h diferena excepto na troca do for pelo while.

51

Para que no exista qualquer dvida, o ciclo for genrico:


for(inicializao; expresso lgica; actualizao) instruo;

equivalente ao ciclo while genrico:


inicializao; inicializao; while(expresso lgica) { instruo; actualizao; }

Se a expresso lgica for falsa logo no primeiro ciclo, as instrues do ciclo nunca so executadas, quer se utilize o ciclo for ou o ciclo while. Pode-se sempre alterar a expresso lgica para que seja verdadeira no primeiro ciclo, mas complicando a expresso, pelo que a linguagem C tem uma outra forma de executar ciclos, o do-while: dodo {

instruo; lgica); } while(expresso lgica);

Exemplifica-se o ciclo do-while com o exerccio do menu em Funes, que pode ser reescrito da seguinte forma:

52
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h>

Parte II Funes, Vectores e Recurso

int Menu() { int opcao; /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("\nOpcao: "); scanf("%d",&opcao); /* retornar a opo seleccionada */ return opcao; } int main() { int opcao; do { opcao=Menu(); /* ver qual a opo */ if(opcao==1) printf("Opcao escolhida A"); else if(opcao==2) printf("Opcao escolhida B"); else if(opcao==3) printf("Opcao escolhida C"); else if(opcao!=0) printf("Opcao invalida"); } while(opcao>0); printf("Fim do programa."); } Programa 6-5 Verso com ciclos do-while do Programa 5-2

Mostra-se a execuo passo-a-passo dos primeiros 20 passos. Este programa tem o mesmo funcionamento que o original, mas com a utilizao do ciclo do-while possvel chamar apenas uma s vez a funo Menu. Com esta soluo pode-se ponderar a necessidade de uma funo para o menu, uma vez que a funo chamada apenas uma s vez, e tem uma dimenso reduzida.

6. Mais Ciclos e Condicionais


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> int main() { int opcao; do { /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("nOpcao: "); scanf("%d",&opcao); /* ver qual a opo */ if(opcao==1) printf("Opcao escolhida A"); else if(opcao==2) printf("Opcao escolhida B"); else if(opcao==3) printf("Opcao escolhida C"); else if(opcao!=0) printf("Opcao invalida"); } while(opo>0); printf("Fim do programa."); } Programa 6-6 Verso removendo a funo Menu do Programa 6-5

53

Os ciclos while e do-while devem ser utilizados nos ciclos menos ortodoxos quando no h uma varivel iteradora clara, ou a expresso lgica muito complexa, de forma a no ter numa s linha do ciclo for muito longa. Nos ciclos normais, a opo pelo ciclo for resulta quase sempre em ganho de legibilidade. Os condicionais tm tambm uma alternativa cadeia ifelse utilizada no programa acima. Em situaes em que as expresses lgicas na cadeia if-else so comparaes com valores concretos, como o caso do exemplo do menu, podese utilizar o switch, que recebe uma varivel e lista-se todos os casos.

54
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h>

Parte II Funes, Vectores e Recurso

int main() { int opcao; do { /* mostrar as opes do menu */ printf("\nMenu:\n1 - opcao A\n2 - opcao B\n3 - opcao C\n0 - sair"); printf("\nOpcao: "); scanf("%d",&opcao); /* ver qual a opo */ switch(opo) { case 1: printf("Opcao escolhida A"); break; case 2: printf("Opcao escolhida B"); break; case 3: printf("Opcao escolhida C"); break; case 0: break; default: printf("Opcao invalida"); } } while(opcao>0); printf("Fim do programa."); } Programa 6-7 Verso com switch do Programa 6-6

O switch tem de ter o valor de uma expresso numrica como base, e no uma expresso lgica, sendo colocada a execuo na linha de cdigo que tiver o caso que ocorreu (case valor:). Se o valor no existir em nenhum dos casos listados, a execuo passa para a linha onde estiver "default:" se existir. Com estas regras, poderia haver problema em utilizar o switch no fosse a existncia dos comandos break; que saem nesse instante do comando switch. Se no fosse assim, no caso da opo 1 ser escolhida, o programa passaria para a linha do caso 1, e de seguida seguiria normalmente para o caso 2, 3 e a opo invlida, s porque esto nas linhas de cdigo aps a opo 1. Colocando um break; aps o fim do cdigo que trata cada opo, resolve-se o problema. Pode-se ver na execuo passo-a-passo que nos passos 5, 13 e 20, o switch passou imediatamente para a linha que trata o caso pretendido, em vez de executar vrios condicionais. Num outro exemplo podemos ver uma implementao alternativa da funo DiasDoMes, extrada do Programa 5-4:

6. Mais Ciclos e Condicionais


1 int DiasDoMes(int mes, int ano) 2 { 3 switch(mes) 4 { 5 case 2: 6 /* teste de ano bissexto */ 7 if(Bissexto(ano)) 8 return 29; 9 else 10 return 28; 11 /* meses de dias 31 */ 12 case 1: 13 case 3: 14 case 5: 15 case 7: 16 case 8: 17 case 10: 18 case 12: 19 return 31; 20 default: 21 return 30; 22 } 23 } Programa 6-8 Funo DiasDoMes com switch do Programa 5-4

55

Neste caso pretende-se identificar todos os casos de meses de 31, pelo que basta colocar os diferentes casos em cada linha, e retornar o valor correcto apenas aps a linha que identifica o ltimo ms de 31 dias. Como aqui utilizam-se comandos return, que saem imediatamente da funo, no h necessidade de utilizar a instruo break; no switch. O comando break; pode ser utilizado tambm em ciclos, para sair do ciclo actual. Existe ainda o break; comando continue; utilizvel dentro de ciclos, que permite saltar para a prxima iterao do ciclo. continue; Estes comandos no se devem no entanto utilizar com frequncia, dado que dessa forma as expresses lgicas, inicializaes e actualizaes associados ao ciclo, no controlam completamente o ciclo, sendo auxiliadas por eventuais condicionais dentro do ciclo, sobre a qual os comandos break; e continue; esto. Sendo de evitar a utilizao destes comandos, no ser dado nenhum exemplo em que tais comandos sejam teis. Erros comuns mais directamente associados a este captulo: Ciclos contendo apenas uma varivel lgica no teste de paragem o Forma: Esta estratgia consiste em utilizar nos ciclos (normalmente while), uma varivel no teste de paragem, atribuindo o valor verdadeiro e dentro do ciclo, quando for necessrio sair dele, fazer uma atribuio varivel a falso o Problema: O real teste de paragem fica diludo nas instrues que alteram esta varivel, bem como condicionais que utilizem o break, e ter algo em vez de num local em vrios, torna sempre mais complicada a leitura e manuteno de cdigo. Esta situao ocorre porque falhou na identificao do real teste de paragem. pior se necessitar de acompanhar esta varivel com uma outra varivel que altera de valor em cada ciclo (uma varivel iteradora) o Resoluo: Se optar com frequncia para esta situao, deve rever o seu cdigo procurando alternativas

Um exemplo simples deste erro a seguinte verso do Programa 4-2:

56
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h>

Parte II Funes, Vectores e Recurso

int main() { int soma=0, i=1, continua=1; while(continua) { soma+=i*i; i++; if(i>4) continua=0; } printf("Soma dos primeiros 4 quadrados: %d", soma); }

A varivel lgica continua pode ser actualizada em qualquer parte do ciclo, sendo a verdadeira condio de paragem distribuda pelos condicionais associados sua alterao. Este exemplo to pequeno que no d para dividir a condio de paragem, apenas desloc-la do ciclo while para o final do ciclo, mas mesmo assim pode-se verificar a degradao da clareza do cdigo. Uma outra verso a evitar teria sido em vez da varivel continua utilizar-se a constante 1 de forma a obter-se um ciclo infinito, utilizando-se a instruo break; em vez de continua=0; para sair do ciclo. Todos os ciclos tm a mesma complexidade, pode-se trocar um ciclo por outro, no entanto com o ciclo for o cdigo fica normalmente melhor arrumado. A utilizao do switch normalmente no melhora a leitura do cdigo, mas sempre uma opo em relao ao if. Agora que conhece todas as alternativas, faa uma reviso aos exerccios que fez e refaa os ciclos e condicionais de forma a melhorar a simplicidade e leitura do seu cdigo.

7. Vectores

57

7. VECTORES
Nos programas tratados at este momento, foram utilizadas variveis de diferentes tipos, mas nunca se abordou conjuntos de variveis. Suponhamos que estamos interessados em registar um valor por cada dia da semana. A natureza dos valores no relevante, tanto pode ser o nmero de emails enviados, como o nmero de cafs tomados, o que interessa que se pretende guardar um valor por cada dia da semana, e no final obter a soma e a mdia dos valores introduzidos. Com o que sabemos, o seguinte programa resolveria o problema:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <stdio.h> int main() { int segunda, terca, quarta, quinta, sexta, sabado, domingo; int total; /* introduo de valores */ printf("Segunda: "); scanf("%d",&segunda); printf("Terca: "); scanf("%d",&terca); printf("Quarta: "); scanf("%d",&quarta); printf("Quinta: "); scanf("%d",&quinta); printf("Sexta: "); scanf("%d",&sexta); printf("Sabado: "); scanf("%d",&sabado); printf("Domingo: "); scanf("%d",&domingo); /* calculos */ total=segunda+terca+quarta+quinta+sexta+sabado+domingo; printf("Soma: %d\n",total); printf("Media: %f\n",total/7.0); } Programa 7-1 Calculo do total e mdia de um indicador por cada dia da semana

Execuo do programa:
C:\ C:\>semana Segunda: 2 Terca: 3 Quarta: 1 Quinta: 2 Sexta: 3 Sabado: 4 Domingo: 1 Soma: 16 Media: 2.285714

Se necessitamos de um valor por cada dia da semana, temos de criar uma varivel por cada dia da semana. Neste caso a

58

Parte II Funes, Vectores e Recurso

operao pedida muito simples, a varivel total pode ir sendo actualizada medida que se introduz os valores, podendo-se evitar ter criado uma varivel por cada dia. Mas apenas um exemplo que pretende ser o mais simples possvel. Se quiser calcular a varincia, tem-se de fazer outra passagem pelos valores da semana aps obter a mdia, sendo nesse caso essencial que os valores fiquem em memria. Como temos variveis distintas, no podemos fazer um ciclo para fazer a operao de soma, por exemplo. Evidentemente que esta opo no seria prtica se em vez de termos 7 variveis, fossem necessrias 31 para cada dia do ms, ou 365 para cada dia do ano. A soluo poder criar com um s nome, um conjunto de variveis, quantas forem necessrias, e ter uma forma de acesso atravs de um ndice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main() { int diasDaSemana[7]; int total=0,i; /* introduo de valores */ printf("Segunda: "); scanf("%d",&diasDaSemana[0]); printf("Terca: "); scanf("%d",&diasDaSemana[1]); printf("Quarta: "); scanf("%d",&diasDaSemana[2]); printf("Quinta: "); scanf("%d",&diasDaSemana[3]); printf("Sexta: "); scanf("%d",&diasDaSemana[4]); printf("Sabado: "); scanf("%d",&diasDaSemana[5]); printf("Domingo: "); scanf("%d",&diasDaSemana[6]); /* calculos */ for(i=0;i<7;i++) total+=diasDaSemana[i]; printf("Soma: %d\n",total); printf("Media: %f\n",total/7.0); } Programa 7-2 Calculo do total e mdia, verso com vectores

Note-se na declarao da varivel na linha 5. Alm do nome, tem entre parntesis rectos, o nmero 7. Significa isto que a declarao no se refere a uma s varivel inteira, mas sim a um conjunto de 7 variveis, um vector com 7 elementos. Em vez de int poderia estar qualquer outro tipo, no h diferena nenhuma, podem-se declarar vectores de qualquer tipo.

7. Vectores

59

Como aceder a cada uma das 7 variveis? Simplesmente utilizando tambm entre parntesis rectos, o ndice da varivel, de 0 a 6. isso que feito na introduo dos valores, e que permite na linha 25 e 26 construir um ciclo que processa todos os dias da semana. Se em vez de 7 fossem 31, no havia problema, bastaria alterar a constante 7 para 31. No h problema excepto na introduo de dados, que agora teria de ser copiada e estendida para introduzir 31 valores em vez de 7. Ser possvel colocar tambm esta zona de baixo de um ciclo? Sim, desde que se tenha as strings tambm debaixo de um vector. Mas afinal qual o tipo associado string?
Uma string um conjunto de caracteres. Acabmos de fazer um conjunto de 7 valores inteiros, com um vector, e sabemos como criar variveis que sejam caracteres. O que obtemos se declararmos simplesmente um vector de caracteres?
char str[10];

A resposta simples, uma string. A declarao da varivel str acima como sendo um vector de 10 caracteres, uma string. Na primeira posio str[0] est o primeiro carcter da string, na segunda posio str[1] est o segundo carcter, e assim sucessivamente. No entanto h aqui um problema. Se der a varivel str a uma funo, tem de passar tambm o tamanho da string, caso contrrio se estivesse a processar a string, haveria o risco de aceder fora dela, em str[10], str[11], etc. Na declarao dos vectores na linguagem C, o espao em memria para guardar as 10 variveis pedidas reservado, e apenas isso, no guardado nenhum registo do tamanho do vector. Nas instrues de acesso aos elementos, se existir um acesso a elementos fora da dimenso alocada vaise estar a aceder a espao de memria que poder estar a ser utilizado para outras variveis, provavelmente as variveis que esto declaradas a seguir, e que podem at ser de outro tipo. da responsabilidade do programador garantir que isto no acontea. Para as strings, de forma a evitar andar a guardar o nmero de caracteres, e tambm por outros motivos, convencionou-se que o ltimo elemento da string o carcter 0. Esse carcter no faz parte da string, apenas indica que a string acabou. Portanto na declarao conveniente alocar espao suficiente para guardar todos os caracteres da string, mais um que o carcter terminador.

60
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int main() { char str[10]; int i;

Parte II Funes, Vectores e Recurso

printf("Introduza uma string: "); gets(str); for(i=0;str[i]!=0;i++) printf("\nCaracter %d: %c",i,str[i]); } Programa 7-3 Confirmao que uma string um vector de caracteres

Execuo do programa:
C:\>string Introduza uma string: asdf Caracter Caracter Caracter Caracter 0: 1: 2: 3: a s d f

Pode-se verificar que condio para continuar no ciclo que carcter actual no seja igual a zero. Veja este exemplo com ateno para compreender como a string introduzida na verdade realmente apenas um vector do tipo char, com a particularidade de acabar em 0. Sem esta particularidade seria necessrio ter outra varivel com a dimenso da string introduzida. Repare que o tamanho da string na declarao uma constante do programa, enquanto o tamanho da string introduzida foi apenas de 4 caracteres. No exemplo acima a varivel str, que tem no um mas 10 caracteres, foi utilizada sem parnteses rectos na linha 9. Na verdade, enquanto a varivel str[2] do tipo char, a varivel str do tipo char[], ou char*, e representa um vector (a dimenso no relevante). O que a funo gets recebe portanto o vector e no um s carcter. Atendendo a que este tipo no mais que o endereo de memria onde os caracteres esto alojados, tambm chamado de apontador. So os apontadores e no cada elemento do vector que passado para as funes, quando se utiliza vectores. Vamos fazer uma funo strlen que retorna o tamanho da string, que faz parte da biblioteca string.h, mas vamos implement-la para ilustrar a passagem de vectores para dentro das funes.

7. Vectores
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> int strlen(char str[]) { int i; for(i=0;str[i]!=0;i++); return i; } int main() { char str[10]; printf("Introduza uma string: "); gets(str); printf("A string '%s', tem %d caracteres.",str,strlen(str)); } Programa 7-4 Calcular o tamanho de uma string

61

Execuo do programa:
C:\>strlen strlen Introduza uma string: sdf sd A string 'sdf sd', tem 6 caracteres.

A passagem da string na linha 15, foi igual passagem da string na linha 14, e na declarao da string na funo strlen na linha 3, utilizouse o tipo char[] (em alternativa pode-se utilizar o tipo char*). Na linha 3 no se indicou a dimenso do vector, dado que essa no conhecida, apenas que um vector de caracteres. Para a funo passado apenas o endereo do vector, mas com isso a funo consegue aceder a todos os elementos do vector, como se pode ver na linha 6. Na execuo passo-a-passo est apenas a chamada funo strlen. O argumento passado apenas a referncia ao vector, e no o contedo do vector, neste caso 7 caracteres. Os valores acedidos por str[i] so os caracteres que esto declarados na funo main, e no uma cpia. Falta agora responder a uma questo. Como inicializar os valores num vector? Para as variveis basta colocar uma atribuio na declarao, mas para os vectores tem-se de colocar uma atribuio para vrios valores. isso mesmo que feito, como podemos ver neste exemplo da funo dos DiasDoMes.

62

Parte II Funes, Vectores e Recurso

int DiasDoMes(int mes, int ano) { int diasDoMes[] = { 31,28,31,30,31,30, /* Janeiro a Junho */ 31,31,30,31,30,31 /* Julho a Dezembro */ }; if(mes==2 && Bissexto(ano)) return 29; return diasDoMes[mes-1]; } Programa 7-5 Funo DiasDoMes com um vector inicializado na declarao

Nesta implementao do DiasDoMes, o nmero de dias que cada ms tem colocado na inicializao do vector. Neste caso at a dimenso do vector deixada em aberto, mas poderia estar l o 12. Como no est no vector fica com o tamanho da lista indicada na atribuio. Ao colocar os valores em memria, deixam de ser necessrios os condicionais utilizados anteriormente, e suficiente retornar o valor com o ndice correspondente. Notar que tem que se subtrair por 1, uma vez que o primeiro ms o 1, enquanto o primeiro ndice do vector o 0. Agora como que so inicializadas as strings? Ser que as strings no printf so diferentes das que falamos aqui, j que so delimitadas por aspas? Vamos ver as respostas no resultado do seguinte programa:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main() { char str1[]="ABC"; char str2[]={'A','B','C','\0'}; char str3[]={65,66,67,0}; printf("str1: %s\nstr2: %s\nstr3: %s",str1,str2,str3); } Programa 7-6 Exemplificao das diferentes maneiras de se inicializar strings

Execuo do programa:
C:\>string2 str1: ABC str2: ABC str3: ABC

As trs strings so declaradas e inicializadas de maneiras distintas, no entanto ficam exactamente com o mesmo valor, como se pode confirmar na execuo do programa. A string str1 utiliza o formato que temos vindo a utilizar no printf, ou seja, delimitar as strings com aspas. A string entre aspas convertida para algo idntico verso 2, ou seja, uma lista como no exemplo anterior, mas em vez de nmeros tem letras, qual se junta um ltimo carcter com o valor zero. A verso 3, segue o exemplo anterior, considerando os caracteres como nmeros, fazendo uso do conhecimento do nmero do carcter A o 65, e como as letras so nmeros, os trs formatos so exactamente iguais. A string tem 3 caracteres, e ocupa 4 bytes em qualquer dos casos. Estamos agora em condies de resolver o problema inicial de forma mais adequada, ou seja, colocar em ciclo a entrada de dados. Para tal basta colocar num vector as strings a utilizar no printf.

7. Vectores
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <stdio.h> int main() { int diasDaSemana[7]; int total=0,i; char str[10]; char prompt[7][] = { "Segunda: ", "Terca: ", "Quarta: ", "Quinta: ", "Sexta: ", "Sabado: ", "Domingo: " }; /* introduo de valores */ for(i=0;i<7;i++) { printf(prompt[i]); scanf("%d",&diasDaSemana[i]); } /* calculos */ for(i=0;i<7;i++) total+=diasDaSemana[i]; printf("Soma: %d\n",total); printf("Media: %f\n",total/7.0); } Programa 7-7 Calculo do total e mdia, sem cdigo repetido, utilizando vectores

63

Ao colocar a informao varivel na inicializao da varivel prompt, foi possvel colocar o cdigo genrico junto, permitindo assim executar ciclos onde estava cdigo duplicado com ligeiras diferenas. Assim ser mais fcil alterar o cdigo para funcionar de forma idntica mas com outros dados, uma vez que se separou a parte genrica da parte especfica. Execuo passo-a-passo dos primeiros e ltimos passos. A varivel prompt na verdade um conjunto de conjuntos de caracteres, o que o

64

Parte II Funes, Vectores e Recurso

mesmo que dizer que um conjunto de strings. Um exemplo de uma varivel muito parecida com a prompt o argumento argv que a funo main pode ter, e pretende precisamente ter tambm um conjunto de strings que foram colocadas como argumentos do programa.
1 2 3 4 5 6 #include <stdio.h> int main(int argc, char *argv[]) { printf("Argumentos: %d\nArgumento 0: %s",argc,argv[0]); } Programa 7-8 Leitura de argumentos passados ao programa

Execuo do programa:
C:\>arg sdfsd Argumentos: 2 Argumento 0: arg

O programa imprime o primeiro argumento, que o nome do prprio programa. A declarao de argv no tem dois pares de parnteses rectos vazios, dado que apenas pode haver um, que o ltimo, embora alguns compiladores o permitam mais que um, como o caso do TCC. A alternativa aos parnteses rectos vazios o asterisco antes. Outra declarao vlida seria char **argv. Nos restantes tipos no h problema em utilizar vectores de vectores, portanto matrizes, ou vectores de vectores de vectores. normal referir-se a estes tipos como sendo vectores multidimensionais. Na linguagem C no h limite de dimenses pr-definido, mas no se aconselha a utilizar mais que 3 dimenses num vector. Vamos dar um exemplo da soma de duas matrizes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> #define SIZE 10 int main() { int matriz1[SIZE][SIZE]; int matriz2[SIZE][SIZE]; int matrizSoma[SIZE][SIZE]; int i,j; /* inicializar matrizes */ for(i=0;i<SIZE;i++) for(j=0;j<SIZE;j++) { matriz1[i][j]=i+j; matriz2[i][j]=i*j; } /* somar matrizes */ for(i=0;i<SIZE;i++) for(j=0;j<SIZE;j++) matrizSoma[i][j]=matriz1[i][j]+matriz2[i][j]; /* mostrar resultado */ for(i=0;i<SIZE;i++) { /* mudar de linha */ printf("\n"); for(j=0;j<SIZE;j++) printf("%3d ",matrizSoma[i][j]); } } Programa 7-9 Soma de duas matrizes

7. Vectores Execuo do programa:


C:\>matriz

65

0 1 2 3 4 5 6 7 8 9 1 3 5 7 9 11 13 15 17 19 2 5 8 11 14 17 20 23 26 29 3 7 11 15 19 23 27 31 35 39 4 9 14 19 24 29 34 39 44 49 5 11 17 23 29 35 41 47 53 59 6 13 20 27 34 41 48 55 62 69 7 15 23 31 39 47 55 63 71 79 8 17 26 35 44 53 62 71 80 89 9 19 29 39 49 59 69 79 89 99 O acesso a um valor da matriz (vector de vectores) idntico ao dos vectores, mas em vez de um ndice tem que se fornecer dois, um para indicar o vector, e outro para indicar o elemento

dentro desse vector. No printf foi utilizado %3d e no %d, uma vez que se pretende que os nmeros da matriz estejam alinhados, sendo importante que todos os nmeros ocupem o mesmo espao. Para indicar o nmero de caracteres a utilizar num nmero inteiro, pode-se colocar esse valor entre a % e o d. Este exemplo comea com uma instruo de prprocessador ainda no conhecida, o #define. Estas instrues no so para o computador executar, mas sim para o compilador na altura em que est a processar o cdigo do programa. A instruo uma macro que indica que deve ser substitudo o texto "SIZE" pelo valor "10" em todo o cdigo onde o texto "SIZE" ocorra. Desta forma possvel alterar essa constante num s local, e essas alteraes reflectirem-se em todo o cdigo. o que se vai fazer para poder apresentar uma execuo passo-a-passo para este programa, caso contrrio torna-se demasiado longo, em que foi alterado para #define SIZE 2.

66 O valor mximo das dimenses dos vectores, strings, matrizes, normal definir-se desta forma, atravs de macros. Isto porque no podem ser utilizadas variveis nas dimenses dos vectores, at porque as declaraes tm de estar no incio das funes, e nessa altura pode no estar ainda calculada a dimenso que necessria. Tem que se utilizar um valor maior que o necessrio, e se houver problemas, alterase a macro e fica todo o cdigo actualizado. No mdulo 3 vamos saber como alocar memria indicando a dimenso necessria em tempo de execuo.

Parte II Funes, Vectores e Recurso

Erros comuns mais directamente associados a este captulo: Variveis com nomes quase iguais o Forma: Algumas variveis necessrias tm na verdade o mesmo nome, pelo que se utiliza os nomes nomeA, nomeB, nomeC, nomeD, ou nome1, nome2, nome3, nome4 o Problema: Esta situao uma indicao clara que necessita de um vector nome[4]. Se no utilizar o vector no possvel depois fazer alguns ciclos a iterar pelas variveis, e iro aparecer forosamente instrues idnticas repetidas por cada varivel o Resoluo: Deve nesta situao procurar alternativas de fazer cdigo em que esta situao lhe tenha ocorrido. No utilize cdigo de outra pessoa, apenas o seu cdigo com este tipo de erro lhe poder ser til M utilizao do switch ou cadeia if-else o Forma: De forma a executar instrues dependente do valor de um ndice, utilizar um switch colocando as diversas instrues para cada caso do ndice o Problema: Muitas vezes ao utilizar no switch um ndice (ou cadeia if-else), as instrues em cada caso ficam muito parecidas, ficando com cdigo longo e de custo de manuteno elevado. Se essa situao ocorrer deve rever o seu cdigo, dado que est provavelmente a falhar uma alternativa mais potente, que a utilizao de vectores o Resoluo: Criar um vector com as constantes necessrias para chamar numa s instruo todas as outras dependentes do ndice

7. Vectores

67

Macros em todas as constantes o Forma: Tudo o que constante tem direito a uma macro, de forma a poder ser facilmente alterada o Problema: A utilizao de macros tem um custo, que incorre nas linhas de cdigo onde utilizada: no conhecido o valor real da macro. Se esta for utilizada em situaes em que o fluxo de informao seja claro, como um valor inteiro limite sendo os ciclos desde uma constante at esse limite, no h qualquer problema. Se no entanto for utilizada em locais dependentes de outras constantes, sejam em macros sejam no cdigo, ou que seja importante saber o valor da macro para saber qual a sequncia de instrues, ou num caso extremo, o valor da macro necessrio para saber se uma instruo vlida, evidentemente que o valor da macro em si no pode ser alterado sem que o cdigo seja revisto. Perde portanto o sentido, uma vez que prejudica a leitura do cdigo e no facilita qualquer posterior alterao. As macros devem ser independentes entre si e de outras constantes o Resoluo: Remova do cdigo as macros que so dependentes entre si, ou que dificilmente podem mudar sem implicar outras alteraes no cdigo

O exemplo do Programa 7-1 para motivar a utilizao de vectores um exemplo da ocorrncia do primeiro erro, variveis com nomes quase iguais, neste caso relacionados. Por vezes a evidncia superior, sendo utilizadas vrias variveis do mesmo tipo e com o mesmo prefixo. As verses da funo DiasDoMes anteriores apresentada no Programa 7-5 foram utilizadas para introduzir diversos conceitos, mas so na verdade ocorrncias do erro da m utilizao do switch ou cadeia if-else. Tm no entanto a atenuante de resultar em apenas 3 situaes, e no um valor distinto por cada dia do ms. Sobre o erro das macros, vamos dar uma verso do Programa 7-9 com um nmero exagerado de erros deste tipo para melhor ilustrar o custo da utilizao das macros.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <stdio.h> #define SIZE 10 #define INICIO 0 #define NOVALINHA "\n" #define IMPRIMEVALOR "%3d " #define OPERACAO1 i+j; #define OPERACAO2 i*j; #define OPERACAO3 matriz1[i][j]+matriz2[i][j]; #define ACESSO(a,b) matrizSoma[a][b] #define ITERAR_i for(i=INICIO;i<SIZE;i++) #define ITERAR_j for(j=INICIO;j<SIZE;j++) #define ITERAR_ij ITERAR_i ITERAR_j int main() { int matriz1[SIZE][SIZE]; int matriz2[SIZE][SIZE]; int matrizSoma[SIZE][SIZE]; int i,j; /* inicializar matrizes */ ITERAR_ij { matriz1[i][j]=OPERACAO1 matriz2[i][j]=OPERACAO2 } /* somar matrizes */ ITERAR_ij matrizSoma[i][j]=OPERACAO3 /* mostrar resultado */ ITERAR_i

68
34 35 36 37 38 39 40 } {

Parte II Funes, Vectores e Recurso

/* mudar de linha */ printf(NOVALINHA); ITERAR_j printf(IMPRIMEVALOR,ACESSO(i,j)); }

As macros esto definidas no incio do programa, e por ordem de gravidade de erro. A primeira, SIZE 10, aconselhada, resulta em apenas vantagens dado que a constante ao ser alterada no influencia em nada o bom funcionamento do programa. A segunda macro, INICIO 0, cai no erro dado que no pode e no faz sentido ter um valor distinto do zero, o primeiro ndice de um vector o zero, faz parte da linguagem de programao. Utilizar esta macro apenas d flexibilidade para cometer um erro, e ao ler o cdigo no se percebe no local que o processamento desde o incio da matriz. As duas macros NOVALINHA e IMPRIMEVALOR, colocando todas as strings do programa em macros, so at um procedimento normal para programas com diversas lnguas, em que as strings tm de ser traduzidas e colocadas num local parte do cdigo, no utilizando obrigatoriamente macros. No entanto as strings colocadas em macros no tm texto para traduzir, e a sua alterao pode levar a que o ciclo final no funcione, e quem l os printfs no cdigo, no sabe o que estes fazem at conhecer o valor das macros. As macros OPERACAO1/2/3 so j uma agresso ao leitor, dado que acabam por ser uma espcie de funes sem abstraco, que utilizam as variveis do local onde so chamadas. A macro ACESSO(a,b), com argumentos, uma forma curta de aceder a uma varivel que j est declarada, e cujo acesso to simples quanto possvel, pelo que se pode considerar uma reformatao da linguagem de programao. Deve-se utilizar o facto de o leitor conhecer a sintaxe da linguagem C, e no reformatar a linguagem para aceder neste caso a um elemento de matrizSoma de forma alternativa. Finalmente, acaba-se com o pior que se pode fazer utilizando macros, as macros ITERAR, que encapsulam os prprios ciclos. Para este cdigo ser lido ter-se-ia de andar com os olhos para cima e para baixo para desvendar o que faz. Uma situao extrema pode-se com as macros tornar uma outra linguagem de programao vlida, tendo o leitor que ao ler as macros aprender a sintaxe dessa linguagem de forma a poder ler o programa. Est introduzido o principal conceito em estruturas de dados, os vectores. Com base neste conceito ser possvel abordar uma maior variedade de problemas. Tal como o conceito de varivel, a determinao dos vectores necessrios para resolver um problema, aparentemente simples, das partes mais complexas e importantes a decidir antes de comear a escrever um programa. Ir aperceber-se desse problema quando for necessrio utilizar vectores auxiliares, que no sejam dados de entrada e/ou sada do problema em questo.

8. Procedimentos

69

8. PROCEDIMENTOS
Como vimos no captulo de Funes, uma funo permite definir uma abstraco no clculo de um valor. Quando se implementa a funo, apenas nos preocupamos como com os argumentos recebidos se calcula o valor pretendido, abstraindo das possveis utilizaes da funo. Quando se utiliza a funo, apenas nos preocupamos com a passagem dos valores correctos funo, de forma a obter o valor pretendido calculado, abstraindo da forma como esta est implementada. O conceito de "Procedimento" idntico ao da funo, mas na verdade no calcula um valor, mas sim executa um determinado procedimento, no retornando nenhum valor. Esta pequena diferena relativamente ao conceito funo, no conduz a grande distino da forma como so implementados procedimentos ou funes, no merecendo na linguagem C uma distino, muito embora exista em outras linguagens. A distino feita aqui a nvel de conceito. Um procedimento, faz algo, logo o seu nome tem de estar associado a um verbo. Uma funo calcula algo, logo o seu nome tem de estar associado grandeza calculada. Um procedimento chamado como uma instruo isolada, uma funo chamada numa expresso, nem que a expresso seja apenas a funo, para que o valor retornado seja utilizado ou numa atribuio a uma varivel, ou num argumento de uma funo/procedimento. Vamos ver um exemplo. Necessitamos de fazer uma funo para implementar o primeiro exemplo dado no captulo Variveis, a troca do valor de duas variveis. Esta funo no deve retornar nada, deve apenas trocar o valor das variveis. Qual dever ser o seu valor de retorno? Qual dever ser o seu nome? A resposta a ambas as perguntas simples: no tem valor de retorno; o nome deve conter o verbo da aco efectuada, ou seja "Trocar" ou se preferir "Troca". Trata-se de um procedimento.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int Troca(int x, int y) { int aux=x; x=y; y=aux; } int main() { int x=123, y=321; Troca(x,y); printf("x: %d y: %d",x,y); } Programa 8-1 Tentativa de implementao de uma funo para trocar o valor de duas variveis

Este programa tem uma funo Troca, que um procedimento, pelo que se chamou numa instruo isolada, o seu valor de retorno seria desperdiado caso existisse. Em todo o caso no retorna nada, apenas tem o cdigo de troca de duas variveis, o x e o y. Execuo do programa:
C:\>troca x: 123 y: 321

70

Parte II Funes, Vectores e Recurso

A execuo do programa no correu como o previsto, a varivel x continuou com 123, e a varivel y com 321. Porqu? Apenas se trocou os valores das variveis x e y da funo Troca, mas os valores que estamos interessados trocar os valores das variveis x e y da funo main. Pode-se da funo main chamar Troca(2*x,10*y); e nessa altura o que que trocava de valor? A funo Troca no conhece nem tem de conhecer as expresses utilizadas para a chamar, s sabe que os valores passados esto nas variveis locais x e y. Apenas retornar um valor, e esse valor ser a sua contribuio para o programa. Temos de primeiro ter uma forma de dizer que a funo Troca no retorna um valor do tipo inteiro, nem qualquer outro. Se omitir o tipo de retorno a linguagem C assume que inteiro, pelo que necessrio utilizar um outro tipo disponvel, o tipo void. Este tipo no tipo de varivel nenhum, ideal para os procedimentos em que no se pretende retornar nada. Em segundo lugar temos de dar permisso funo Troca para alterar os valores das variveis x e y da funo main. No no entanto necessrio um comando extra da linguagem C, na verdade isso j foi feito no captulo Vectores, no exemplo int strlen(char str[]), em que passamos uma string para um argumento. Foi passado na verdade o endereo do vector, e a funo conseguiu aceder aos valores de todos os elementos do vector. Neste caso no temos vector, mas como um vector um conjunto de elementos, um elemento tambm um vector.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> void Troca(int x[], int y[]) { int aux=x[0]; x[0]=y[0]; y[0]=aux; } int main() { int x[1]={123}, y[1]={321}; Troca(x,y); printf("x: %d y: %d",x[0],y[0]); } Programa 8-2 Funo Troca que realmente troca o valor de duas variveis

Execuo do programa:
C:\>troca2 x: 321 y: 123

O programa funcionou como esperado. No entanto teve de haver algum cuidado ao declarar as variveis como vectores de uma unidade na funo main, para ter acesso ao

8. Procedimentos

71

endereo das variveis x e y. Esta situao resolve-se com o operador & da linguagem C, que aplicado a uma varivel retorna o seu endereo, que precisamente o que necessitamos para no ter de declarar um vector de uma varivel.
10 int main() 11 { 12 int x=123, y=321; 13 Troca(&x,&y); 14 printf("x: %d y: %d",x,y); 15 }

Falta ainda na funo Troca, uma forma mais simples de aceder ao primeiro elemento, at porque no h mais elementos, apenas pretendido o acesso ao valor do primeiro elemento. A linguagem C tem tambm o operador *, que aplicado a um endereo, retorna o valor nesse endereo:
3 void Troca(int x[], int y[]) 4 { 5 int aux = *x; 6 *x = *y; 7 *y = aux; 8 }

Os argumentos tambm sugerem que se trata de um vector x e y, enquanto se pretende apenas o endereo de duas variveis, ou se quiser um vector unitrio. J vimos que o int x[] equivalente a int *x, e precisamente esta uma das situaes mais aconselhvel para a segunda verso, de forma a dar indicaes ao leitor que se pretende apenas aceder ao valor no endereo e no a um conjunto de valores a comear nesse endereo. No entanto no demais realar que para a linguagem C esta diferena no tem significado. Vejamos ento a verso final.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> void Troca(int *x, int *y) { int aux = *x; *x = *y; *y = aux; } int main() { int x=123, y=321; Troca(&x,&y); printf("x: %d y: %d",x,y); } Programa 8-3 Verso final da funo de troca de duas variveis

A este tipo de passagem de parmetros designada de passagem por referncia, uma vez que se envia a referncia da varivel, e no a prpria varivel, para que o contedo da varivel possa ser alterado. Todos os procedimentos devem ter algum parmetro passado por variveis por referncia, caso contrrio no h qualquer efeito da sua chamada. H no entanto duas situaes: a sua aco incide sobre recursos externos, como imprimir uma mensagem no ecr; altera valores de variveis globais.

72

Parte II Funes, Vectores e Recurso

A linguagem C permite que existam declaraes no no incio de funes, mas sim fora delas. As variveis declaradas fora das funes so variveis globais, criadas no incio do programa, e acessveis a todas as funes. Vejamos outra verso da troca de duas variveis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> int x=123, y=321; void Troca() { int aux=x; x=y; y=aux; } int main() { Troca(); printf("x: %d y: %d",x,y); } Programa 8-4 Exemplo de utilizao de variveis globais

Neste caso o procedimento Troca nem sequer necessita de argumentos, dado que vai trocar o valor de x e y, e so exactamente essas variveis que so impressas no printf do main. O primeiro passo no na primeira linha da funo main, mas sim na declarao das variveis globais. Estas variveis esto fora da funo main, e acessveis a todas as funes. O passo 2 chama a funo main. Nas execues passo-a-passo sem variveis globais, assume-se que a funo main acabou de ser chamada antes do primeiro passo. Pode-se pensar que assim melhor, dado que nem sequer preciso passar argumentos. Nada mais errado. Nesta verso do programa, a funo Troca ao perder os argumentos, perdeu generalidade, apenas pode trocar o valor de x e y, quando anteriormente poderia trocar o valor de quaisquer duas variveis inteiras. Por outro lado, como as variveis x e y esto disponveis em todo o programa, tanto a utilizao como a atribuio dos valores destas variveis tm de considerar todas as restantes linhas de cdigo. Pode uma zona do cdigo estar a fazer uma utilizao a estas variveis, e outra zona de cdigo uma utilizao diferente. No portanto possvel que ao implementar uma funo que utilize estas variveis, abstrair-se do resto do programa, a vantagem da abstraco funcional. Nos outros exemplos, cada uma das variveis declaradas apenas eram utilizadas/alteradas com instrues na funo onde foram declaradas, no sendo necessrio considerar o resto do cdigo para utilizar/alterar cada varivel. As variveis globais so desaconselhadas dado que quebram a abstraco funcional, mas por vezes so necessrias por motivos prticos. Existe no entanto uma forma para em certas situaes utilizar-se uma "falsa" varivel global, que na verdade uma varivel global mas apenas acessvel a uma s funo, no quebrando desta forma a abstraco funcional. Trata-se de variveis estticas (static), que podem ser declaradas numa funo, mas mantm o valor entre chamadas, que o mesmo que uma varivel global.

8. Procedimentos
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int Troca(int *x, int *y) { static int trocas=0; int aux = *x; *x = *y; *y = aux; return ++trocas; } int main() { int x=123, y=321; printf("Troca %d: x=%d, y=%d\n", Troca(&x,&y),x,y); printf("Troca %d: x=%d, y=%d\n", Troca(&x,&y),x,y); printf("Troca %d: x=%d, y=%d\n", Troca(&x,&y),x,y); } Programa 8-5 Exemplo de utilizao de variveis estticas (static)

73

Execuo do programa:
C:\>troca Troca 1: x=321, y=123 Troca 2: x=123, y=321 Troca 3: x=321, y=123

Neste programa troca-se o valor das variveis x e y vrias vezes. A funo Troca retorna agora o nmero de trocas que j efectuou. Tem uma varivel declarada com static, o que significa que uma varivel esttica, mantendo o valor entre chamadas. Pode assim ser utilizada para contador, sendo o valor dessa varivel o valor retornado. A funo printf chama a funo Troca de forma a imprimir esse valor, e depois utiliza o valor de x e y, entretanto j alterados pela funo Troca. Ao executar 3 vezes, podemos ver que o contador est a funcionar e que os valores das variveis vo trocando. A execuo passo-a-passo trata as variveis estticas da mesma forma que as variveis globais, muito embora estejam acessveis apenas para as funes onde esto declaradas. A linha 5 nunca executada quando chamada a funo Troca, mas sim no incio da execuo. A utilizao de variveis estticas no deve estar ligada com a funcionalidade pretendida. Contadores do nmero de chamadas a determinada funo um bom exemplo, mas se estiver a tentar utiliz-la como uma funcionalidade pretendida, desaconselha-se.

74

Parte II Funes, Vectores e Recurso

No se pode acabar esta seco sem referir que o que se acabou de fazer com a passagem de argumentos por referncia foi introduzir apontadores na linguagem C. As declaraes na funo Troca com argumentos, podem estar na declarao das variveis locais de qualquer funo. Podemos ter apontadores para qualquer varivel, e fazer operaes sobre apontadores, como incrementar, passando o apontador para o endereo da varivel seguinte, ou somar, saltando-se tantos endereos como o valor somado. Vamos exemplificar implementando uma funo que recebe duas strings, e retorna um apontador para a primeira ocorrncia de uma string na outra (strstr). Esta funo faz parte da biblioteca string.h, e pode ser utilizada tal como a strlen j referida, s quais se juntam tambm as funes strcpy e strcat de forma a se obter o conjunto de funes essenciais sobre strings. A primeira, strcpy, copia o contedo de uma string para a outra, e a segunda, strcat, adiciona o contedo de uma string na outra. Todas as funes da string.h assumem que foi declarado uma string com espao suficiente para as operaes que executam, e que as strings terminam com zero.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> char *strstr(char *find, char *str) { int i; /* percorrer toda a string str */ while(*str!=0) { /* tentar fazer match a comear em str */ for(i=0; str[i]!=0 && find[i]!=0 && find[i]==str[i]; i++); /* se chegou ao fim da string de procura, ento h match */ if(find[i]==0) /* retornar o incio do match */ return str; /* incrementar o incio da string */ str++; } return NULL; } int main() { char str[]="percorrer toda a string str"; char find[10]; char *resultado; printf("Introduza a string de procura: "); gets(find); resultado=strstr(find,str); printf("Resultado: %s\n",resultado); } Programa 8-6 Procura a primeira ocorrncia de uma string em outra (strstr)

Repare-se que se utiliza o facto de str ser um apontador para o incio da string, para no ciclo principal servir desta prpria varivel como varivel iteradora. procurado no segundo ciclo um match entre find e os primeiros caracteres de str. Se tal no for conseguido, incrementa-se o incio da string (varivel str) e repete-se o ciclo. Na funo main guarda-se o resultado da chamada funo numa varivel declarada como apontador resultado. Poder-se-ia ter chamado a funo strstr logo na funo printf, mas pretendeu-se ilustrar que se pode guardar um endereo sem problema, neste caso com a primeira ocorrncia de find em str.

8. Procedimentos Execuo do programa:


C:\>strstr Introduza a string de procura: str Resultado: string str C:\>strstr Introduza a string de procura: abc Resultado: (null)

75

Como se pode ver na primeira execuo do programa, o resultado foi a impresso da primeira ocorrncia at ao final da string, uma vez que uma string apenas acaba com o carcter zero. O segundo caso est relacionado com o valor retornado da funo, em caso de no encontrar nada. Retorna NULL, que o endereo vazio. Quando no se pretende atribuir nenhum valor a um apontador, deve-se utilizar esta constante, que pertence linguagem C. O programa tentou imprimir uma string a apontar para NULL na segunda execuo, o que no deve ser feito, atendendo a que NULL o endereo vazio e portanto no tem caracteres. Por motivos prticos, a string "(null)" aparecer como resultado desta operao na maior parte dos compiladores actuais. Igualmente vlido embora no pertencente linguagem C, ser considerar o valor de NULL como sendo zero, podendo-se fazer uso deste facto em expresses lgicas omitindo operadores lgicos e considerando NULL como falso e todos os restantes endereos como verdadeiro, mas desaconselha-se esse tipo de utilizao pelos motivos j explicados no captulo Condicionais. A execuo passo-a-passo bastante extensa, est aqui uma verso cortada, mas ainda assim podese verificar o funcionamento de um programa com apontadores. Nestes programas move-se o

76

Parte II Funes, Vectores e Recurso

endereo da varivel em anlise, em vez de utilizar um ndice para aceder a determinada varivel num vector. Esta tcnica acaba por ser mais eficiente, uma vez que o acesso a uma posio arbitrria de um vector mais lento que o acesso ao valor de uma varivel atravs do endereo, mas pode resultar tambm em cdigo mais confuso. Erros comuns mais directamente associados a este captulo: Variveis globais o Forma: Em vez de utilizar alguns dos argumentos nas funes, declara-se a varivel globalmente em vez de na funo main, de forma a no s ser acessvel a todas as funes, como no ser necessrio pass-la como argumentos nas funes o Problema: Ao fazer isto, no s mascara as ms opes na diviso do cdigo por funes, dado que no controla os argumentos da funo, como mais importante, perde principal vantagem das funes: quando implementa/rev a funo, apenas tem de considerar a funo e pode abstrair-se do resto do cdigo. Como h variveis globais, ao implementar a funo tem que se considerar todos os restantes locais que essas variveis so utilizadas (leitura / atribuio), para que o cdigo na funo seja compatvel. Por outro lado, ao no se aperceber quando cria a funo do nmero de argumentos que recebe e nmero de variveis que altera, pode estar a criar funes que reduzem muito pouco a complexidade, sendo a sua utilidade diminuta o Resoluo: Utilize o seu cdigo com variveis globais para fazer uma reviso geral. Deve remover todas as variveis globais e coloc-las na funo main, deixando apenas as constantes globais que faam sentido estar definidas globalmente, e macros. Depois deve ir refazendo os argumentos das funes, e eventualmente rever as funes que necessita de forma a ter funes com poucos argumentos e com uma funcionalidade clara e simples Nomes sem significado o Forma: Atendendo a que um bom nome gasta tempo e irrelevante para o bom funcionamento do cdigo, qualquer nome serve o Problema: O cdigo deve ser lido no s pelo compilador como por outras pessoas, pelo que a m utilizao de nomes revela por um lado a m organizao mental de quem o escreve, e por outro um desrespeito por quem l o cdigo. A troca de todos identificadores por nomes abstractos e sem sentido, pode ser uma forma para tornar o cdigo ilegvel, no caso de se pretender proteg-lo. Pode tambm ser uma situao confundida com uma situao de fraude, resultante de uma tentativa de um estudante alterar os identificadores de um cdigo obtido ilicitamente o Resoluo: Verificar todos os identificadores de uma s letra, ou muito poucas letras, bem como nomes abstractos, funes sem uma grandeza associada, e procedimentos sem verbo. Se no consegue encontrar nomes claros para um identificador, seja uma varivel, uma funo, o nome de uma estrutura, etc., pense numa palavra em que explique para que a varivel serve, o que a funo/procedimento faz, o que contm a estrutura, etc. Se no consegue explicar isso, ento o identificador no existe Funes muito grandes o Forma: Tudo o que necessrio fazer, vai sendo acrescentado na funo, e esta fica com cada vez uma maior dimenso. Esta situao ocorre devido ao problema ser muito complexo

8. Procedimentos o

77

Problema: Ao ter uma funo grande, vai ter problemas de complexidade. No s as declaraes de variveis da funo ficam longe do cdigo onde so utilizadas, como no consegue visualizar a funo de uma s vez, sendo muito complicado verificar o cdigo. Esta uma situao clara de m utilizao da principal ferramenta da programao, que lhe permite controlar a complexidade do cdigo: abstraco funcional o Resoluo: Deve dividir a funo em partes que faam sentido, de forma a ter funes pequenas e com poucos argumentos. Dividir a funo s fatias m ideia, dado que os argumentos necessrios para cada fatia podem ser muitos. Se houver vrias fases, sim, colocar cada fase numa funo, caso contrrio deve verificar se os ciclos interiores podem construir funes que no s utilizem poucos argumentos como tambm tenham possibilidade de ser utilizadas em outras situaes. Se a funo reunir estas condies, ter certamente um nome claro Funes com muitos argumentos o Forma: Aps criar a funo, ao adicionar como argumentos as variveis que esta utiliza (leitura / atribuio), este valor revela-se elevado, mas o que necessrio o Problema: Se o nmero de argumentos muito elevado, a abstraco que a funo representa fraca, uma vez que para utilizar a funo, necessrio ter muita informao. Para alm disso, se a funo for muito curta, pode dar mais trabalho a arranjar os parmetros certos para chamar a funo, que colocar o seu cdigo, sendo assim a funo mais prejudicial que til. No caso dos diversos argumentos estarem relacionados, significa que no foram agregados numa estrutura o Resoluo: Deve procurar alternativas na criao de funes. Dividir uma funo existente a meio, provoca situaes destas. Para desagregar uma funo, deve procurar o cdigo repetido, se no existir, o cdigo no interior dos ciclos tipicamente um bom candidato a funo, de forma a manter em cada funo um ou dois ciclos. No caso de as variveis estarem relacionadas, criar uma estrutura para as agregar

O Programa 8-4 um exemplo de uma ocorrncia do erro das variveis globais. Um exemplo de uma ocorrncia de nomes sem significado seria se em vez de utilizar Troca para o nome da funo que troca o valor de duas variveis, utilizar o nome Processa. Esse nome nada diz sobre o que o procedimento faz, pelo que o leitor fica sem saber se quem o escreveu sabe o que fez, uma vez que no foi capaz de dar um nome condicente com a sua funo. Exemplos dos outros dois erros, no se encontram neste texto, dado que so utilizados sempre exemplos o mais curto possvel para introduzir cada conceito. O leitor provavelmente encontrar ocorrncias desses erros no seu prprio cdigo, ao realizar os exerccios azuis e vermelhos. Foram introduzidos alguns conceitos nesta seco, no s procedimentos, como tambm passagem de valores por referncia, variveis globais e estticas, e apontadores. Todos estes conceitos no tm uma grande novidade, so construdos com base nos conceitos j dados anteriormente, nomeadamente em funes e vectores, mas so essenciais a uma boa estruturao do cdigo. Cuidado especial deve ser dado no caso de se sentir tentado a recorrer com frequncia a variveis globais, sendo esse um dos sintomas de que no est a aproveitar as totais potencialidades da abstraco funcional. Num exerccio em que tal lhe acontea, analise com redobrada ateno resolues alternativas.

78

Parte II Funes, Vectores e Recurso

9. RECURSO
A "Recurso" no mais que a aplicao da abstraco funcional, na prpria funo a implementar. A abstraco funcional permite implementar uma funo com base em apenas os seus argumentos, e ignorar tudo o resto. Ao mesmo tempo podemos utilizar funes sem querer saber como esto implementadas. possvel utilizar a funo que se est a implementar? Certamente que no se pode utilizar a funo que se est a implementar para implement-la, excepto se utilizar argumentos distintos. Vamos supor que se tem casos particulares muito simples, e para esses sabemos o resultado sem problema. No caso da funo factorial, esses casos so quando o nmero 0 ou 1, o valor do factorial 1. Pode-se fazer a funo para esses casos, e no caso genrico, utilizar o facto de N!= ( N 1)! N . Assim podemos utilizar a funo Factorial para implementar a funo Factorial, mas utilizando argumentos mais baixos, e no limite ir ser utilizado os casos particulares simples. Vamos implementar no exerccio seguinte esta funo tanto no modo recursivo como no modo iterativo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> int FactorialR(int n) { if(n<2) return 1; return FactorialR(n-1)*n; } int FactorialI(int n) { int i, resultado=1; for(i=2;i<=n;i++) resultado*=i; return resultado; } int main() { int n=5; printf("Factorial de %d: %d = %d", n, FactorialR(n), FactorialI(n)); } Programa 9-1 Factorial, verso iterativa e recursiva

Execuo do programa:
C:\>factorial Factorial de 5: 120 = 120

Repare-se na simplicidade da funo FactorialR, tratamento dos casos simples, e tratamento do caso genrico recorrendo mesma funo mas com um argumento mais baixo, que acabar por tender para o caso simples. Esta funo nem requer a criao de variveis locais, apenas utiliza o argumento. Ao contrrio, a funo FactorialI implementa o factorial todo, e retorna o valor. No entanto foi necessrio um ciclo, e criar uma varivel iteradora para iterar de 2 a N, bem como criar uma varivel para guardar o resultado. Ambas as funes retornam o mesmo resultado, como se pode verificar na execuo do programa, mas a verso recursiva consideravelmente mais simples.

9. Recurso

79

Para compreender bem a recurso, analise com cuidado a execuo passo-a-passo acima. As diversas chamadas a FactorialR no se confundem em momento algum. Cada linha de cdigo diz respeito sempre ltima funo que foi chamada, e assim permanecer at que a funo retorne, passando a execuo para a linha de onde a funo foi chamada, ou para a linha seguinte. Ao contrrio da verso recursiva, o FactorialI no se chama a si prprio, mas utiliza duas variveis i e resultado para calcular o valor pretendido. A recurso pode ser utilizada para substituir ciclos, tal como exemplificado no factorial. Um exemplo clssico quando se introduz a recurso o problema das torres de Hanoi.

Figura 9-1 Problema das Torres de Hanoi, em madeira. Fonte: wikipdia

Tem-se 3 zonas onde se podem colocar discos numa pilha: esquerda, meio, direita. Os discos tm dimenses distintas e esto inicialmente na pilha da esquerda ordenados por dimenso, o disco maior em baixo, o mais pequeno em cima. Pode-se movimentar um disco de cada vez, do topo da pilha, para uma outra pilha que esteja vazia ou tenha um disco maior, mas nunca para cima de um disco menor. Pretende-se obter os movimentos necessrios para passar todos os discos da pilha da esquerda para a pilha da direita. Este problema aparentemente complexo, pode ser resolvido facilmente pela via recursiva, tratando primeiro os casos especiais. Os casos especiais so aqui o movimento de 1 s disco, j que isso

80

Parte II Funes, Vectores e Recurso

simples, basta mover o disco da origem para o destino. Agora qual o caso genrico? Ser possvel mover N discos da pilha A para a pilha B, sabendo como mover N-1 discos de uma pilha para outra? A resposta sim, dado que o problema mover o disco maior. Se mover os discos acima, portanto os N-1 discos, de A para uma pilha intermdia C, depois estamos em condies de mover 1 disco de A para B e de seguida mover os N-1 discos que estavam em C para B.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> void Hanoi(int de, int para, int medio, int discos) { static char pilhas[3][] = { "esquerda", "meio", "direita" }; if(discos==1) printf("\nMover do(a) %s para %s", pilhas[de], pilhas[para]); else if(discos>1) { Hanoi(de,medio,para,discos-1); Hanoi(de,para,medio,1); Hanoi(medio,para,de,discos-1); } } int main() { int discos; printf("Torres de Hanoi. Quantos discos quer movimentar? "); scanf("%d",&discos); Hanoi(0,2,1,discos); } Programa 9-2 Torres de Hanoi

Repare na simplicidade da expresso genrica. Mesmo o movimento de um s disco pode ser reutilizado o cdigo j feito na condio acima, chamando a funo recursivamente. Como todas as chamadas funo tm o argumento disco com um valor inferior ao recebido, este tipo de utilizao lcito, uma vez que se est a diminuir a complexidade do problema at ao ponto ser um caso especial, o movimento de um s disco. Execuo do programa:
C:\>hanoi Torres de Hanoi. Quantos discos quer movimentar? 2 Mover disco do(a) esquerda para meio Mover disco do(a) esquerda para direita Mover disco do(a) meio para direita C:\>hanoi Torres de Hanoi. Quantos discos quer movimentar? 4 Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover Mover disco disco disco disco disco disco disco disco disco disco disco disco disco disco disco do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) do(a) esquerda para meio esquerda para direita meio para direita esquerda para meio direita para esquerda direita para meio esquerda para meio esquerda para direita meio para direita meio para esquerda direita para esquerda meio para direita esquerda para meio esquerda para direita meio para direita

9. Recurso

81

Na execuo do programa pode-se ver que a soluo ao problema pretendido tem uma relao exponencial relativamente ao nmero de discos. Para 2 discos h 3 movimentos, para 4 discos h 15 movimentos. O nmero de movimentos mnimo para N discos realmente 2 N 1 . Execuo passo-a-passo:

Pode-se ver na execuo passo-a-passo que por cada chamada recursiva, necessrio alocar os argumentos da funo. Se existirem muitas chamadas recursivas, e a funo utilizar muitas variveis locais, poder haver problemas de memria, enquanto uma verso iterativa tem esse problema controlado. Repare na utilizao do vector esttico com o nome das pilhas na linha 5. Se fosse utilizado um vector normal, este seria criado em cada chamada funo, a adicionar s 3 variveis locais, e atendendo a que a funo chamada muitas vezes, seria um considervel desperdcio de memria. Um outro exemplo clssico um exerccio que faz parte dos exerccios da Parte I, a funo Fibonacci. A definio recursiva, pelo que se pode implementar facilmente atravs de recurso. Reveja esse exerccio e implemente a verso recursiva. Repare que a verso recursiva da funo Fibonacci mais lenta que a verso iterativa, o que deve conseguir notar quando pedir nmeros elevados. Isso acontece porque o valor da funo nos pontos mais baixos, repetidamente necessrio na verso recursiva, mesmo que j tenha sido calculado anteriormente. Uma forma de resolver o problema, quando h hiptese da funo ser chamada duas ou mais vezes com os mesmos argumentos, guardar o valor dos clculos num vector esttico, e verificar se o valor pedido j foi ou no calculado. Essa soluo tem no entanto custo na clareza do cdigo, o que pode no compensar em tornar a funo recursiva. Nem sempre a recurso acresce custo computacional. Nos dois exemplos acima a diferena entre uma verso iterativa e uma verso recursiva diminuta. No entanto, mesmo que exista aumento de custo, por vezes a reduo da complexidade do cdigo compensadora. Caso duvide deste ponto, implemente a verso iterativa das torres de Hanoi.

82

Parte II Funes, Vectores e Recurso

Erros comuns mais directamente associados a este captulo: M utilizao da recurso o Forma: Uma funo tem um conjunto de instrues que tm de ser executadas vrias vezes. Uma forma de fazer isto no final da funo colocar um teste a controlar o nmero de execues, e chamar a funo recursivamente o Problema: Se a chamada recursiva est no final, e o cdigo tem de passar sempre por esse condicional, ento pode remover a chamada substituindo por um ciclo. Ao chamar a funo recursivamente, as variveis locais j criadas no vo mais ser necessrias, mas por cada passo do ciclo ficar um conjunto de variveis locais espera de serem destrudas. Se o ciclo for muito grande, o stack ir certamente rebentar o Resoluo: Substituir a chamada recursiva por um ciclo e respectivo teste de paragem. Caso a chamada recursiva no seja no final da funo, ou exista mais que uma chamada recursiva, ento sim, prefervel utilizar-se recurso

A funo FactorialR do Programa 9-1 um exemplo deste tipo de erro. Nos exerccios da Parte II encontrar alguns que apenas se podem resolver de forma relativamente simples, atravs da recurso. Tenha em mente que no deve utilizar recurso para o que no til. Por exemplo, o caso do Factorial, embora mais simples, o ganho de simplicidade do cdigo no relevante comparativamente verso iterativa. J nas torres de Hanoi o ganho muito relevante. A funo Fibonacci um exemplo aparentemente em que se deve aplicar a recurso, mas resulta em perda de eficincia. A recurso uma ferramenta muito poderosa que tem de ser utilizada com muito cuidado. Tanto pode reduzir um problema muito complexo num problema trivial, como pode criar problemas de performance e mesmo de memria.

2) Exerccios

83

2) Exerccios argumentos.c
Faa um programa que indique os argumentos recebidos na linha de comando. Notas: Utilize os parmetros da funo main, argc e argv

Execuo de exemplo:
C:\>argumentos abcd 1234 Argumento 0: argumentos Argumento 1: abcd Argumento 2: 1234

Pergunta: assumindo que o nome do programa argumentos, qual deve ser a linha de comando para ter dois argumentos, o primeiro ABC_123 e o segundo 123_ABC?

inverte.c
Faa um programa que pea ao utilizador para inserir uma string, e utiliza uma funo que recebe a string, e inverte a ordem dos caracteres da string. Notas: Utilize a funo gets(str), para ler uma string, e strlen(str) para obter o nmero de caracteres na string, pertencente biblioteca string.h. Considere que as strings inseridas pelo utilizador no ultrapassam os 255 caracteres. Imprima os resultados intermdios.

Execuo de exemplo:
C:\>inverte Inversao de um texto. Texto: inverte 0: enverti 1: etverni 2: etrevni Texto invertido: etrevni

Pergunta: Aplique ao programa a seguinte string, e coloque na resposta o texto invertido: lcdsfkgjzoxilsd sdsdfd sdhjkafads dfwerwqad dsfs dfsdfsdkj

84

Parte II Funes, Vectores e Recurso

rand.c
Faa um programa que utiliza uma funo para gerar nmeros pseudo-aleatrios, de acordo com a seguinte frmula:

x(n + 1) = mod(a x(n ) + b, m )


Em que a , b e m so constantes grandes e primos. Os nmeros pseudo-aleatrios so sempre inicializados por um primeiro valor, a semente (seed), que ser o valor de x (0 ) na expresso acima. Utilize o valor retornado por time(NULL); (nmero de segundos no instante em que a funo chamada), de forma a inicializar a semente com o tempo, tendo assim resultados distintos em cada execuo do programa. O programa deve gerar 10 nmeros pseudo-aleatrios, entre 0 e um valor mximo, introduzido pelo utilizador. Notas:
mod o resto da diviso, que em C o operador % Deve incluir a biblioteca time.h para poder utilizar a funo time.

Se tem um nmero pseudo-aleatrio grande, e pretende um nmero de 0 a N-1, pode utilizar o resto da diviso desse nmero por N para obter o valor pretendido.

Execuo de exemplo:
C:\>rand Gerador de numeros aleatorios inteiros. Valor maximo: 99 seed=1282148053: 33 52 2 2 26 44 66 29 68 24

Pergunta: se a , b e m forem respectivamente 231533, 82571 e 428573, e com seed=1 temporariamente, quais os trs primeiros nmeros gerados para valores entre 0 e 9? Escreva os nmeros separados por espaos.

find.c
Faa um programa que utiliza uma funo que recebe um vector de inteiros, o seu tamanho, e um elemento, e retorna a primeira posio em que o elemento encontrado no vector, ou -1 se o elemento no estiver no vector. O programa deve procurar o nmero 2. Notas: Utilize a funo standard rand() para gerar valores aleatrios (e no a que fez no exerccio anterior), e inicialize a semente chamando a funo srand(1), (ambas as funes pertencem biblioteca stdlib.h) antes de inicializar o vector com elementos aleatrios de 0 a 999. Utilize um vector com 1000 elementos. Mostre os 10 primeiros elementos do vector

2) Exerccios Execuo de exemplo:


C:\>find Vector: 41 467 334 500 169 724 478 358 962 464 Posicao de 2: xxx

85

Pergunta: Qual a primeira posio em que aparece o elemento 2?

maximo.c
Faa um programa que utiliza uma funo para retornar o valor mximo num vector de reais (float), inicializado com valores gerados pela distribuio exponencial negativa:

log(Uniforme(0,1))
Notas: A funo log est disponvel na biblioteca math.h A funo "Uniforme" devolve valores reais entre 0 e 1, mas no existe nas bibliotecas. Gere valores reais entre 0 e 1 (nunca gerando o zero), gerando um valor entre 1 e 10000 com a funo rand (inicialize com srand(1)), aps o qual efectue a diviso real por 10000, para assim ficar com valores desde 0,0001 at 1,0000.

Execuo de exemplo:
C:\>maximo Vector: 5.47 0.17 0.46 0.43 0.09 0.56 1.91 0.07 0.36 0.81 0: 5.47 54: 5.57 268: 6.12 915: 7.xx xx Valor maximo: 7.xxxxx xxxxx

Pergunta: Gerando um vector com 1000 elementos, utilize a funo desenvolvida para calcular o maior valor no vector. Indique na resposta esse valor com preciso de 6 dgitos.

mdc.c
Faa um programa que utiliza uma funo recursiva para calcular o mximo divisor comum, utilizando o seguinte algoritmo recursivo: mdc(x,y)=x se y=0, caso contrrio mdc(x,y)=mdc(y,mod(x,y)), em que mod(x,y) o resto da diviso de x por y Notas: O resto da diviso em C o operador %

86 Execuo de exemplo:

Parte II Funes, Vectores e Recurso

C:\>mdc Calculo do maximo divisor comum entre dois numeros. Indique x e y:48 120 MDC: 24 A soma dos MDC dos pares de numeros de 1 a 100: xxxxx

Pergunta: percorra todos pares de nmeros distintos entre 1 e 100, no percorrendo o mesmo par duas vezes, e some os mximos divisores comuns. Indique o resultado na resposta.

sort.c
Faa um programa que utiliza uma funo para ordenar um vector de inteiros, com o seguinte algoritmo: percorre todos os pares sequencialmente, e se estiverem pela ordem inversa, troca os elementos. Notas: Percorrer todos os pares apenas uma vez independente da ordem, no necessrio analisar o par 10, 35, e o par 35, 10.

Exemplo passo-a-passo:
Iterao 0 1 2 3 4 5 6 v[0] 478 358 358 358 358 358 358 v[1] 358 478 478 478 478 464 464 v[2] 962 962 962 962 962 962 478 v[3] 464 464 464 464 464 478 962

Na tabela acima, cada linha uma iterao, sendo o estado do vector as 4 ltimas colunas dessa linha. A bold esto os dois elementos em comparao no vector. Se estiverem pela ordem inversa, trocam de lugar. Aps a ltima iterao, em que so comparados os elementos das posies 2 e 3, o vector fica ordenado. Execuo de exemplo:
C:\>sort Vector antes de ordenado: 41 467 334 500 169 724 478 358 962 464 Vector depois de ordenado: 1 2 3 3 3 6 7 7 8 8 Quartil 25: xxx Quartil 50: xxx Quartil 75: xxx

Pergunta: utilizando a mesma inicializao de um vector de 1000 inteiros do exerccio find.c, ordene o vector e indique o quartil de 25, 50 e 75% (valores nas posies 250, 500 e 750). Indique os trs valores separados por um espao.

2) Exerccios

87

removedups.c
Faa um programa que utilize uma funo que remove os elementos duplicados num vector de inteiros ordenados. Deve retornar o nmero de elementos finais no vector. Notas: Para remover um elemento no vector, todos os elementos seguintes tm de ser copiados para as novas posies.

Execuo de exemplo:
C:\>removedups Vector inicial: 41 467 334 500 169 724 478 358 962 464 Elementos que permanecem no vector: xxx Vector final: 1 2 3 6 7 8 9 10 11 15

Pergunta: utilizando a mesma inicializao de um vector de 1000 inteiros do exerccio find.c, aps orden-lo com cdigo do exerccio sort.c, execute esta funo e indique na resposta o nmero de elementos que permanecem no vector.

somanumeros.c
Faa um programa que pea ao utilizador para introduzir uma string com nmeros reais separados por espaos, e retorne a sua soma. Utilize preciso dupla e apresente o resultado em notao cientfica com preciso 15. Notas: Pode utilizar a funo strtok da biblioteca string.h, que tem a seguinte utilizao:
char *pt=(char*)strtok(string," "); while(pt!=NULL) { processar pt pt=(char*)strtok(NULL," "); }

Pode utilizar a funo atof que recebe uma string e retorna o seu valor numrico. Deve incluir a biblioteca stdlib.h, para utilizar esta funo.

Execuo de exemplo:
C:\>somanumeros Introduza um conjunto de numeros reais separados por espacos. 1.20 (1.2) 4.60 (3.4) 10.20 (5.6) 18.00 (7.8) A soma dos numeros e' 18.

1.2 3.4 5.6 7.8

Pergunta: Qual o resultado para a seguinte string: "1 1.12320023400234044 1e+5 1e-5 -4"?

88

Parte II Funes, Vectores e Recurso

trocos2.c
Faa um programa com a mesma funcionalidade que o exerccio 1) trocos.c, agora utilizando uma funo que dado um montante, o devolve em moedas, mas em vez de utilizar cdigo repetido para cada moeda, utilize um ciclo em que a informao de cada moeda est num vector previamente inicializado. Calcule tambm o total de moedas necessrio, para os montantes 0.01; 0.03; 0.07; 0.15; ... i=i*2+0.01; ...; 20. Notas: Os vectores previamente inicializados, podem conter o valor das moedas e os textos a apresentar. Chame uma funo trocos, para saber quantas moedas so necessrias para cada um dos montantes, e faa um ciclo a comear em 0.01, e com a frmula acima descrita

Execuo de exemplo:
C:\>trocos2 Introduza um montante em euros, podendo ter centimos: 3.49 2 euros: 1 1 euro: 1 20 centimos: 2 5 centimos: 1 2 centimos: 2 Total moedas: xx

Pergunta: Qual o total de moedas necessrio para os montantes acima descritos?

jogodados.c
Faa um programa que implemente um jogo de dados em que um jogador lana sucessivamente um dado, at optar por parar ficando com o nmero de pontos somados. Se sarem dois dados iguais consecutivos, o jogador fica com os pontos que tem, mas negativos. Faa um programa para simular o jogo, mediante o nmero de lanamentos do jogador. Notas: Para responder pergunta em baixo, execute N jogos para cada nmero de lanamentos, com N suficientemente grande de forma a obter um valor mdio relativamente estvel. Para obter execues distintas, inicialize a semente com base no tempo, chamando no incio do programa uma s vez: srand(time(NULL));

Exemplo de jogos:
Lanamentos: 4 jogo1: 3 1 6 5 +15 jogo2: 3 4 4 -11 ...

2) Exerccios Execuo de exemplo:


C:\>jogodados Jogo do lancamento de dados. Indique numero de lancamentos: 4 3 4 3 1 Pontos: 11 Valores medios dos pontos: Com 1 lancamento(s): xxx Com 2 lancamento(s): xxx Com 3 lancamento(s): xxx Com 4 lancamento(s): xxx Com 5 lancamento(s): xxx Com 6 lancamento(s): xxx Com 7 lancamento(s): xxx Com 8 lancamento(s): xxx Com 9 lancamento(s): -5.06

89

Pergunta: Indique por ordem e separado por um espao, os nmeros de lanamentos cujo valor esperado seja superior a 3.

baralhar.c
Faa um programa que utiliza uma funo que recebe um vector e a sua dimenso, e baralhe os elementos nele contidos. Ateno que deve realizar o nmero mnimo de trocas, e a probabilidade de cada elemento estar em qualquer posio deve ser igual. Notas: Comeando pelo primeiro elemento, tem de haver um valor aleatrio para decidir qual ser o primeiro elemento. O segundo valor aleatrio decidir qual ser o segundo elemento (no removendo o primeiro elemento que j foi escolhido aleatoriamente), e assim sucessivamente para todos os restantes elementos.

Exemplificao: Na tabela abaixo, em cada linha est uma iterao, bem como o valor das diversas posies do vector, e o resultado de um valor aleatrio gerado em cada iterao para decidir qual o elemento nessa iterao. O valor aleatrio pertence a um intervalo cada vez mais pequeno, at que o ltimo valor um valor aleatrio 0 ou 1, para decidir a ordem das duas ltimas posies no vector.
Iterao 0 1 2 3 Valor Aleatrio rand()%4 = 2 rand()%3 = 0 rand()%2 = 1 0 48 80 80 80 1 22 22 22 22 2 80 48 48 25 3 25 25 25 48

Execuo de exemplo:
C:\>baralhar Vector identidade: 0 1 2 3 4 5 6 7 8 9 Vector baralhado: 41 486 348 581 249 804 550 568 186 689 Na posicao 250, 500, 750: xxx xxx xxx

90

Parte II Funes, Vectores e Recurso

Pergunta: crie um vector de 1000 elementos com valores de 0 a 999 seguidos, e chame a funo para os baralhar (chame srand(1)). Indique na resposta os elementos nas posies 250, 500 e 750, separados por espaos.

mergesort.c
Faa um programa que utiliza uma funo que ordena os elementos de um vector da seguinte forma: divide o vector em duas metades, ordena cada metade separadamente, e depois junta ambas as metades ordenadas. Notas: Ao juntar duas partes ordenadas, basta ver apenas os elementos no topo de cada metade, j que ambas as metades esto ordenadas. Como h duas operaes separadas, ordenar parte do vector e juntar duas partes de um vector, mais fcil criar uma funo para cada uma destas operaes. Para a pergunta em baixo, ao declarar um vector de 1.000.000 de elementos, utilize a keyword static, ou uma varivel global, de forma a evitar que o vector fique no stack7 uma vez que muito grande.

Execuo de exemplo:
C:\>mergesort Ordenado de 0 a 124999 Ordenado de 125000 a 249999 Ordenado de 0 a 249999 Ordenado de 250000 a 374999 Ordenado de 375000 a 499999 Ordenado de 250000 a 499999 Ordenado de 0 a 499999 Ordenado de 500000 a 624999 Ordenado de 625000 a 749999 Ordenado de 500000 a 749999 Ordenado de 750000 a 874999 Ordenado de 875000 a 999999 Ordenado de 750000 a 999999 Ordenado de 500000 a 999999 Ordenado de 0 a 999999 Quantis: xxx xxx xxx

Pergunta: gerando agora um vector aleatrio com 1.000.000 de elementos, com valores de 0 a 999 (utilize rand, e chame srand(1)), utilize a funo de ordenao desenvolvida para ordenar o vector e retorne os quantis de 25, 50 e 75% (valores nas posies 250000, 500000, e 750000), separados por espaos.

O stack ser dado no captulo Memria

2) Exerccios

91

binarysearch.c
Faa um programa que utiliza uma funo que recebe um vector ordenado, tamanho e um elemento, e retorna a posio em que o elemento se encontra (ou -1 se no estiver no vector), efectuando uma procura binria: primeiro verifica a posio do meio, se for igual retorna, caso contrrio selecciona apenas o segmento em que poder estar o elemento ( esquerda ou direita do valor testado), e volta a testar a posio do meio desse segmento. O processo continua recursivamente at que o segmento apenas tenha um elemento. Notas: Inclua o cdigo do exerccio mergesort.c de modo a obter o vector de elementos ordenados, e coloque uma mensagem no incio da funo recursiva.

Execuo de exemplo:
C:\>binarysearch Procurar 250 entre Procurar 250 entre ... Procurar 500 entre Procurar 500 entre ... Procurar 750 entre Procurar 750 entre ... Posicao de 250, 500 0 e 999999 0 e 499998 0 e 999999 500000 e 999999 0 e 999999 500000 e 999999 e 750: 251952 503905 755858

Pergunta: utilizando o mesmo vector ordenado do exerccio anterior, procure as posies dos valores 250, 500 e 750. Indique o nmero de chamadas recursivas para cada uma das procuras, separadas por espaos.

numeracaoromana.c
Faa um programa que utiliza duas funes, uma para converter um nmero em numerao romana, e outro para a operao inversa. Relembra-se as correspondncias entre letras e nmeros: M=1000; D=500; C=100; L=50; X=10; V=5; I=1. No sistema de numerao romano as letras devem situar-se da ordem de maior valor para a de menor valor. No se deve escrever mais de trs I, X ou C em qualquer nmero. Se estas letras se situam esquerda de um V, L ou D, subtrai-se o seu valor ao das respectivas letras. Exemplo: IX, XC ou XL correspondem a 9, 90 e 40 respectivamente. Notas: Idealize o algoritmo voc mesmo. Caso no vislumbre nenhuma soluo, comece por considerar o problema at ao nmero 10.

92 Execuo de exemplo:

Parte II Funes, Vectores e Recurso

C:\>numeracaoromana Conversao de numeracao arabe para romana e vice-versa. Numero arabe: 1234 234 M 134 MC 34 MCC 24 MCCX 14 MCCXX 4 MCCXXX 0 MCCXXXIV Resultado: MCCXXXIV Numero romano: MCCXXXIV 1000 CCXXXIV 1100 CXXXIV 1200 XXXIV 1210 XXIV 1220 XIV 1230 IV 1234 Resultado: 1234 Caracteres dos numeros de 1 a 999, em numeracao romana: xxxx

Pergunta: calcule o nmero de caracteres romanos necessrios para os nmeros de 1 a 999 (somar o nmero de letras desses nmeros).

pokerdados.c
Faa um programa para o jogo do Poker de Dados. Joga-se com 5 dados, em que um jogador lana os dados (cada dado tem um valor de 1 a 6), podendo relanar qualquer nmero de dados uma segunda vez. Ganha o jogador que ficar com o melhor lanamento. Pretende-se que simule o lanamento de um jogador neste jogo, podendo o jogador escolher os dados a relanar, e diga qual o resultado. Os resultados so os seguintes: poker real = 5 iguais; poker quadruplo = 4 iguais; fullen = trio + par; sequncia = 1 a 5 ou 2 a 6; trio = 3 iguais; duplo par = par + par; par = 2 iguais; nada = no se verifica nenhum dos casos anteriores. Notas: Faa uma funo que para um conjunto de 5 dados, retorne o resultado do jogo.

Execuo de exemplo:
C:\>pokerdados Valores dos dados lancados: 6 1 2 1 1 Frequencia dos valores: 3 1 0 0 0 1 (trio) Retencao (1-reter, 0-descartar): 01011 Valores dos dados finais: 4 1 2 1 1 Frequencia dos valores: 3 1 0 1 0 0 (trio) nada: xx par: xxx duplo par: xxx

Pergunta: para responder a esta pergunta chame srand(1), e faa 1000 lanamentos de dados, e indique quantas vezes saiu: nada; par; duplo par. Coloque na resposta estes trs valores separados por espaos.

2) Exerccios

93

strreplace.c
Faa um programa que utiliza uma funo que substitui numa string, todas as ocorrncias de uma string A por uma string B, assumindo que h espao devidamente alocado (no h realocao de strings). Notas: Se a string B conter A, a string em A em B no deve ser substituda recursivamente.

Execuo de exemplo:
C:\>strreplace Substituir num texto, as ocorrencias de uma string A por uma string B. Texto: texto a transformar troco o "o" por "os" String A: o String B: os textos a transformar troco o "o" por "os" textos a transformar trocos o "o" por "os" textos a transformar trocos os "o" por "os" Resultado (3 trocas): textos a transformar trocos os "o" por "os"

Nas strings introduzidas o e os adicionou-se um espao. Pergunta: Coloque na resposta o resultado da substituio de "xju" por "uxjuc" em "sdxjukflj sxjuxjudfgklj sdfli sdlfkxjuj erlijs sdfikxjxjuulsj xju xdvflkj wsefrlij wefsxjuld dfg".

ndamas.c
Faa um programa que pea um nmero N, e liste todas as possveis posies para colocar N damas, num tabuleiro de Xadrez de lado N, sem que nenhuma dama ataque outra (mesma diagonal, linha ou coluna). Faa um programa recursivo. Mostre apenas a primeira posio, e conte quantas posies existem. Notas: Considere um valor mximo de 16 damas

Execuo de exemplo:
C:\>ndamas Conta quantas posicoes existem, num tabuleiro de NxN, para colocacao de N damas sem que estas se ataquem mutuamente. Indique N (maximo 16): 5 +---------------+ | # . . . . | | . . # . . | | . . . . # | | . # . . . | | . . . # . | +---------------+ Total: 10

94

Parte II Funes, Vectores e Recurso

Pergunta: quantas posies existem para 13 damas?

doisdados.c
Escreva um programa que lance dois dados um nmero elevado de vezes, e registe a frequncia absoluta da soma dos dados. Faa tambm uma funo para apresentar um grfico de barras com o histograma, que escale os valores a apresentar de forma a utilizar 16 linhas, utilizando uma coluna por cada valor da srie, neste caso, um resultado de lanamento de dados, sendo o valor o nmero de lanamentos. Notas: Faa uma funo separada e a mais genrica possvel para representar o grfico de barras Como tem de imprimir o grfico na horizontal, tem de imprimir o grfico de barras para um vector bidimensional antes de o imprimir

Execuo de exemplo:
C:\>doisdados Simulacao de 10000 lancamentos de dois dados: 1677| # xxxx 1565| # xxxx 1453| ## xxxx 1342| ## xxxx 1230| ###xxxx xxxx 1118| ###xxxx xxxx 1006| ####xxxx xxxx 894| ####xxxx xxxx 783| ####xxxx xxxx 671| #####xxxx xxxx 559| #####xxxx xxxx 447| ######xxxx xxxx 335| ######xxxx xxxx 224|#######xxxx xxxx 112|#######xxxx xxxx 0|########### +----------23456789DOD Valores: 250 546 780 1117 1468 1677 1330 xxxx xxx xxx xxx Numero de caracteres # presentes no histograma: xx

Pergunta: faa o histograma utilizando 10.000 lanamentos, e chamando srand(1). Indique o nmero de cardinais no histograma.

calendario.c
Faa um programa que imprima um calendrio mensal, para um ano e ms introduzidos pelo utilizador, com o dia da semana nas colunas, e atendendo s seguintes regras e feriados nas notas. Notas:

2) Exerccios

95

Determinao de ano bissexto: de 4 em 4 anos, um ano bissexto; de 100 em 100 anos no ano bissexto; de 400 em 400 anos ano bissexto; prevalecem as ltimas regras sobre as primeiras. o Exemplo: O ano 2000 um ano bissexto, sendo 1 de Janeiro a um sbado Dias dos meses: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 Pscoa8: a = ano Mod 19 b = ano \ 100 (diviso inteira) c = ano Mod 100 d=b\4 e = b Mod 4 f = (b + 8) \ 25 g = (b - f + 1) \ 3 h = (19 * a + b - d - g + 15) Mod 30 i=c\4 k = c Mod 4 l = (32 + 2 * e + 2 * i - h - k ) Mod 7 M = (a + 11 * h + 22 * l) \ 451 Ms = (h + l - 7 * M + 114) \ 31 Dia = ((h + l - 7 * M + 114) Mod 31) + 1 2-feira de Carnaval: Pscoa - 48 dias 3-feira de Carnaval: Pscoa - 47 dias 6-feira Santa: Pscoa - 2 dias Corpo de Deus: Pscoa + 60 dias Feriados fixos: ano novo 1/1; dia da liberdade 25/4; dia do trabalhador 1/5; dia de Portugal 10/6; implantao da Repblica 5/10; restaurao da independncia 1/12; Natal 25/12. Faa vrias pequenas funes, uma por cada funcionalidade que necessite.

Execuo de exemplo:
C:\>calendario Calendario mensal, com indicacao de feriados. Indique ano mes:2010 10 Outubro de 2010: ####################### # D S T Q Q S S # ####################### # 1 2 # # 3 4 F 6 7 8 9 # #10 11 12 13 14 15 16 # #17 18 19 20 21 22 23 # #24 25 26 27 28 29 30 # #31 # ####################### Resposta: xxxx

Pergunta: some para o ms de Fevereiro, desde 2000 a 2099, o nmero de feriados, o nmero de dias do ms, e o dia da semana para o dia 1 (Domingo vale 0, Sbado vale 6). Coloque o resultado na resposta.

Fonte: http://pt.wikipedia.org/wiki/C%C3%A1lculo_da_P%C3%A1scoa

96

Parte II Funes, Vectores e Recurso

editdistance.c
Faa um programa que utiliza uma funo que recebe duas strings e retorna a distncia de edio (mostrando informao passo a passo), de acordo com o seguinte algoritmo9:
m[i,j] = distance(s1[1..i], s2[1..j]) m[0,0] = 0 m[i,0] = i, i=1..|s1| m[0,j] = j, j=1..|s2| for i=1..|s1| for j=1..|s2| if s1[i]=s2[j] then m[i,j] = m[i-1,j-1] else m[i,j] = min( m[i-1,j-1] + 1, /* substituio */ m[i-1, j] + 1, /* remoo */ m[i, j-1] + 1 ) /* insero */

Notas:

Considere strings at um tamanho mximo, 256

Execuo de exemplo:
C:\>editdistance Calculo da distancia de edicao entre duas strings. Indique s1: Portugal Indique s2: Brasil Matriz de distancias: B r a s i l 0 1 2 3 4 5 6 P 1 1 2 3 4 5 6 o 2 2 2 3 4 5 6 r 3 3 2 3 4 5 6 t 4 4 3 3 4 5 6 u 5 5 4 4 4 5 6 g 6 6 5 5 5 5 6 a 7 7 6 5 6 6 6 l 8 8 7 6 6 7 6 Operacoes: Portugal (l) Portugal Portugal [-a] Portugl Portugl [g/i] Portuil Portuil [u/s] Portsil Portsil [t/a] Porasil Porasil (r) Porasil Porasil [-o] Prasil Prasil [P/B] Brasil Apagar: 2, Inserir: 0, Substituir: 4 Resultado: 6

Pergunta: Calcule as trs distncias de edio entre: "Sport Lisboa e Benfica", "Sporting Clube de Portugal", e "Futebol Clube do Porto". Indique na resposta para cada uma das distncias, separado por espaos, o nmero de substituies.

Fonte: http://en.wikipedia.org/wiki/Levenshtein_distance

PARTE III MEMRIA, ESTRUTURAS E FICHEIROS


Na Parte II pouco foi dito sobre a gesto da memria, as variveis relacionadas entre si eram declaradas em separado, nem to pouco se falou em ficheiros. Na Parte III vamos finalmente abordar estas questes, reservando ainda um captulo para matria menos relevante mas caracterstica da linguagem C, a que se chamou de Truques. Mais relevante e complementar abstraco funcional a abstraco de dados, que introduzida nesta parte, sendo essencial utilizar nas grandes aplicaes. A abstraco de dados a base da programao por objectos. No final desta parte estar munido de todo o conhecimento que necessita de saber para construir aplicaes em programao estruturada.

100

Parte III Memria, Estruturas e Ficheiros

10.

MEMRIA

At este momento o conhecimento sobre a memria num programa resume-se no seguinte: sempre que necessrio, declaram-se variveis no incio das funes, que na forma de vectores podem ser arbitrariamente grandes. Nada mais haveria a dizer, no fossem os seguintes problemas: Todo o espao necessrio a cada momento tem de ser conhecido antes de compilar o programa (em tempo de compilao / compile time); No pode uma funo criar memria para ser utilizada na funo que a chamou, toda a memria que uma funo pode aceder, tem de ser declarada no incio da funo.

O primeiro problema deveras desagradvel quando no se sabe partida o tamanho de um vector. Para o resolver, at aqui declarava-se uma macro com um tamanho por excesso, a utilizar na declarao do vector de forma a facilitar a sua alterao se necessrio. No caso de o programa necessitar um valor maior enquanto est a correr (em tempo de corrida / run time), tem-se de informar o utilizador que o programa no tinha sido compilado a contar com memria suficiente para satisfazer o seu pedido. O valor por excesso pode levar a que exista um desperdcio de memria, inviabilizando vrias aplicaes a correr em simultneo apenas porque tm uma reserva de memria feita em tempo de compilao, e no quando for conhecido o valor necessrio em tempo de corrida. Vamos ver uma verso generalizada do exemplo das semanas no captulo Vectores, em que se pede para introduzir valores de dimenso varivel, obtendo-se no final a mdia e o desvio padro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <stdio.h> #include <math.h> #define MAXVECTOR 10 int main() { float valor[MAXVECTOR]; float media=0,variancia=0; int i,n; printf("Quantos valores tem o vector: "); scanf("%d",&n); if(n<=0 || n>MAXVECTOR) { printf("Programa compilado para permitir no maximo %d elementos.\n", MAXVECTOR); return; } /* introduo de valores */ for(i=0;i<n;i++) { printf("Valor %d: ",i); scanf("%f",&valor[i]); } /* calculos */ for(i=0;i<n;i++) media+=valor[i]; media/=n;

10. Memria
32 33 34 35 36 37 38 } for(i=0;i<n;i++) variancia+=(media-valor[i])*(media-valor[i]); variancia/=n; printf("Media: %g\n",media); printf("Desvio padrao: %g\n",sqrt(variancia)); Programa 10-1 Generalizao do Programa 7-2, calculando a mdia e o desvio padro

101

Execuo de exemplo:
C:\>media Quantos valores tem o vector: 3 Valor 0: 123 Valor 1: 12 Valor 2: 10 Media: 48.3333 Desvio padrao: 52.8036 C:\>media Quantos valores tem o vector: 11 Programa compilado para permitir no maximo 10 elementos.

Na primeira execuo, precisvamos 3 valores, mas foi utilizado um vector de 10, e na segunda execuo, eram precisos 11 valores, mas o programa no estava compilado para poder retornar a mdia e o desvio padro de um conjunto de 11 valores. Em todo o caso prefervel assim a fazer um acesso fora do espao alocado. O segundo problema de maior gravidade, uma vez que quanto muito uma funo pode eventualmente utilizar memria j alocada nas funes que j a chamaram, acedendo memria de outras funes por referncia, atravs de apontadores, mas nunca pode ela prpria criar memria para que as restantes funes a utilizem, inviabilizando abstraces de funes que necessitem criar memria. Por exemplo, poderamos estar interessados em fazer isto:
/* retorna um vector de inteiros com dimenso n, com valores aleatrios */ ? VectorAleatorio(int n);

Em vez disso tem que se criar o vector com a dimenso n, e passar como argumento junto com o valor n.

102

Parte III Memria, Estruturas e Ficheiros

STACK
Para resolvermos os dois problemas enunciados, necessrio saber como que um programa realmente cria o espao necessrio para as suas variveis. Para tal vamos olhar de novo para o primeiro exemplo de recurso, no por causa do cdigo, mas pela execuo passo-a-passo. Em cada um dos 26 passos, o programa necessita de ter o cdigo na memria, e tem a linha em que est actualmente, o que permite obter a instruo actual. H tambm um espao para "Resultado", aps o qual h um nmero varivel de colunas necessrias para registar o valor das variveis. Ora, ao espao necessrio para escrever uma linha nesta execuo passo-apasso, corresponde tambm espao em memria necessrio pelo computador. H os registos internos fixos, um deles tem realmente a instruo a executar e outros podem ser utilizados para valores temporrios, como os colocados na coluna "Resultado", sendo estes registos dependentes da arquitectura do computador. Algo que comum a todos os programas a correr em computadores actuais, o espao necessrio de memria, mais vasto que registos, e de dimenso varivel, para poder guardar o valor das variveis declaradas e as funes que so chamadas: stack (pilha). Este espao de memria tem o seu nome dado que as variveis so colocadas em posies de memria consecutivas pela ordem que so declaradas, tal como na execuo passo-a-passo se coloca uma nova varivel na prxima coluna livre, mas por outro lado, quando a funo retorna, o espao para as variveis declaradas nessa funo libertado, ficando disponvel para outras funes. Na execuo acima, pode-se ver que a funo FactorialI, utilizou as mesmas colunas que eram utilizadas pela funo FactorialR, que tinha corrido anteriormente, mas como j tinha terminado no necessitava mais de memria. H problemas de memria relacionados apenas com o stack que convm realar. Note-se que o prprio stack um bloco de memria, e como tal foi alocado. Se o programa abusar do stack, pode acontecer que no exista espao no stack para alocar mais variveis, tal como a folha de papel na execuo passo-a-passo pode atingir a borda. Isto pode acontecer se alocar variveis muito grandes, ou se a recurso for exagerada, provocando muita alocao de memria. Vamos continuar no exemplo do factorial, para ter um problema de falta de espao no stack.

10. Memria
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> int FactorialR(int n) { if(n<2) return 1; return FactorialR(n-1)*n; } int FactorialI(int n) { int i, resultado=1; for(i=2;i<=n;i++) resultado*=i; return resultado; } int main() { int n=1000; printf("Factorial de %d: %d = %d", n, FactorialR(n), FactorialI(n)); } Programa 10-2 Rplica do Programa 9-1

103

Execuo de exemplo para n=1000:


C:\>factorial Factorial de 1000: 0 = 0

O valor zero normal, j que o valor do factorial largamente ultrapassa a dimenso representvel por um nmero de 4 bytes, e assim que o resultado acumulado seja nulo, ser sempre nulo at ao fim do produto. O importante aqui foi verificar que ambas as formas de clculo do factorial correram bem e retornaram correctamente. Execuo de exemplo para n=1000000:
C:\>factorial

O programa abortou. A verso recursiva estoirou o stack, se utilizar apenas a verso iterativa o resultado fornecido. Repare-se que a verso recursiva para n=1000000 significa 1000000 de chamadas, tendo que ser reservado para cada uma o apontador de onde a funo foi chamada, e o espao para guardar o parmetro n. A verso iterativa chamada uma s vez, e aloca apenas espao para as suas variveis locais uma s vez. O stack no o local apropriado para guardar muita memria. Do exemplo, conclui-se que no se deve contar com um nmero infindvel de chamadas recursivas de funes, porque em cada chamada tem que se contar com o espao para declarar as variveis locais da funo, e o stack tem limites.

104

Parte III Memria, Estruturas e Ficheiros

HEAP
Para resolver os problemas enunciados, temos de ter uma forma de alocar memria, e uma vez alocada, quando j no for precisa tem que se poder libertar, em vez de se declarar variveis no stack sendo estas libertas automaticamente quando a funo retorna. Existem precisamente duas funes para este efeito, uma para alocar memria malloc e outra para libertar memria alocada pelo malloc: free10.
void *malloc(int); void free(void*); malloc retorna um apontador para um tipo void, apontando para a memria alocada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <stdio.h> #include <math.h> #include <stdlib.h> void Media(int n) { float *valor; float media=0,variancia=0; int i; valor=(float*)malloc(sizeof(float)*n); if(valor!=NULL) { /* introduo de valores */ for(i=0;i<n;i++) { printf("Valor %d: ",i); scanf("%f",&valor[i]); } /* calculos */ for(i=0;i<n;i++) media+=valor[i]; media/=n; for(i=0;i<n;i++) variancia+=(media-valor[i])*(media-valor[i]); variancia/=n; printf("Media: %g\n",media); printf("Desvio padrao: %g\n",sqrt(variancia)); free(valor); } else printf("Memoria insuficiente para alocar %d elementos.\n", n); } int main() { int n; printf("Quantos valores tem o vector: "); scanf("%d",&n); Media(n); } Programa 10-3 Calculo da mdia e desvio padro, alocando apenas a memria necessria

10

As funes malloc e free pretencem biblioteca stdlib.h

10. Memria Execuo de exemplo:


C:\>media2 Quantos valores tem o vector: 3 Valor 0: 123 Valor 1: 12 Valor 2: 10 Media: 48.3333 Desvio padrao: 52.8036 C:\>media2 Quantos valores tem o vector: 11 Valor 0: 11 Valor 1: 2 Valor 2: 2 Valor 3: 3 Valor 4: 4 Valor 5: 5 Valor 6: 6 Valor 7: 7 Valor 8: 23 Valor 9: 1 Valor 10: 3 Media: 6.09091 Desvio padrao: 5.99173 C:\>media2 Quantos valores tem o vector: 1000000000 Memoria insuficiente para alocar 1000000000 elementos.

105

O espao alocado foi o necessrio para efectuar as operaes pretendidas. Ao pedir um valor muito elevado para o vector, na ltima execuo, a funo malloc no consegue reservar memria, e retorna NULL, dando erro, mas no dependente dos valores utilizados na compilao, mas sim dos recursos disponveis na mquina quando o programa correu. O heap tem limites, se no consegue alocar memria a funo malloc retorna NULL. Na linha 10, o valor retornado pelo malloc do tipo void*, mas na verdade pretendido um tipo float*. lcito efectuar-se a atribuio, sendo convertido o tipo void* para float*, mas a instruo tem um cast para (float*) para que o compilador force a converso na expresso mesmo antes da atribuio, e por outro lado ao ler-se o cdigo no se tenha dvidas, que a converso pretendida e no se trata de um erro de atribuio entre variveis de tipos distintos. O cast j poderia ter sido referido anteriormente, dado que se pode utilizar em qualquer parte de uma expresso para a converter para o tipo pretendido, no entanto a sua importncia maior no contexto dos apontadores. Por exemplo para fazer a diviso real entre duas variveis inteiras x e y, pode-se utilizar a seguinte expresso: (float)x/y. Para fazer a diviso inteira e converter o resultado para float, ser: (float)(x/y). Vamos agora fazer um programa que vai lendo strings, e as junta numa s string:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXSTR 255 char *Concatenar(char *str, char *str2) { char *pt; /* duplicar str2 se str nulo */

106
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

Parte III Memria, Estruturas e Ficheiros


if(str==NULL) { pt=(void *)malloc(strlen(str2)+1); if(pt!=NULL) strcpy(pt,str2); } else { pt=(void *)malloc(strlen(str)+strlen(str2)+1); if(pt!=NULL) { strcpy(pt,str); strcat(pt,str2); free(str); } } return pt; } int main() { char *texto=NULL, str[MAXSTR]; do { gets(str); texto=Concatenar(texto,str); } while(strlen(str)>0); printf("%s",texto); free(texto); } Programa 10-4 Concatena as strings introduzidas, utilizando apenas a memria necessria

Execuo de exemplo:
C:\>strings

primeira linhasegunda linhaultima linha

primeira linha segunda linha ultima linha

A vantagem neste exemplo, permitir ir alocando memria conforme as necessidades. Se o utilizador escrever pouco texto, o programa necessita de pouca memria, requisitando mais memria medida que o utilizador escreve mais texto. Repare-se que a funo Concatenar, no s liberta o espao que j no necessrio, como aloca o novo espao, e retorna o apontador para que na funo main o apontador se mantenha actualizado. Pode-se agora fazer abstraces funcionais, que aloquem memria, como por exemplo a criao de um vector de inteiros aleatrios:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <stdlib.h> int *VectorAleatorio(int n, int base) { int *vector,i; vector=(int*)malloc(sizeof(int)*n); if(vector!=NULL) { for(i=0;i<n;i++) vector[i]=rand()%base; } return vector; } void MostraVector(int *vector, int n) { int i; for(i=0;i<n;i++)

10. Memria
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 printf("%d ",vector[i]); } int main() { int *vector,n,base; srand(1); printf("Dimensao: "); scanf("%d",&n); printf("Valor maximo: "); scanf("%d",&base); vector=VectorAleatorio(n,base); if(vector!=NULL) { MostraVector(vector,n); free(vector); } } Programa 10-5 Gerao de um vector aleatrio

107

Execuo de exemplo:
C:\>vectoraleatorio Dimensao: 10 Valor maximo: 10 1 7 4 0 9 4 8 8 2 4

O vector alocado na funo VectorAleatorio foi passado para a funo MostraVector, sem problema. Pode-se na funo main abstrair dos detalhes no s da inicializao do vector, como tambm da sua criao. Toda esta flexibilidade no vem sem um preo. Temos agora uma nova dimenso nos erros possveis com memria, ao ponto de outras linguagens de programao tomarem o seu espao devido a terem formas automticas de evitar parte destes problemas. Vamos considerar os seguintes erros: Ler uma posio de memria no alocada; Alterar uma posio de memria no alocada (e possivelmente em uso); No libertar memria aps alocar; Libertar memria de um endereo invlido: o Endereo nulo; o Endereo no alocado com o malloc (pode ser do stack, ou conter lixo); o Endereo j libertado anteriormente.

O primeiro erro pode acontecer da seguinte maneira: pode-se por algum motivo no alocar memria (por falta de memria ou por no chamar a funo malloc), e mesmo assim ler-se o contedo:
27 ... 28 int main() 29 { 30 char *texto=NULL, str[MAXSTR]; 31 do { 32 gets(str); 33 texto=Concatenar(texto,str); 34 } while(strlen(str)>0); 35 free(texto);

108
36 37 38 }

Parte III Memria, Estruturas e Ficheiros


texto=NULL; printf("%s",texto);

Execuo de Exemplo:
C:\>strings2 primeira linha segunda linha terceira linha (null)

Repare-se que neste caso, a string foi libertada e atribudo o valor NULL, e depois foi tentado aceder string na posio de memria no alocada. O texto impresso "(null)". De onde vem este texto? Como j foi referido no captulo Procedimentos, so estes realmente os caracteres que esto nos primeiros bytes de memria, dado que este erro comum e desta forma o programador sabe que est a imprimir um apontador para NULL. Outra forma deste erro ocorrer pedir-se uma posio de memria acima do vector que foi alocado. Vamos supor que no exemplo dos vectores aleatrios, a funo MostraVector tinha um nmero desfasado relativamente ao ndice correcto.
15 16 17 18 19 20 21 22 ... void MostraVector(int *vector, int n) { int i; for(i=1;i<=n;i++) printf("%d ",vector[i]); } ...

Execuo de exemplo:
C:\>vectoraleatorio2 Dimensao: 10 Valor maximo: 10 7 4 0 9 4 8 8 2 4 393364

O ltimo valor no foi gerado, foi lido de uma posio de memria no alocada, e estava o valor 393364 como poderia estar outro qualquer, depende da ltima utilizao desse endereo de memria. Este tipo de erro normalmente no leva ao crash da aplicao, excepto se o valor lido incorrecto for o de um apontador, dado que nesse caso pode-se de seguida ler ou escrever outros valores a partir desse apontador que est incorrecto. O segundo erro tem normalmente efeitos mais devastadores que o primeiro, dado que pode ter consequncias num local completamente distinto do local onde est o erro. O endereo pode ser no espao em que est cdigo, o que tem consequncias imprevisveis. Vamos supor que ao alocar espao para as strings, no se contava com o carcter terminador.

10. Memria
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ... char *Concatenar(char *str, char *str2) { char *pt; /* duplicar str2 se str nulo */ if(str==NULL) { pt=(void *)malloc(strlen(str2)); if(pt!=NULL) strcpy(pt,str2); } else { pt=(void *)malloc(strlen(str)+strlen(str2)); if(pt!=NULL) { strcpy(pt,str); strcat(pt,str2); free(str); } } return pt; } ...

109

Execuo de exemplo:
C:\>strings3

primeira linhasegunda linhaterceira linha

primeira linha segunda linha terceira linha

Por acaso no houve problema, porque o espao de memria utilizado indevidamente no foi utilizado para mais nada. Vamos agravar a situao, supondo que se escrevia para a posio de memria NULL, o que pode acontecer se no se verificar se a operao de alocao de memria foi bem sucedida.
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ... char *Concatenar(char *str, char *str2) { char *pt; /* duplicar str2 se str nulo */ if(str==NULL) { pt=(void *)malloc(strlen(str2)+1); if(pt!=NULL) strcpy(pt,str2); } else { pt=(void *)malloc(strlen(str)+strlen(str2)+1); if(pt!=NULL) { pt=NULL; strcpy(pt,str); strcat(pt,str2); free(str); } } return pt; } ...

110 Execuo de exemplo:


C:\>strings4

Parte III Memria, Estruturas e Ficheiros

"An unhandle win32 exception occurred in strings4.exe [1488]"

primeira linha segunda linha

A mensagem de erro varia conforme o sistema operativo, e a sua forma de lidar com estas situaes. O programa no entanto que deveria ter o cuidado de evitar que esta situao ocorra. O terceiro erro ocorre quando se cria memria em diversos locais, mas para cada bloco no garantido que seja chamada a funo de libertao free. O apontador para o bloco pode at ficar com outro bloco, ficando blocos de memria perdidos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXSTR 255 char *Concatenar(char *str, char *str2) { char *pt; /* duplicar str2 se str nulo */ if(str==NULL) { pt=(void *)malloc(strlen(str2)+1); if(pt!=NULL) strcpy(pt,str2); } else { pt=(void *)malloc(strlen(str)+strlen(str2)+1); if(pt!=NULL) { strcpy(pt,str); strcat(pt,str2); } } return pt; } int main() { char *texto=NULL, str[MAXSTR]; do { gets(str); texto=Concatenar(texto,str); } while(strlen(str)>0); printf("%s",texto); }

Execuo de exemplo:
C:\>strings5

primeira linhasegunda linhaterceira linha

primeira linha segunda linha terceira linha

Nenhuma das alocaes libertada. Ficam blocos de memria alocados perdidos at que a aplicao acabe. No h erro da aplicao. Se existirem muitos blocos de memria no libertos, a memria acabar por se esgotar, ou pelo menos, a aplicao utiliza mais memria que necessita, e que poderia ser utilizada em outras aplicaes. Nas aplicaes que correm durante muito tempo, por exemplo em servidores, no pode haver nenhum bloco alocado no liberto, porque com o tempo de corrida acabar sempre por esgotar a memria disponvel. Para que este problema no ocorra, tem

10. Memria

111

que se ter em mente quando se aloca memria, o local onde esse bloco ser liberto, nem que seja no final do programa. O quarto erro o que tem efeitos normalmente de crash da aplicao, mas o efeito varia conforme o ambiente em que corre. Em todo o caso deve-se evitar que ocorram. Libertao de o apontador para NULL:
1 2 3 4 5 6 7 8 #include <stdio.h> #include <stdlib.h> int main() { free(NULL); printf("Libertacao ok"); }

Libertao de um apontador para um vector no stack:


1 2 3 4 5 6 7 8 9 #include <stdio.h> #include <stdlib.h> int main() { int vector[10]; free(vector); printf("Libertacao ok"); }

Libertao dupla do mesmo bloco de memria:


1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <stdlib.h> int main() { int *vector; vector=(int*)malloc(sizeof(int)*10); free(vector); free(vector); printf("Libertacao ok"); }

A ltima forma de erro normalmente a mais comum. Com medo de que um bloco no seja liberto, por vezes h mais que um local onde o mesmo bloco liberto. Uma boa prtica aps libertar um bloco de memria, atribuir o apontador para NULL ou outro valor. Como o anterior bloco de memria j no vlido, e poder ser utilizado em outra funo, no faz sentido manter um apontador a referenci-lo. A linguagem C no faz testes de acessos fora dos vectores ou a posies no alocadas. Dada a sua caracterstica de estar perto da mquina, no seria lcito retirar tempo de processamento numa operao to comum como o acesso a uma posio de memria, pelo que o programador que deve garantir que o seu programa no acede fora das zonas alocadas. Os sistemas operativos modernos fazem no entanto essas verificaes ao nvel do programa, de forma a garantir que um programa no acede a espao de memria que no lhe pertence, o que a acontecer poderia representar um risco de segurana.

112

Parte III Memria, Estruturas e Ficheiros

Erros comuns mais directamente associados a este captulo: Atribuies entre variveis de tipos distintos o Forma: Principalmente quando se utiliza apontadores, por vezes necessrio fazer atribuies entre apontadores de tipos distintos, ou entre apontadores e inteiros. Isso no problema porque um apontador um nmero de 32 bits. o Problema: Se atribui um apontador de um tipo a outro, este vai aceder ao endereo de memria apontado como se fosse o segundo tipo, quando este foi alocado e tem dados como se fosse o primeiro tipo. Pode ter consequncias imprevisveis, uma vez que os dados ficam com o acesso incoerente. Em caso algum deve assumir o tipo do apontador, uma vez que tal pode variar conforme o compilador/computador em que o cdigo for utilizado o Resoluo: Se for necessria atribuio entre apontadores de tipos distintos, ao alocar memria por exemplo, ento deve utilizar no cdigo um cast para o tipo de destino, de forma a clarificar que no se trata de um erro. Fora a situao de alocao de memria, no precisa de qualquer outra atribuio entre tipos distintos. Em situaes menos ortodoxas, pode utilizar o tipo union, para fundir dados de mais de um tipo No libertar memria aps alocar o Forma: Atendendo a que no h erros por no libertar memria, e sendo a aplicao de curta durao, justifica-se a no libertao da memria, j que quando a aplicao terminar tudo libertado. o Problema: A aplicao tem os endereos dos blocos de memria que alocou, j no precisa deles, pode libert-los. Se no o fizer, no h garantia da altura em que esses blocos sero libertos, nem se o custo computacional de os libertar o mesmo. O argumento da pequena aplicao no deve ser justificao para qualquer erro, o cdigo que funciona em pequenas aplicaes poder ser reutilizado em outras aplicaes, e se for bem feito, no necessitar de alteraes o Resoluo: Tenha sempre ateno ao alocar memria do local exacto onde esse bloco ser certamente libertado Alocar memria e no testar se a operao foi bem sucedida o Forma: A alocao de memria uma operao comum, e se o programa no utiliza grandes quantidades de memria, sempre bem sucedida, pelo que no vale a pena efectuar a verificao j que complica desnecessariamente o cdigo. o Problema: Mesmo que a aplicao utilize pouca memria, a alocao de memria pode falhar se o sistema operativo tiver a memria esgotada por outras aplicaes, ou por outras instncias da mesma aplicao. A no verificao pode provocar acesso a zonas de memria proibidas com resultados imprevisveis o Resoluo: Em todas as alocaes de memria, verifique se estas foram bem sucedidas, e no caso de falharem, certifique-se que o programa tem um procedimento vlido

11. Estruturas

113

11.

ESTRUTURAS

Cada varivel identificada por um nome. Quantas forem necessrias, quantas so criadas. No entanto, por vezes h variveis que esto relacionadas entre si. No exemplo do nmero de dias do ms, o ano e ms esto relacionadas uma vez que fazem parte de uma data. No entanto a linguagem C no tem o tipo data, da ter-se de utilizar o cdigo utilizado no captulo Funes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h> int Bissexto(int ano) { return ano%400==0 || ano%4==0 && ano%100!=0; } int DiasDoMes(int mes, int ano) { if(mes==2) { /* teste de ano bissexto */ if(Bissexto(ano)) return 29; else return 28; } else if(mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12) { return 31; } else { return 30; } } int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); printf("%d",DiasDoMes(ano,mes)); } Programa 11-1 Rplica do Programa 5-4

Se em vez de uma data, que tem trs valores numricos, pretendermos guardar por exemplo informao sobre uma pessoa, com o nome, sexo, data de nascimento, morada, email e telefone, j seriam muitas variveis relacionadas. Se pretender que o programa registe vrias pessoas, ento tem-se de ter vrios vectores em paralelo para guardar esta informao. Evidentemente que esta soluo torna-se facilmente impraticvel, tanto para passar a informao relativa a uma pessoa para uma funo, como para guardar vrios registos de uma pessoa.

114

Parte III Memria, Estruturas e Ficheiros

As estruturas pretendem precisamente resolver este problema, permitindo agrupar variveis relacionadas entre si.
struct { int ano, mes, dia; } data;

A varivel data declarada no como sendo o tipo int, mas como sendo do tipo:
struct { int ano, mes, dia; }

O acesso varivel deve ser feito a cada campo da varivel, atravs do operador ".":
data.ano=2010; data.mes=12; data.dia=1;

Existe no apenas uma varivel data, mas sim trs variveis: data.ano, data.mes, data.dia11. No entanto pode-se referenciar a varivel data, e copiar por exemplo o contedo das trs variveis, para outra varivel do mesmo tipo:
/* copia todos os campos da varivel data para os campos da varivel data2 */ data2=data;

A varivel data2 teria de ser declarada da mesma forma, tendo que se repetir os campos. Para evitar esta tarefa, e eventuais erros nos nomes dos campos, pode-se dar nome estrutura e assim declara-se apenas uma s vez a estrutura, fora de funes, e esta passa a estar disponvel para declaraes de variveis desse tipo:
struct SData { int ano, mes, dia; }; struct SData data, data2;

Podemos agora reescrever o exemplo do nmero de dias do ms:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
11

#include <stdio.h> struct SData { int ano, mes, dia; }; int Bissexto(int ano) { return ano%400==0 || ano%4==0 && ano%100!=0; } int DiasDoMes(struct SData data) { if(data.mes==2) { /* teste de ano bissexto */ if(Bissexto(data.ano)) return 29;

Inicializao da estrutura na declarao da varivel: struct {int ano,mes,dia;} data={2010,12,1} ={2010,12,1}; ={2010,12,1} colocam-se os valores de cada campo da estrutura pela mesma ordem com que esto declarados.

11. Estruturas
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 else return 28; } else if(data.mes==1 || data.mes==3 || data.mes==5 || data.mes==7 || data.mes==8 || data.mes==10 || data.mes==12) { return 31; } else { return 30; } } int main() { struct SData data; int dias; printf("Indique ano: "); scanf("%d", &data.ano); printf("Indique mes: "); scanf("%d", &data.mes); printf("%d",DiasDoMes(data)); } Programa 11-2 Calculo dos dias de um ms/ano, utilizando estruturas

115

Uma estrutura pode estar dentro de outra. Vejamos o registo de uma pessoa, que utiliza a data de nascimento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <stdio.h> #define MAXSTR 255 struct SData { int ano, mes, dia; }; struct SPessoa { char *nome; char sexo; struct SData nascimento; char *morada; char *email; long telefone; }; struct SPessoa LerPessoa() { struct SPessoa pessoa; char str[MAXSTR]; printf("Nome: "); gets(str); pessoa.nome=(char*)malloc(strlen(str)+1); strcpy(pessoa.nome,str); printf("Sexo: "); scanf("%c",&pessoa.sexo); printf("Data de nascimento (dd-mm-aaaa): "); scanf("%d-%d-%d", &pessoa.nascimento.dia, &pessoa.nascimento.mes, &pessoa.nascimento.ano); printf("Morada: "); gets(str); gets(str); pessoa.morada=(char*)malloc(strlen(str)+1); strcpy(pessoa.morada,str); printf("Email: "); gets(str); pessoa.email=(char*)malloc(strlen(str)+1);

116
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

Parte III Memria, Estruturas e Ficheiros


strcpy(pessoa.email,str); printf("Telefone: "); scanf("%ld",&pessoa.telefone); return pessoa; } void MostraPessoa(struct SPessoa pessoa) { printf("\nNome: %s\nSexo: %c\nData de Nascimento: %d-%d-%d\n", pessoa.nome, pessoa.sexo, pessoa.nascimento.dia, pessoa.nascimento.mes, pessoa.nascimento.ano); printf("Morada: %s\nEmail: %s\nTelefone: %d\n", pessoa.morada, pessoa.email, pessoa.telefone); } void Libertar(struct SPessoa pessoa) { free(pessoa.nome); free(pessoa.morada); free(pessoa.email); } int main() { struct SPessoa pessoa; pessoa=LerPessoa(); MostraPessoa(pessoa); Libertar(pessoa); } Programa 11-3 Registo de uma estrutura com dados de diferentes tipos de pessoas

Execuo de exemplo:
C:\>pessoa Nome: Joaquim Lima Sexo: M Data de nascimento (dd-mm-aaaa): 1-1-1980 Morada: Avenida da Liberdade n.1 Email: joaquim@gmail.com Telefone: 123456789 Nome: Joaquim Lima Sexo: M Data de Nascimento: 1-1-1980 Morada: Avenida da Liberdade n.1 Email: joaquim@gmail.com Telefone: 123456789

No necessrio passar as estruturas por referncia nos argumentos das funes, e pode-se retornar uma estrutura sem problema, como se fosse do tipo inteiro. Todas as variveis da estrutura so copiadas, tanto na passagem de parmetros como ao retornar da funo. Como h apenas uma varivel pessoa, para poupar tempo de cpia conveniente passar a estrutura por referncia, de forma a ser copiado apenas um apontador e no todos os campos da estrutura. Para aceder a um campo de uma estrutura, utiliza-se o operador ".". Se em vez da estrutura utilizarmos um apontador, tem que se aceder a um campo primeiro com o operador * para identificar o contedo, e depois o ponto: (*pessoa).nome. Este tipo de anotao pode baixar a legibilidade do cdigo, no caso de haver muitas estruturas passadas por referncia, como esperado. Para evitar essa situao, a linguagem C tem um operador ->, equivalente: pessoa>nome

11. Estruturas

117

Pode-se estar interessado em declarar uma varivel de um tipo "inteiro", e no estar interessado sequer em saber o tipo concreto do inteiro (long, short, int), e querer poder decidir mais tarde, mas que tal no force a edio de todas as declaraes de variveis desse tipo. Embora as estruturas permitam criar facilmente variveis de tipos complexos, o termo struct tem de ser utilizado sempre que se declara uma varivel complexa, e seria interessante em vez de ter struct SData variavel; se tivesse TData variavel; A utilizao de macros poderia ser uma opo para resolver estes dois problemas embora de risco muito elevado, dado que podem ser substitudos textos em locais imprprios. Os tipos das variveis tm tendncia para serem curtos, dado que so normalmente conceitos simples e claros, pelo que o risco de substituies indevidas aumenta. O typedef serve precisamente para resolver este problema, sem o risco de trocas acidentais que a utilizao de macros teria.
/* a partir deste momento, existe o tipo inteiro, que um int */ typedef int inteiro;

Notar que se existir a necessidade de em outro compilador/computador o inteiro ser do tipo long, ou short, basta editar esta linha e todo o cdigo fica actualizado.
/* TData passa a ser um tipo struct SData */ typedef struct SData TData;

O efeito o pretendido. No entanto o identificador SData foi criado apenas para ser utilizado no typedef. Tal no necessrio, pode-se simplesmente utilizar o typedef logo na declarao da estrutura:
typedef struct { int ano, mes, dia; } TData;

Vejamos o exemplo modificado com passagem de estruturas por referncia, operador ->, e typedef:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #define MAXSTR 255 typedef struct { int ano, mes, dia; } TData; typedef struct { char *nome; char sexo; TData nascimento; char *morada; char *email; long telefone; } TPessoa; void LerPessoa(TPessoa *pessoa) { char str[MAXSTR];

118
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

Parte III Memria, Estruturas e Ficheiros


printf("Nome: "); gets(str); pessoa->nome=(char*)malloc(strlen(str)+1); strcpy(pessoa->nome,str); printf("Sexo: "); scanf("%c",&pessoa->sexo); printf("Data de nascimento (dd-mm-aaaa): "); scanf("%d-%d-%d", &pessoa->nascimento.dia, &pessoa->nascimento.mes, &pessoa->nascimento.ano); printf("Morada: "); gets(str); gets(str); pessoa->morada=(char*)malloc(strlen(str)+1); strcpy(pessoa->morada,str); printf("Email: "); gets(str); pessoa->email=(char*)malloc(strlen(str)+1); strcpy(pessoa->email,str); printf("Telefone: "); scanf("%ld",&pessoa->telefone); } void MostraPessoa(TPessoa *pessoa) { printf("\nNome: %s\nSexo: %c\nData de Nascimento: %d-%d-%d\n", pessoa->nome, pessoa->sexo, pessoa->nascimento.dia, pessoa->nascimento.mes, pessoa->nascimento.ano); printf("Morada: %s\nEmail: %s\nTelefone: %d\n", pessoa->morada, pessoa->email, pessoa->telefone); } void Libertar(TPessoa *pessoa) { free(pessoa->nome); free(pessoa->morada); free(pessoa->email); } int main() { TPessoa pessoa; LerPessoa(&pessoa); MostraPessoa(&pessoa); Libertar(&pessoa); } Programa 11-4 Verso do registo de pessoas, mais legvel e eficiente

Esta verso mais legvel, e tambm mais eficiente. O tipo TPessoa til apenas para a aplicao em desenvolvimento, e a existir em outras aplicaes ser certamente diferente. Pelo contrrio, o tipo TData pode ser reutilizado em vrias aplicaes. Vamos introduzir um tipo com um potencial de reutilizao superior data: vector. Um vector normalmente est associado sua dimenso, a qual tem que ser transportada num parmetro parte para dentro das funes, devendo ser utilizado esse valor para percorrer todo o vector, ou testar se o ndice pedido est alocado ou no. Temos portanto um potencial para definir um tipo com uma alta taxa de reutilizao.
typedef struct { int n;

11. Estruturas
int *valor; } TVectorInt;

119

Vamos refazer o exemplo do vector aleatrio com base nesta estrutura:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <stdio.h> #include <stdlib.h> typedef struct { int n; int *valor; } TVectorInt; TVectorInt VectorAleatorio(int n, int base) { TVectorInt vector; int i; vector.valor=(void*)malloc(sizeof(int)*n); if(vector.valor!=NULL) { vector.n=n; for(i=0;i<n;i++) vector.valor[i]=rand()%base; } else vector.n=0; return vector; } void MostraVector(TVectorInt vector) { int i; for(i=0;i<vector.n;i++) printf("%d ",vector.valor[i]); } int main() { TVectorInt vector; int n,base; srand(1); printf("Dimensao: "); scanf("%d",&n); printf("Valor maximo: "); scanf("%d",&base); vector=VectorAleatorio(n,base); if(vector.valor!=NULL) { MostraVector(vector); free(vector.valor); } } Programa 11-5 Estrutura de um vector com os valores e a sua dimenso

Este exemplo foi melhorado no sentido em que se poupou a passagem para MostraVector de um segundo argumento relacionado com o vector, a sua dimenso, que assim est mesmo associado ao vector. Note-se que neste caso a estrutura passada por valor, uma vez que tem apenas dois campos, nunca sendo o vector em si copiado. Infelizmente as tarefas especficas desta estrutura continuam espalhadas pelo cdigo: Alocao do vector Acesso a uma posio do vector Libertao do vector

120

Parte III Memria, Estruturas e Ficheiros

As estruturas ao permitirem o encapsulamento de dados so a base para a abstraco de dados, tambm chamado de tipos abstractos de dados. Embora este conceito tenha ficado obsoleto com o conceito de classe da programao orientada por objectos, tambm a sua base, pelo que a boa utilizao de tipos abstractos de dados, ser o suporte para a compreenso de classes. Na linguagem C esta a nica ferramenta de controle de complexidade dos dados. Tal como a abstraco funcional permite que se utilize uma funo sem saber como est implementada, e ao implementar a funo no necessitamos de saber como ser utilizada, permitindo desta forma controlar a complexidade de um programa, e implementar um programa de dimenso arbitrria, os tipos abstractos de dados permitem que se utilize dados sem saber como esto realmente declarados, e que ao declarar um tipo abstracto de dados, no se esteja a preocupar como o tipo ser utilizado. Um tipo abstracto de dados consiste em definir no s uma estrutura, como tambm um conjunto de funes para acesso estrutura, no devendo a estrutura ser acedida por qualquer outra funo directamente. Quando for necessrio alterar a estrutura, o resto do programa ficar a funcionar, no sendo necessrio rev-lo, e o mesmo se passa com o resto do programa, pode utilizar os mtodos de acesso sem se preocupar como esto implementados.
TVectorInt VICriar(int); int VITamanho(TVectorInt); int VIValorO(TVectorInt,int); void VIValorI(TVectorInt,int,int); void VILibertar(TVectorInt*);

Estas funes so as necessrias para conter a complexidade de dados associada ao TVectorInt no cdigo apresentado. Desde que se verifique a condio de que nenhuma outra funo acede directamente estrutura de dados, e que estas funes de acesso no tm nada especfico da aplicao, ser sempre um bom tipo abstracto de dados. Apenas desta forma este tipo abstracto de dados poder ser reutilizado em outras aplicaes, e fornecer realmente uma abstraco de dados.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <stdlib.h> /* Tipo de dados abstracto: TVectorInt */ typedef struct { int n; int *valor; } TVectorInt; TVectorInt VICriar(int n) { TVectorInt vector; vector.valor=(void*)malloc(sizeof(int)*n); if(vector.valor!=NULL) vector.n=n; else vector.n=0; return vector; } int VITamanho(TVectorInt vector) { return vector.n; } void VIValorI(TVectorInt vector, int i, int valor) { if(i>=0 && i<vector.n)

11. Estruturas
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 vector.valor[i]=valor; } int VIValorO(TVectorInt vector, int i) { if(i>=0 && i<vector.n) return vector.valor[i]; return 0; } void VILibertar(TVectorInt *vector) { if(vector->valor!=NULL) { free(vector->valor); vector->valor=NULL; vector->n=0; } } /* Programa */ TVectorInt VectorAleatorio(int n, int base) { TVectorInt vector; int i; vector=VICriar(n); for(i=0;i<VITamanho(vector);i++) VIValorI(vector,i,rand()%base); return vector; } void MostraVector(TVectorInt vector) { int i; for(i=0;i<VITamanho(vector);i++) printf("%d ",VIValorO(vector,i)); } int main() { TVectorInt vector; int n,base; srand(1); printf("Dimensao: "); scanf("%d",&n); printf("Valor maximo: "); scanf("%d",&base); vector=VectorAleatorio(n,base); MostraVector(vector); VILibertar(&vector); } Programa 11-6 Tipo abstracto de dados TVectorInt

121

N a implementao das funes VIValorI e VIValorO de acesso ao vector foi colocado um teste de validade, podendo assim num s local controlar se esse teste deve ou no existir. Esta opo no seria possvel sem um tipo abstracto de dados. Na funo VILibertar foram tomadas medidas para que o vector no seja libertado se no estiver alocado, ou se j foi libertado, sendo a nica funo a passar a estrutura por referncia, uma vez que altera o valor dos seus campos. O programa em si, ao ficar liberto das especificidades do tipo de dados abstracto, tambm simplificado.

122

Parte III Memria, Estruturas e Ficheiros

Este tipo pode no entanto ser melhorado, dado que para criar o vector tem de se saber o seu tamanho, sendo os pedidos de acesso fora do espao inicialmente criado recusados. O procedimento poderia ser distinto, por exemplo, no ser necessrio definir o tamanho do vector inicialmente, e o vector ser realocado sempre que necessrio. Adicionava-se alguma complexidade ao tipo abstracto de dados, mas o programa ficaria liberto da necessidade de especificar a dimenso do vector. Foi adicionado ao programa cdigo para medir o tempo utilizado na criao do vector, de forma a podermos medir a sua performance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <stdio.h> #include <stdlib.h> #include <time.h> /* Tipo de dados abstracto: TVectorInt */ typedef struct { int n; int *valor; } TVectorInt; TVectorInt VICriar() { TVectorInt vector; vector.valor=NULL; vector.n=0; return vector; } int VITamanho(TVectorInt *vector) { return vector->n; } void VIInternoRealocar(TVectorInt *vector, int i) { int k, *vectorAntigo; vectorAntigo=vector->valor; vector->valor=(int*)malloc(sizeof(int)*(i+1)); if(vector->valor!=NULL) { for(k=0;k<vector->n;k++) vector->valor[k]=vectorAntigo[k]; vector->n=i+1; } else vector->n=0; if(vectorAntigo!=NULL) free(vectorAntigo); } void VIValorI(TVectorInt *vector, int i, int valor) { /* acesso fora dos parmetros, realocar */ if(i>=vector->n) VIInternoRealocar(vector,i); if(i>=0 && i<vector->n) vector->valor[i]=valor; } int VIValorO(TVectorInt *vector, int i) { /* no colocar a realocao aqui, j que esta operao de leitura e no de escrita */ if(i>=0 && i<vector->n) return vector->valor[i]; return 0;

11. Estruturas
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 } void VILibertar(TVectorInt *vector) { if(vector->valor!=NULL) { free(vector->valor); vector->valor=NULL; vector->n=0; } } /* Programa */ TVectorInt VectorAleatorio(int n, int base) { TVectorInt vector; int i; vector=VICriar(); for(i=0;i<n;i++) VIValorI(&vector,i,rand()%base); return vector; } void MostraVector(TVectorInt *vector) { int i; for(i=0;i<VITamanho(vector);i++) printf("%d ",VIValorO(vector,i)); } int main() { TVectorInt vector; int n,base; clock_t instante; srand(1); printf("Dimensao: "); scanf("%d",&n); printf("Valor maximo: "); scanf("%d",&base); instante=clock(); vector=VectorAleatorio(n,base); if(n<100) MostraVector(&vector); VILibertar(&vector); printf("Tempo (s): %.3f",(float)(clock()-instante)/CLOCKS_PER_SEC); } Programa 11-7 Verso de TVectorInt com apontadores, e controle do tempo

123

Todos os mtodos do tipo abstracto de dados foram passados para referncia, dado que necessrio realocao em VIValorI, e para no estar metade dos mtodos com a estrutura por valor e outra metade por referncia, colocou-se tudo em referncia. Feito o tipo robusto, agora vamos optimiz-lo. Atendendo a que o tempo de realocao fora a que todos os elementos sejam copiados para o novo vector, para adicionar elementos de 1 a 1, para um vector de tamanho N, o nmero de cpias a realizar da ordem de N2. O primeiro elemento copiado N vezes, o segundo elemento copiado N-1 vezes, etc.

124

Parte III Memria, Estruturas e Ficheiros

Vamos executar o cdigo com valores elevados, de forma a ter uma ideia do tempo utilizado pela via experimental. Execuo de exemplo:
C:\>vectoraleatorio5 Dimensao: 1000 Valor maximo: 100 Tempo (s): 0.016 C:\>vectoraleatorio5 Dimensao: 2000 Valor maximo: 100 Tempo (s): 0.032 C:\>vectoraleatorio5 vectoraleatorio5 Dimensao: 10000 Valor maximo: 100 Tempo (s): 0.390 C:\>vectoraleatorio5 Dimensao: 20000 Valor maximo: 100 Tempo (s): 1.343 C:\>vectoraleatorio5 Dimensao: 100000 Valor maximo: 100 Tempo (s): 31.375

clara a drstica mudana ao atingir valores mais elevados. Se com valores at 10000 os tempos so desprezveis e aparentemente com crescimento linear, os valores acima de 20000 so bastante significativos e crescimento quadrtico. Uma forma de melhorar a performance, uma vez que se tivssemos alocado logo o vector com a dimenso final, o nmero de cpias era apenas N, sempre que o vector no chega, alocar logo o dobro do espao necessrio. Assim, o nmero de realocaes baixa consideravelmente, sendo num vector de N elementos, cada elemento quanto muito atribudo uma vez e copiado outra vez, ou seja, da ordem de N operaes. Para fazer isto, necessrio ter a dimenso alocada do vector, e a dimenso lgica do vector. Como utilizamos tipos abstractos de dados, podemos fazer as alteraes necessrias para que o programa fique com esta optimizao, alterando apenas funes do tipo abstracto de dados. Sendo este o primeiro teste da abstraco de dados, de total importncia que no seja necessrio alterar o programa, caso contrrio confirmaramos que a abstraco de dados no estava correctamente definida.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /* Tipo de dados abstracto: TVectorInt */ typedef struct { int n,alocado; int *valor; } TVectorInt; TVectorInt VICriar() { TVectorInt vector; vector.valor=NULL; vector.n=0; vector.alocado=0; return vector; } int VITamanho(TVectorInt *vector)

11. Estruturas
22 { 23 return vector->n; 24 } 25 26 void VIInternoRealocar(TVectorInt *vector, int i) 27 { 28 int k, *vectorAntigo; 29 vectorAntigo=vector->valor; 30 /* alocar o dobro do necessrio */ 31 vector->valor=(int*)malloc(sizeof(int)*(i+1)*2); 32 if(vector->valor!=NULL) 33 { 34 for(k=0;k<vector->n;k++) 35 vector->valor[k]=vectorAntigo[k]; 36 vector->alocado=(i+1)*2; 37 } else 38 vector->alocado=0; 39 if(vectorAntigo!=NULL) 40 free(vectorAntigo); 41 } 42 43 void VIValorI(TVectorInt *vector, int i, int valor) 44 { 45 /* acesso fora dos parmetros, realocar */ 46 if(i>=vector->alocado) 47 VIInternoRealocar(vector,i); 48 49 if(i>=0 && i<vector->alocado) 50 { 51 vector->valor[i]=valor; 52 /* actualizar a dimenso lgica do vector */ 53 if(i>=vector->n) 54 vector->n=i+1; 55 } 56 } 57 58 int VIValorO(TVectorInt *vector, int i) 59 { 60 /* no colocar a realocao aqui, j que esta operao de 61 leitura e no de escrita */ 62 if(i>=0 && i<vector->n) 63 return vector->valor[i]; 64 return 0; 65 } 66 67 68 void VILibertar(TVectorInt *vector) 69 { 70 if(vector->valor!=NULL) 71 { 72 free(vector->valor); 73 vector->valor=NULL; 74 vector->alocado=0; 75 vector->n=0; 76 } 77 } Programa 11-8 Ajustes feitos no tipo abstracto de dados TVectorInt, para melhoria da eficincia

125

Foi adicionado um campo, e feitos os ajustes necessrios, apenas no tipo abstracto de dados. Vamos agora confirmar pela via experimental a vantagem desta mudana.

126 Execuo de exemplo:

Parte III Memria, Estruturas e Ficheiros

C:\>vectoraleatorio6 Dimensao: 1000 Valor maximo: 100 Tempo (s): 0.000 C:\>vectoraleatorio6 Dimensao: 10000 Valor maximo: 100 Tempo (s): 0.016 C:\>vectoraleatorio6 Dimensao: 100000 Valor maximo: 100 Tempo (s): 0.015 C:\>vectoraleatorio6 Dimensao: 1000000 Valor maximo: 100 Tempo (s): 0.078

Nem o valor mais alto do teste anterior, 100000, tem um tempo que se considere digno de alguma preciso. Foi necessrio aumentar o valor para 1000000 para ter quase uma dcima de segundo. Face a estes resultados, at a utilizao da anterior implementao at 10000 valores deixa de ser razovel, dado que a alternativa no gastar praticamente tempo nenhum. Esta soluo tem no entanto o problema de desperdiar at 50% do espao alocado, o que pode no ser razovel em algumas aplicaes. Est introduzida a vantagem dos tipos abstractos de dados. No seria correcto no entanto terminar esta seco, tendo j sido dada a alocao de memria, sem a introduo da estrutura de dados mais famosa que utiliza estruturas: listas. As listas pretendem guardar uma sequncia de elementos, tal como os vectores. No entanto, ao contrrio destes, alocam um bloco de memria por cada elemento, no havendo necessidade de realocaes, e por outro lado utilizam apenas pequenos blocos de memria, facilitando assim a tarefa do gestor de memria. Cada elemento tem de ter no entanto um apontador para o prximo elemento, pelo que esta estrutura tem de utilizar o tipo struct:
typedef struct SListaInt { int valor; struct SListaInt *seguinte; } TListaInt;

Repare-se que a definio da estrutura recursiva. No portanto possvel deixar de fazer um paralelo entre listas (recursivo) e vectores (iterativo), da mesma forma que existe funes recursivas (chamam-se a si prprias) e iterativas (processam os valores iterativamente). Para identificar uma lista, basta ter um apontador para o primeiro elemento, sendo normalizado no s que a lista vazia o apontador para NULL, como tambm o elemento seguinte ao ltimo elemento o valor NULL, ou seja, h uma lista vazia aps o ltimo elemento. Como utilizamos o tipo abstracto de dados, mesmo com esta mudana radical na estrutura definida, podemos fazer a mudana envolvendo apenas o tipo abstracto de dados. Mas como as listas tm um acesso natural distinto dos vectores, vamos definir um tipo de dados abstracto mais adequado para a utilizao de listas.

11. Estruturas
TListaInt* LIAdicionar(TListaInt *lista, int valor); TListaInt *LILibertar(TListaInt *lista)

127

O acesso feito nos prprios membros, tambm no programa, dado que o cdigo no programa simplificado graas a esta estrutura como se ver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <stdio.h> #include <stdlib.h> #include <time.h> /* Tipo de dados abstracto: TListaInt */ typedef struct SListaInt { int valor; struct SListaInt *seguinte; } TListaInt; TListaInt* LIAdicionar(TListaInt *lista, int valor) { TListaInt *novo; novo=(TListaInt*)malloc(sizeof(TListaInt)); if(novo!=NULL) { novo->valor=valor; novo->seguinte=lista; return novo; } return lista; } void LILibertar(TListaInt *lista) { TListaInt *aux; while(lista!=NULL) { aux=lista->seguinte; free(lista); lista=aux; } } /* Programa */ TListaInt *ListaAleatoria(int n, int base) { TListaInt *lista=NULL; /* nova lista */ int i; for(i=0;i<n;i++) lista=LIAdicionar(lista,rand()%base); return lista; } void MostraLista(TListaInt *lista) { while(lista!=NULL) { printf("%d ", lista->valor); lista=lista->seguinte; } } int main() { TListaInt *lista; int n,base; clock_t instante; srand(1);

128
65 66 67 68 69 70 71 72 73 74 75 76 77 }

Parte III Memria, Estruturas e Ficheiros

printf("Dimensao: "); scanf("%d",&n); printf("Valor maximo: "); scanf("%d",&base); instante=clock(); lista=ListaAleatoria(n,base); if(n<100) MostraLista(lista); LILibertar(lista); printf("Tempo (s): %.3f",(float)(clock()-instante)/CLOCKS_PER_SEC); Programa 11-9 Tipo abstracto de dados TListaInt

Repare-se na simplicidade do ciclo while, em MostraLista. Apenas so passados apontadores, e no mtodo LIAdicionar, como a lista alterada, tem de existir tambm uma atribuio para o valor retornado, que representa o incio da lista, como se pode ver em ListaAleatoria. Alternativamente poder-se-ia ter enviado o endereo da lista, mas este formato considera-se mais simples e revelador que o topo da lista vai alterando. Execuo de exemplo:
C:\>listaaleatoria Dimensao: 10 Valor maximo: 10 4 2 8 8 4 9 0 4 7 1 Tempo (s): 0.000 C:\>listaaleatoria Dimensao: 1000 Valor maximo: 100 Tempo (s): 0.000 C:\>listaaleatoria listaaleatoria Dimensao: 10000 Valor maximo: 100 Tempo (s): 0.000 C:\>listaaleatoria Dimensao: 100000 Valor maximo: 100 Tempo (s): 0.094 C:\>listaaleatoria Dimensao: 1000000 Valor maximo: 100 Tempo (s): 0.578

Os valores em termos de tempos so ligeiramente superiores melhor verso dos vectores, uma vez que o nmero de alocaes de memria superior, mas o nmero de operaes idntico e no desperdia memria excepto o espao de um apontador por cada elemento. As listas constituem assim uma alternativa vlida aos vectores, resultando em cdigo mais simples em grande parte dos casos. O preo a pagar pela utilizao das listas, no mtodo de acesso aleatrio: LIValor(lista,i). Para implementar um mtodo deste tipo, tem que se percorrer a lista desde o incio at obter o valor na posio i, enquanto com vectores o acesso imediato. A estrutura de dados de uma aplicao o conjunto de estruturas definidas que permitem carregar os dados da aplicao em memria, e suportam as operaes necessrias. As opes tomadas na definio da estrutura de dados so as mais determinantes para que a aplicao seja facilmente implementada e mantida. No entanto, questes relacionadas com a estrutura de dados no so aprofundadas neste texto, dado que estas questes so mais relevantes apenas para

11. Estruturas

129

aplicaes de mdia e grande dimenso. Aconselha-se apenas que antes de pensar no algoritmo que lhe resolve o problema, pensar nos dados que tem, e como os coloca em memria, ou seja, definir a estrutura de dados, e apenas depois nos algoritmos que necessita de implementar que utilizam a estrutura de dados para produzir o resultado pretendido. normal que se sinta tentado a fazer o inverso, dado que os problemas tratados no tm tratado com estruturas de dados complexas. Para definir a estrutura de dados apenas necessita de conhecer o problema, para implementar um algoritmo, necessita de ter tambm a estrutura de dados definida.

130

Parte III Memria, Estruturas e Ficheiros

12.

FICHEIROS

Um programa pode fazer processamentos fantsticos, mas no final termina e tudo o que aconteceu, desaparece. Uma das grandes vantagens do computador, a possibilidade guardar ficheiros com informao, ao longo do tempo, caso contrrio ficaria idntico a uma mquina de calcular. Os ficheiros servem para guardar a informao de forma mais permanente, de modo a poder ser utilizada mais tarde, pela mesma aplicao ou outra. No entanto, ao executar um programa, todos os dados tm de ser introduzidos pelo teclado, e todos os dados processados so reproduzidos no ecr. Seria interessante ter uma forma de indicar um ficheiro de origem dos dados, e eventualmente um ficheiro de sada dos dados. precisamente esse efeito que tm os operadores de linha de comando < e >:
C:\> prog < in.txt

O ficheiro in.txt ser colocado na entrada de dados de prog. Vejamos o exemplo do nmero de dias do ms j conhecido. Execuo de exemplo:
C:\>diasdomes Indique ano: 2010 Indique mes: 2 28

No ficheiro in.txt coloca-se o texto introduzido:

Figura 12-1 Contedo do ficheiro in.txt

Execuo de exemplo:
C:\>diasdomes < in.txt Indique ano: Indique mes: 28

Repare-se que o programa no ficou espera que se introduza o ano e o ms, estes valores foram lidos do ficheiro in.txt, sendo o resultado final 28. Para o processo inverso, redireccionar o texto de sada para um ficheiro, utiliza-se o operador >
C:\> prog > out.txt

Para o ficheiro out.txt ser enviado a sada de dados do programa.

12. Ficheiros Execuo de exemplo:


C:\>diasdomes > out.txt 2010 2

131

A aplicao j no mostra o texto "Indique ano:" nem "Indique mes:" dado que todo o texto que deveria de ir para o ecr est a ser colocado no ficheiro out.txt. No final o ficheiro out.txt fica deste modo:

Figura 12-2 Contedo do ficheiro out.txt

Parte dos dados que so para especificar o que o utilizador deve introduzir, no fazem sentido neste tipo de utilizaes, em que o utilizador sabe bem o que quer, ao ponto de colocar os dados de entrada num ficheiro, portanto no est dependente do texto que a aplicao lhe mostra para colocar os dados correctamente. Vamos mostrar um exemplo mais real, alterando o exemplo da gerao dos nmeros aleatrios para no mostrar texto de identificao da informao esperada ao utilizador, e mostrar todos os valores gerados independente do nmero de valores.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 ... int main() { TVectorInt vector; int n,base; srand(1); scanf("%d",&n); scanf("%d",&base); vector=VectorAleatorio(n,base); MostraVector(&vector); VILibertar(&vector); }

Execuo de exemplo:
C:\>vectoraleatorio7 1 7 4 0 9 4 8 8 2 4 C:\>vectoraleatorio7 > out.txt

10 10

1000 100

Os 1000 valores aleatrios foram colocados no ficheiro out.txt. O ficheiro ficou com o seguinte contedo:

132

Parte III Memria, Estruturas e Ficheiros

Figura 12-3 Novo contedo do ficheiro out.txt

Por vezes pretende-se juntar informao no ficheiro, e no criar um ficheiro novo. Para tal pode-se utilizar o operador de linha de comando >>, que em vez de apagar o ficheiro antes de colocar a informao do programa, adiciona a informao ao final do ficheiro: Execuo de exemplo:
C:\>vectoraleatorio7 >> out.txt

10 10

Pode-se confirmar que os ltimos valores no ficheiro out.txt so menores que 10:

Figura 12-4 Ficheiro out.txt com algum texto adicionado pela ltima operao

Sobre redireccionamento dos dados de entrada e sada para ficheiros tudo. Pode no entanto ser interessante enviar os dados de um programa, no para um ficheiro mas sim para outro programa. Desta forma tem-se dados a passar directamente de um programa para outro. Isso possvel atravs do operador de linha de comando |:
C:\> prog1 | prog2

12. Ficheiros

133

Os dados de sada de prog1, so os dados de entrada de prog2. Vamos a um exemplo concreto. Suponhamos que pretendemos gerar valores aleatrios para testar o programa diasdomes, e pretendemos utilizar o programa de gerao de vectores aleatrios. O seguinte comando satisfaz o pretendido. A entrada de dados inicial est em in.txt, e a sada de dados final vai para out.txt:
C:\>vectoraleatorio7 < in.txt | diasdomes > out.txt

O contedo dos ficheiros o seguinte:

Figura 12-5 Contedo actual dos ficheiros in.txt e out.txt

Na entrada de dados foram pedidos dois valores aleatrios at 13, uma vez que o ms no pode ser superior a 12. Poderia ter sido gerado um valor invlido para o ms (o valor 0), mas tal no aconteceu, os valores gerados foram "2 7". Os valores gerados foram passados para o programa diasdomes que finalmente colocou o resultado no ficheiro out.txt. Pode-se encadear quantos programas forem necessrios, podendo inclusive estar desenvolvidos em linguagens de programao distintas. Neste caso vectoraleatorio7 est a ser utilizado como um gerador de casos de teste para diasdomes. Para colocar diasdomes sobre muitas situaes vlidas, o ideal ser utilizar um programa especfico para gerar valores aleatrios na gama de valores que diasdomes suporta. Assim podem ser efectuados muitos testes, que de outra forma, testados manualmente, no s levaria muito tempo como provavelmente no seriam detectados tantos problemas. Pode-se fazer tambm um programa para verificar o correcto output de diasdomes, e dar um alerta no caso de detectar algum problema. O redireccionamento da entrada e sada de dados, no resolve no entanto o problema de forma completa. sem dvida uma ptima ferramenta, mas apenas se pode ter um ficheiro de entrada e outro de sada. Pode haver necessidade de se querer utilizar o teclado, e mesmo assim obter o contedo de um ficheiro, ou utilizar o ecr, e mesmo assim gravar num ficheiro, ou ainda abrir dois ou mais ficheiros. O ideal seria poder abrir os ficheiros que fossem necessrios, e aceder a eles como se estivesse a ler do teclado e a imprimir para o ecr. precisamente para isso que servem as funes de ficheiros, em modo de texto: fopen/fprintf/fscanf/fgets/feof/fclose fopen/fprintf/fscanf/fgets/feof /feof/fclose

134 LER:

Parte III Memria, Estruturas e Ficheiros

/*abertura em modo texto, para leitura, retorna NULL se no abrir */ FILE *f=fopen fopen(ficheiro,"rt"); fopen /* idntico a scanf, mas antecedendo um ficheiro aberto para leitura */ fscanf(f,...); fscanf /* idntico a gets, mas com indicao do tamanho mximo da string12 */ fgets(str,MAXSTR,f); fgets /* testa se foi atingido o final do ficheiro f */ feof(f); feof /* fecha o ficheiro aps j no ser necessrio */ fclose(f); fclose

A funo fopen retorna um apontador para o tipo FILE, com a referncia a utilizar em todas as funes sobre o ficheiro, sendo fechado por fclose. Pode-se fazer um paralelo entre fopen/fclose e o malloc/free, enquanto estes ltimos alocam/libertam blocos de memria, os primeiros abrem/fecham ficheiros. Programa dos dias do ms reescrito para ler informao do ficheiro in.txt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main() { int ano, mes, dias; FILE *f; f=fopen("in.txt","rt"); if(f==NULL) return; fscanf(f,"%d", &ano); fscanf(f,"%d", &mes); fclose(f); if(mes==2) { /* teste de ano bissexto */ if(ano%400==0 || ano%4==0 && ano%100!=0) printf("29"); else printf("28"); } else if(mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12) { printf("31"); } else { printf("30"); } } Programa 12-1 Nmero de dias dado ms/ano, lendo dados a partir do ficheiro in.txt

Execuo de exemplo:
C:\>diasdomes2 28

12

A funo fgets retrona a string lida, a no ser que tenha existido um erro, e nesse caso retorna NULL

12. Ficheiros GRAVAR:

135

/*abertura em modo texto, para leitura, retorna NULL se no abrir*/ FILE *f=fopen fopen(ficheiro,"wt"); fopen /* idntico a scanf, mas antecedendo um ficheiro aberto para leitura */ fprint(f,...); fprint fclose(f); /* fecha o ficheiro aps j no ser necessrio*/ fclose

Pode-se gravar dados adicionando-os ao final do ficheiro, neste caso o modo de leitura seria "at". Programa dos dias do ms reescrito para ler de in.txt e gravar para out.txt:
1 #include <stdio.h> 2 3 int main() 4 { 5 int ano, mes, dias; 6 FILE *f,*f2; 7 f=fopen("in.txt","rt"); 8 f2=fopen("out.txt","wt"); 9 if(f==NULL || f2==NULL) 10 return; 11 fscanf(f,"%d", &ano); 12 fscanf(f,"%d", &mes); 13 fclose(f); 14 15 if(mes==2) 16 { 17 /* teste de ano bissexto */ 18 if(ano%400==0 || ano%4==0 && ano%100!=0) 19 fprintf(f2,"29"); 20 else 21 fprintf(f2,"28"); 22 } else if(mes==1 || mes==3 || mes==5 || mes==7 || 23 mes==8 || mes==10 || mes==12) 24 { 25 fprintf(f2,"31"); 26 } else 27 { 28 fprintf(f2,"30"); 29 } 30 fclose(f2); 31 } Programa 12-2 Nmero de dias do ms dado ms/ano lendo dados de in.txt e escrevendo para out.txt

Execuo de exemplo:
C:\>diasdomes3

Ficheiro out.txt:

Figura 12-6 Contedo do ficheiro out.txt aps execuo do Programa 12-2

Tudo o resto igual utilizao normal do printf/scanf/gets, to igual que na verdade mesmo igual. Existe um ficheiro que est sempre aberto para a leitura: stdin. Existe outro ficheiro que est

136

Parte III Memria, Estruturas e Ficheiros

sempre aberto para escrita: stdout. Estes ficheiros no devem ser atribudos a nada, nem fechados com fclose.
printf(...); igual a fprintf(stdout,...); scanf(...); igual a fscanf(stdin,...); gets(...); igual a fgets(...,MAXSTR,stdin);

Entre o gets e fgets h mais um argumento, para proteger a dimenso do vector alocada. Estaria tudo dito relativamente aos ficheiros, no fosse agora haver outro tipo de utilizao: gravar informao para depois ser lida e processada automaticamente. Nesta situao, no faz sentido os textos de identificao do campo a introduzir, como passa a ser necessrio gravar e ler o ficheiro num formato especfico, para que cada dado seja gravado/lido correctamente. Vamos reutilizar o exemplo da ficha de uma pessoa para gravar numa lista todas as pessoas introduzidas, de forma a ficarem disponveis na vez seguinte que se abrir a aplicao. O formato simples: uma linha por cada campo pedido, da mesma forma que so introduzidos os dados.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <stdio.h> #define MAXSTR 255 /* Tipo de dados abstracto: TListaPessoa */ typedef struct { int ano, mes, dia; } TData; typedef struct SPessoa { char *nome; char sexo; TData nascimento; char *morada; char *email; long telefone; struct SPessoa *seguinte; } TListaPessoa; TListaPessoa* LPAdicionar(TListaPessoa *lista, TListaPessoa pessoa) { TListaPessoa *novo; novo=(TListaPessoa*)malloc(sizeof(TListaPessoa)); if(novo!=NULL) { (*novo)=pessoa; novo->seguinte=lista; return novo; } return lista; } void LPLibertar(TListaPessoa *lista) { TListaPessoa *aux; while(lista!=NULL) { aux=lista->seguinte; free(lista->nome); free(lista->morada); free(lista->email); free(lista); lista=aux; }

12. Ficheiros
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 } /* Programa */ /* colocar fout=NULL para no imprimir texto dos campos a ler */ void LerPessoa(TListaPessoa *pessoa, FILE *fin, FILE *fout) { char str[MAXSTR]; if(fout!=NULL) fprintf(fout,"Nome: "); fgets(str,MAXSTR,fin); pessoa->nome=(char*)malloc(strlen(str)+1); strcpy(pessoa->nome,str); if(fout!=NULL) fprintf(fout,"Sexo: "); fscanf(fin,"%c",&pessoa->sexo); if(fout!=NULL) fprintf(fout,"Data de nascimento (dd-mm-aaaa): "); fscanf(fin,"%d-%d-%d", &pessoa->nascimento.dia, &pessoa->nascimento.mes, &pessoa->nascimento.ano); if(fout!=NULL) fprintf(fout,"Morada: "); fgets(str,MAXSTR,fin); fgets(str,MAXSTR,fin); pessoa->morada=(char*)malloc(strlen(str)+1); strcpy(pessoa->morada,str); if(fout!=NULL) fprintf(fout,"Email: "); fgets(str,MAXSTR,fin); pessoa->email=(char*)malloc(strlen(str)+1); strcpy(pessoa->email,str); if(fout!=NULL) fprintf(fout,"Telefone: "); fscanf(fin,"%ld",&pessoa->telefone); } /* grava para fout, mas apenas mostra texto com o nome dos campos se prompt=1 */ void MostraPessoa(TListaPessoa *pessoa, FILE *fout, int prompt) { if(prompt==0) { fprintf(fout,"%s%c\n%d-%d-%d\n%s%s%d", pessoa->nome, pessoa->sexo, pessoa->nascimento.dia, pessoa->nascimento.mes, pessoa->nascimento.ano, pessoa->morada, pessoa->email, pessoa->telefone); } else { fprintf(fout,"\nNome: %s\nSexo: %c\nData de Nascimento: %d-%d-%d\n", pessoa->nome, pessoa->sexo, pessoa->nascimento.dia, pessoa->nascimento.mes, pessoa->nascimento.ano); fprintf(fout,"Morada: %s\nEmail: %s\nTelefone: %d\n", pessoa->morada, pessoa->email, pessoa->telefone); } } int main() { FILE *f; TListaPessoa *lista=NULL, *i, pessoa;

137

138
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 }

Parte III Memria, Estruturas e Ficheiros


/* ler as pessoas todas do ficheiro pessoas.txt */ f=fopen("pessoas.txt","rt"); if(f!=NULL) { while(!feof(f)) { /* l uma pessoa do ficheiro */ LerPessoa(&pessoa, f, NULL); lista=LPAdicionar(lista,pessoa); } fclose(f); } /* mostrar as pessoas lidas do ficheiro */ for(i=lista; i!=NULL; i=i->seguinte) MostraPessoa(i,stdout,1); /* l uma pessoa do teclado */ LerPessoa(&pessoa, stdin, stdout); lista=LPAdicionar(lista,pessoa); /* grava tudo para o ficheiro */ f=fopen("pessoas.txt","wt"); if(f!=NULL) { for(i=lista;i!=NULL;i=i->seguinte) MostraPessoa(i,f,0); fclose(f); } LPLibertar(lista); Programa 12-3 Ficha de pessoa, com gravao e leitura do ficheiro

Realar na reutilizao dos mtodos para ler uma ficha e gravar, aos quais apenas se permitiu fazer essa operao atravs de um ficheiro. Na funo main que se chama esses mtodos, quer com o stdin/stdout, como com um ficheiro aberto para escrita e leitura. No modo de gravao para ficheiro, muito importante que os campos sejam gravados exactamente da mesma forma que so lidos, caso contrrio os diferentes valores podem no ir para os locais correctos. Execuo de exemplo:
C:\>pessoa3 Nome: Heraclito de Efeso Sexo: M Data de nascimento (dd-mm-aaaa): 1-1--540 --540 Morada: Efeso Email: heraclito@filosofia.com Telefone: 111111111 C:\>pessoa3 Nome: Heraclito de Efeso Sexo: M Data de Nascimento: 1-1--540 Morada: Efeso Email: heraclito@filosofia.com Telefone: 111111111 Nome: Afonso Henriques Sexo: M Data de nascimento (dd-mm-aaaa): 25-7-1109 25Morada: Guimaraes Email: afonso@conquistador.com Telefone: 111111111 C:\>pessoa3 Nome: Heraclito de Efeso Sexo: M Data de Nascimento: 1-1--540 Morada: Efeso

12. Ficheiros
Email: heraclito@filosofia.com Telefone: 111111111 Nome: Afonso Henriques Sexo: M Data de Nascimento: 25-7-1109 Morada: Guimaraes Email: afonso@conquistador.com Telefone: 111111111 Nome: Ludwig van Beethoven Sexo: M Data de nascimento (dd-mm-aaaa): 16-12-1770 16-12Morada: Viena Email: ludwig@musica.com Telefone: 111111111

139

No final o ficheiro de texto pessoas.txt ficou com o seguinte contedo:

Figura 12-7 Ficheiro pessoas.txt com o contedo gravado/lido pelo Programa 12-3

Repare-se que o nome de uma pessoa est colado ao ltimo campo da pessoa anterior. Isto ocorreu devido formatao dos dados de entrada/sada. O ficheiro pode ser editado, reflectindo-se essas alteraes no programa, mas se o formato no for o correcto, os dados deixam de se ler correctamente. Os ficheiros gravados por estarem em modo de texto13 podem ser editados por outra aplicao, e alterados facilmente. Utilizam no entanto muito espao, enquanto para guardar um nmero inteiro so necessrios 4 bytes em memria, para o escrever em modo de texto sem contar com os caracteres que antecedem o nmero, podem ser necessrios at 9 bytes. O modo de texto assume que apenas caracteres imprimveis so utilizados, e aplica algumas operaes (no Windows substitui um \n por um \r\n), existindo o conceito de linha de texto do ficheiro. O modo binrio14 para programas ou ficheiros em outro formato no organizado por linhas e texto, em que a existncia de um carcter com o cdigo de \n pura coincidncia, j que no h linhas.
13 Os modos de texto mais utilizados: rt leitura; wt escrita num ficheiro novo; at escrita no final do ficheiro 14 Os modos binrios mais utilizados: rb leitura; wb escrita num ficheiro novo; ab escrita no final do ficheiro; r+b leitura e escrita em ficheiro existente; w+b leitura e escrita num ficheiro novo

140

Parte III Memria, Estruturas e Ficheiros

Como no existe linhas, o ficheiro gravado/lido com blocos de memria, sendo para isso que as funes fread/fwrite15 existem (ler/gravar um bloco de memria). As funes fseek/ftell permitem recolocar a posio do ficheiro e ler a posio actual no ficheiro, respectivamente. Com a utilizao destas funes, em vez do fprintf/fscanf/fgets, pode-se facilmente gravar e ler dados em modo binrio. Para exemplificar, vamos acrescentar ao tipo abstracto de dados vector, a possibilidade de se gravar/ler para um ficheiro em modo binrio. No exemplo do ficheiro aleatrio, assim que o utilizador coloque um pedido, verifica se o ficheiro existe e nesse caso l os valores do ficheiro, caso contrrio gera os valores e grava-os no ficheiro, para que da prxima vez no seja necessrio gerar os valores.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
15

... void VILer(TVectorInt *vector, FILE *f) { int n; vector->valor=NULL; vector->alocado=0; vector->n=0; /* ler a dimenso do vector */ fread(&n,sizeof(int),1,f); /* realocar para a dimenso exacta */ VIInternoRealocar(vector,n/2); /* ler o vector inteiro */ if(vector->valor!=NULL) { fread(vector->valor,sizeof(int),n,f); vector->n=n; } } void VIGravar(TVectorInt *vector, FILE *f) { fwrite(&(vector->n),sizeof(int),1,f); fwrite(vector->valor,sizeof(int),vector->n,f); } /* Programa */ ... int main() { TVectorInt vector; FILE *f; char str[MAXSTR]; int n,base; srand(1); scanf("%d",&n); scanf("%d",&base); /* verificar se existe um ficheiro com os dados gravados */ sprintf(str,"n%db%d.dat",n,base); f=fopen(str,"rb"); if(f!=NULL) { VILer(&vector,f); fclose(f); } else { vector=VectorAleatorio(n,base); f=fopen(str,"wb"); if(f!=NULL)

Utilizao: fread/fwrite(tipo*, sizeof(tipo), nmero de blocos, f); ambas as funes recebem um apontador a memria a ler/gravar no ficheiro, bem como o tamanho de cada bloco e nmero de blocos.

12. Ficheiros
149 150 151 152 153 154 155 156 } Programa { VIGravar(&vector,f); fclose(f); } } MostraVector(&vector); VILibertar(&vector);

141

12-4 Actualizao do tipo abstracto TVectorInt para ler/gravar para ficheiro, em modo binrio

Execuo de exemplo:
C:\>vectoraleatorio8 vectoraleatorio8 1 7 4 0 9 4 8 8 2 4 C:\>vectoraleatorio8 41 67 34 0 69 24 78 58 62 64 C:\>vectoraleatorio8 41 67 34 0 69 24 78 58 62 64

10 10

10 100

10 100

A ltima execuo foi lida do ficheiro, e no gerada. Neste caso a operao de gerar um valor aleatrio tem um custo computacional muito baixo, no vale a pena gravar para ficheiro. Se fosse uma operao mais demorada, o programa tiraria vantagem de ter j no passado feito essa computao, e agora seria respondido ao pedido de forma imediata. Se por algum motivo os ficheiros fossem apagados, o programa voltaria a fazer os clculos. Este um tipo de estratgia utilizado inclusive em algoritmos e com memria em vez de ficheiros, guardando resultado de alguns clculos pesados em memria, para no caso de serem necessrios no futuro poupar-se o tempo de os recalcular. Notar que as funes VILer e VIGravar so muito curtas, uma vez que as funes fread e fwrite esto adaptadas leitura de vectores de estruturas, sendo necessrio apenas alocar a memria necessria, e chamar as funes. Os dois ficheiros que foram criados automaticamente na execuo de exemplo, n10b10.dat e n10b100.dat ocupam 44 bytes cada, que precisamente o espao necessrio para guardar em memria os 10 valores inteiros de 4 bytes, e um outro com o tamanho do vector. Se a informao for lida sequencialmente, as funes fseek16 e ftell17 no so necessrias, caso contrrio pode-se utilizar estas funes para reposicionar o ponto de leitura ou escrita do ficheiro. Este tipo de utilizao foi no entanto mais relevante no passado, em que havia pouca memria e os ficheiros eram utilizados como se fossem blocos de memria, ficando o ficheiro sempre aberto, e o algoritmo em vez de aceder a um valor em memria teria de aceder a um registo no ficheiro. um processo muito mais lento que o acesso memria, mas necessrio em tempos em que a memria principal era escassa, e actualmente pode ainda ser utilizado em casos muito particulares.

16

Utilizao para posicionamento no ficheiro: fseek(f,posicao,SEEK_SET); outras constantes podem ser utilizadas, SEEK_CUR para a posio ser relativa posio corrente, e SEEK_END para a posio ser relativa ao fim do ficheiro 17 Leitura da posio actual no ficheiro: posicao=fteel(f);

142

Parte III Memria, Estruturas e Ficheiros

No modo texto pode-se editar com editores externos, no modo binrio tal mais difcil, mas ocupa menos espao e mais rpido de ler. Estas duas possibilidades so a base para a tomada de deciso, que tem vindo cada vez mais a recair para o modo de texto em XML, formato este que no deixa de ser texto, mas segue um conjunto de regras mnimas da organizao da informao em tags. A razo desta opo simples: os dados muitas vezes duram mais que as aplicaes. No deve ser optado pelo modo binrio como forma de segurana, dado que facilmente se faz um editor que leia o dados, apenas encriptando possvel adicionar segurana. Erros comuns mais directamente associados a este captulo: Abrir um ficheiro e no chegar a fechar o Forma: Por vezes abre-se o ficheiro, mas a operao de fechar no causa erros e desnecessria dado que o ficheiro ser fechado quando o programa acabar o Problema: O nmero de ficheiros abertos um recurso limitado pelo sistema operativo. Se um ficheiro no mais preciso, deve ser imediatamente fechado de forma a libertar recursos. Se esta prtica no for feita, pode acontecer que pedidos de abertura de ficheiros por esta ou outra aplicao sejam recusados o Resoluo: Verificar o ltimo local onde o ficheiro necessrio, e chamar a correspondente funo fclose de forma a fechar o ficheiro Abrir um ficheiro e no testar se foi bem aberto o Forma: Quando se sabe que o ficheiro existe, no necessrio testar se a operao de abertura do ficheiro foi bem sucedida, perda de tempo o Problema: Pode acontecer mesmo quando h a certeza que o ficheiro existe, que a operao de abertura de ficheiro seja mal sucedida, no caso de estarem demasiados ficheiros abertos, ou por questes de permisses, ou ainda por estarem duas aplicaes a tentar abrir o ficheiro em modo de escrita. As operaes seguintes, que utilizam o apontador retornado para o ficheiro, ficam com um procedimento incerto o Resoluo: Testar sempre se o ficheiro foi bem aberto, e apenas utilizar o apontador para o ficheiro no caso de este ser diferente de NULL

Exemplo destes dois erros pode ser a simplificao do Programa 12-1 apagando as linhas 8 e 9, bem como a linha 12.

13. Truques

143

13.

TRUQUES

Vamos acabar, no com o mais importante, mas sim com o que h de menos importante na linguagem C, que so os operadores binrios (que manipulam bits), bem perto do assembly. Falaremos tambm do operador ? e da estrutura union. Antes dos operadores binrios, introduz-se o operador ?, que permite fazer um condicional dentro de uma expresso:
(cond?exp1:exp2)

Este operador til em situaes que tanto cond, como exp1 e exp2 so pequenas, e levaria criao de um condicional if quando uma pequena expresso servia. O exemplo clssico de boa utilizao a seguinte:
x=(a>b?a:b); /* em x fica o mximo de a e b */ if(a>b) x=a; else x=b;

A verso com o if claramente mais complexa. O operador ? pode no entanto ser mal utilizado se cond, exp1 ou exp2 forem muito longos, ficando o cdigo de difcil leitura. O exemplo do clculo dos dias do ms pode ser reduzido ao clculo de uma s expresso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); dias= (mes==2 ? (ano%400==0 || ano%4==0 && ano%100!=0 ? 29 : 28) : (mes==1 || mes==3 || mes==5 || mes==7 || mes==8 || mes==10 || mes==12 ? 31 : 30)); printf("%d",dias); }
Programa 13-1 Verso condensada do nmero de dias de um dado ms/ano

A funcionalidade deste programa igual aos restantes. No entanto as expresses lgicas utilizadas so muito grandes, pelo que esta verso perde legibilidade. Mesmo na expresso lgica, este operador pode ser utilizado. Ainda no exemplo dos dias do ms, pode-se substituir as diversas igualdades para teste de ms de 31 dias por um operador ?

144
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h>

Parte III Memria, Estruturas e Ficheiros

int main() { int ano, mes, dias; printf("Indique ano: "); scanf("%d", &ano); printf("Indique mes: "); scanf("%d", &mes); dias= (mes==2 ? (ano%400==0 || ano%4==0 && ano%100!=0 ? 29 : 28) : ((mes%2==1?mes<=7:mes>=8) ? 31 : 30)); printf("%d",dias); } Programa 13-2 Verso ainda mais condensada do nmero de dias de um dado ms/ano

Neste caso tem-se compactao da expresso, mas a legibilidade muito reduzida, e a utilizar temse de acompanhar o cdigo de um bom comentrio explicativo. Este operador apenas est nesta seco, dado que quem est a aprender conveniente no ver este tipo de controlo de fluxo, porque na verdade o controle de fluxo dentro de uma expresso, que para o alto nvel da linguagem C, representa uma s instruo. No h controlo de fluxo sem vrias instrues. Na verdade no h contradio, dado que a implementao de uma expresso de C em assembly resulta em mltiplas instrues. Uma particularidade da linguagem C, utilizado por vezes em conjuno com o operador ?, a possibilidade de utilizar atribuies como expresses. Isto , como uma atribuio retorna o valor atribudo, pode ser utilizado dentro de uma expresso. A situao mais natural para fazer uso deste conhecimento a atribuio do mesmo valor a vrias variveis, por exemplo:
x=y=z=0;

Neste exemplo a expresso resultante de z=0 foi utilizada para a atribuio a y, que por sua vez foi utilizada como expresso para a atribuio a x. No entanto pode ser utilizado em situaes mais imaginativas, sem benefcio para a legibilidade do cdigo:
a=(b>(c=0)?b=-b:++c);

A expresso anterior abstracta, dado que para m utilizao do conhecimento no existem bons exemplos. A nica varivel que tem de ter um valor antes da instruo acima a varivel b. Por exemplo, se esta for 10, os valores das variveis ficam a=-10; b=-10; c=0;. Caso seja -10, os valores ficam a=1; b=-10; c=1;. A sequncia de operaes a seguinte: 1. Avaliado b>(c=0) a. Atribudo c=0 b. Retornado c 2. Se verdadeiro, avaliado b=-b a. Atribudo b=-b b. Retornado b 3. Se falso, avaliado ++c a. Incrementado c b. Retornado c 4. Atribudo a a o valor retornado em 2 ou 3

13. Truques

145

A mais-valia dos operadores binrios (que manipulam os bits) disponibilizar em linguagem de alto nvel, operaes de custo muito baixo em assembly e que existem em todos os computadores modernos, que podem ser teis, para alm dos habituais truques envolvendo operaes com potncias de 2, para a implementao eficiente de algumas operaes normalmente envolvendo tipos de dados abstractos. Todas as operaes envolvendo tipos de dados, so tipicamente muito utilizadas, pelo que devem ser optimizadas o melhor possvel. O operador >> (e correspondente >>=, bem como as verses inversas << e <<=) so operadores de deslocao de bits dentro da varivel. Os bits encostados perdem-se. Deslocao:
a = a >> b; ( a>>=b; ) a = a << b; ( a<<=b; )

Tal como num nmero em base decimal, esta operao significa deslocar os dgitos de um nmero para a esquerda ou direita. Na base decimal, o resultado multiplicar ou dividir por 10, por cada deslocao. Em base binria, o resultado multiplicar ou dividir por 2. Como esta operao mais rpida que qualquer operao aritmtica, comum multiplicar/dividir por potncias de 2 utilizando deslocamento de bits.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> #include <time.h> #define CICLOS 100000000 int main() { int valor=12345,i; clock_t instante; instante=clock(); for(i=0;i<CICLOS;i++); printf("\nTempo de %d ciclos vazios: %.3f", CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); instante=clock(); for(i=0;i<CICLOS;i++) { valor*=2; valor/=2; } printf("\nTempo de %d multiplicacoes e divisoes por 2: %.3f", CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); instante=clock(); for(i=0;i<CICLOS;i++) { valor<<=1; valor>>=1; } printf("\nTempo de %d operacoes de deslocacao: %.3f", CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); } Programa 13-3 Instruo de shift vs multiplicao/diviso por 2

Execuo de exemplo:
C:\>deslocacao Tempo de 100000000 ciclos vazios: 0.454 Tempo de 100000000 multiplicacoes e divisoes por 2: 2.093 Tempo de 100000000 operacoes de deslocacao: 0.688

146

Parte III Memria, Estruturas e Ficheiros

Como se pode observar, a vantagem tentadora. O ciclo vazio feito inicialmente, uma vez que estas operaes tm um custo muito baixo, e o custo de operao do ciclo relevante. Pode-se estimar o tempo gasto realmente nas operaes como sendo 0,234 e 1,639 segundos, pelo que a operao de deslocao de um bit cerca de 7 vezes mais rpida que a operao de multiplicao/diviso por 2. As operaes binrias lgicas implementam cada funo lgica mas bit a bit. Estas operaes tm tal como as operaes de deslocamento, um custo muito baixo de execuo. Operaes lgicas binrias:
a=a|b; (a|=b;) a=a&b; (a&=b;) a=a^b; (a^=b;) ~a complemento or and xor (negao binria)

Um nmero em base decimal, o dgito menos significativo tambm o resto da diviso do nmero por 10, e da mesma forma, os dgitos menos significativos em binrio, so o resto da diviso por a correspondente potncia de 2. Com os operadores binrios, o resto da diviso por 2 obtm-se simplesmente: a&1. Notar que apenas o dgito mais baixo poder ficar no nulo. O resto da diviso com 4 seria: a&3. Esta operao naturalmente mais rpida que a operao normal a%2, e a%4. Vamos mostrar agora os bits num nmero real, de forma a exemplificar alguma manipulao de bits. Vamos ler um real para um inteiro, e mostrar no ecr todos os seus bits.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <time.h> int main() { unsigned int valor, bitAlto; int i; printf("Valor real:"); scanf("%g",&valor); bitAlto=1<<31; for(i=0;i<32;i++) { printf("%c",(valor&bitAlto?'1':'0')); valor<<=1; } } Programa 13-4 Representao binria de um nmero real

Execuo de exemplo:
C:\>bits Valor real:1 00111111100000000000000000000000 C:\>bits Valor real:-1 10111111100000000000000000000000 C:\>bits Valor real:2 01000000000000000000000000000000 C:\>bits Valor real:1.5 00111111110000000000000000000000

13. Truques
C:\>bits Valor real:0.1 00111101110011001100110011001101 C:>bits Valor real:0.125 00111110000000000000000000000000 C:\>bits Valor real:1.5 00111111110000000000000000000000

147

No exemplo colocou-se numa varivel o bit mais alto, de forma a detectar o seu valor, e foi-se fazendo operaes de deslocao at estarem todos os bits processados. Pode-se assim ver como composto o nmero do tipo real, com o primeiro bit sendo o sinal, seguido de 8 a comporem o

1, MANTISSA2 2 expoente, e os restantes a mantissa, seguindo a frmula: ( 1) . No entanto o tipo real tem mais particularidades, nomeadamente situaes especiais de infinito, e nmero no vlido, pelo que no se aconselha fazer a manipulao binria de reais, este exemplo apenas pretende ilustrar optimizaes possveis.
SINAL EXPOENTE255

Tivemos que fazer um artifcio para ver o real como inteiro, foi indicar ao scanf um apontador para inteiro sem sinal, quando o scanf esperava um real. A linguagem C tem uma estrutura union que permite uma mesma varivel comportar-se como sendo de diferentes tipos, evitando assim o artifcio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> #include <time.h> int main() { unsigned int bitAlto; int i; union { unsigned int inteiro; float real; } valor; printf("Valor real:"); scanf("%g",&valor.real); bitAlto=1<<31; for(i=0;i<32;i++) { printf("%c",(valor.inteiro&bitAlto?'1':'0')); valor.inteiro<<=1; } } Programa 13-5 Verso do Programa 13-4 utilizando a estrutura union

A estrutura union idntica a struct, mas a sua utilidade muito inferior. Apenas em raras excepes poder esta estrutura ser til. O conhecimento da representao binria dos nmeros reais, poderia ser til para pequenas operaes, como saber se o nmero negativo bastar comparar o bit mais alto, ou para multiplicar/dividir por 2, bastar somar/subtrair a zona do expoente, o que corresponde a fazer uma soma/subtraco inteira por o nmero 1 devidamente deslocado. Vamos medir o ganho potencial desta ltima operao:

148

Parte III Memria, Estruturas e Ficheiros

1 #include <stdio.h> 2 #include <time.h> 3 4 #define CICLOS 100000000 5 6 int main() 7 { 8 union { 9 unsigned int inteiro; 10 float real; 11 } valor; 12 int i,incremento; 13 clock_t instante; 14 15 valor.real=1.0; 16 incremento=1<<23; 17 18 printf("\nReal: %f",valor.real); 19 valor.inteiro+=incremento; 20 printf("\nReal *2: %f",valor.real); 21 valor.inteiro-=incremento; 22 printf("\nReal /2: %f",valor.real); 23 24 instante=clock(); 25 for(i=0;i<CICLOS;i++); 26 printf("\nTempo de %d ciclos vazios: %.3f", 27 CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); 28 29 instante=clock(); 30 for(i=0;i<CICLOS;i++) 31 { 32 valor.real*=2; 33 valor.real/=2; 34 } 35 printf("\nTempo de %d operacoes *=2 e /=2 float: %.3f", 36 CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); 37 instante=clock(); 38 for(i=0;i<CICLOS;i++) 39 { 40 valor.inteiro+=incremento; 41 valor.inteiro-=incremento; 42 } 43 printf("\nTempo de %d operacoes += e -= int: %.3f", 44 CICLOS, (float)(clock()-instante)/CLOCKS_PER_SEC); 45 } Programa 13-6 Utilizao do formato do nmero real para aumentar a eficincia de algumas operaes

Execuo de exemplo:
C:\>mult Real: 1.000000 Real *2: 2.000000 Real /2: 1.000000 Tempo de 100000000 ciclos vazios: 0.453 Tempo de 100000000 operacoes *=2 e /=2 float: 1.266 Tempo de 100000000 operacoes += e -= int: 0.718

H aqui um ganho potencial de 3 vezes os produtos efectuados na verso inteira. Este tipo de utilizao poder no entanto dar mais trabalho que recompensa, na medida que as condies optimizveis que fogem ao procedimento normal no so muito frequentes, e podem ser executadas um nmero diminuto de nmero de vezes no algoritmo no compensando nem o trabalho nem a perda de legibilidade que essa utilizao implica.

13. Truques

149

Uma boa utilizao dos operadores binrios para colocar numa s varivel inteira, vrias variveis booleanas. Exemplifica-se com caractersticas de produtos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> #define #define #define #define NACIONAL 1 BARATO 2 QUALIDADE 4 LEVE 8

void MostrarProduto(int produto) { if(produto&NACIONAL) printf("Nacional; "); if(produto&BARATO) printf("Barato; "); if(produto&QUALIDADE) printf("Qualidade; "); if(produto&LEVE) printf("Leve; "); } main() { int produto[4],i; /* 0: produto barato e de qualidade */ produto[0]=BARATO|QUALIDADE; /* 1: produto nacional e leve */ produto[1]=NACIONAL|LEVE; /* 2: igual ao produto 0, mas no barato */ produto[2]=produto[0]&(~BARATO); /* 3: todas as qualidades do 1 e 2, excepto o ser nacional que inverso */ produto[3]=(produto[1]|produto[2])^NACIONAL; /* mostrar as caractersticas dos produtos */ for(i=0;i<4;i++) { printf("\nProduto %d: "); MostrarProduto(produto[i]); } } Programa 13-7 Exemplo de utilizao de vrias variveis booleanas numa s varivei inteira

Execuo de exemplo:
C:\>flags Produto Produto Produto Produto 0: 1: 2: 3: Barato; Qualidade; Nacional; Leve; Qualidade; Qualidade; Leve;

No exemplo colocou-se macros para cada varivel booleana, e em cada uma est uma potncia de 2 distinta. Ao atribuir um valor basta fazer uma disjuno com a macro, como feito no exemplo nos produtos 0 e 1. Para remover um valor, basta fazer uma conjuno com o complementar da varivel, como feito no produto 2. No produto 3 troca-se um valor utilizando o XOR binrio. Na funo MostrarProduto pode-se ver como se l facilmente os valores, fazendo a conjuno com a macro respectiva.

150

Parte III Memria, Estruturas e Ficheiros

Erros comuns mais directamente associados a este captulo: Comentrios inexistentes em partes complexas o Forma: O cdigo deve ser lido por quem perceba no s da linguagem de programao C, como de algoritmos, e que seja suficientemente inteligente para o compreender o Problema: Quem l cdigo C deve naturalmente possuir todas essas caractersticas, mas tal no motivo para no explicar as partes complexas. Os comentrios podem afectar a legibilidade do cdigo, e quem est a ler cdigo em que tem dificuldade em compreender, no ir apreciar a forma rebuscada e hbil de quem o escreveu, mas sim a simplicidade e clareza com que o fez, ou o explica. Se no conseguir compreender algo, ser certamente por culpa de quem escreveu que no sabia o que fazia e l acabou por obter uma soluo funcional em modo de tentativa e erro, de baixa qualidade o Resoluo: Nas zonas menos claras, comentar explicando todos os truques / habilidades aplicadas. Se ficar algum por explicar, ser confundido certamente com cdigo que nem quem o fez sabe realmente porque funciona, e mais vale refazer novamente se houver algum problema

Exemplo de ocorrncias deste erro encontrar certamente ao rever o seu cdigo antigo. um bom exerccio rever o seu prprio cdigo, para poder com alguma distncia da altura em que o escreveu, reflectir sobre o que necessita realmente de ter em comentrio no cdigo para o ajudar a compreend-lo quando j no se lembrar dele.

3) Exerccios

151

3) Exerccios adiciona.c
Faa uma estrutura para uma lista de inteiros de ligao simples (apontador para o elemento seguinte da lista). Implemente as funes para adicionar um elemento lista, remover o elemento no topo da lista, e para calcular o nmero de elementos na lista. Notas:

No se esquea de remover todos os elementos na lista, antes de sair do programa. Tudo o que alocar deve libertar.

Execuo de exemplo:
C:\>adiciona Numero de elementos: 1000 Elementos: xxx 116 80 686 693 309 598 650 629 676

Pergunta: adicione 1000 nmeros aleatrios entre 0 e 999 a uma lista, e calcule o nmero de elementos na lista, bem como o valor do primeiro elemento na lista. Indique na resposta o primeiro elemento na lista.

more.c
Faa um programa que receba um ficheiro de texto em argumento, e o mostre no ecr, utilizando os primeiros 4 caracteres para numerar a linha. Se uma linha tiver mais de 75 caracteres, a mesma mostrada em vrias linhas, mas o nmero de linha apresentado sempre a do ficheiro. Considere que o comprimento mximo de uma linha de 1024 caracteres. Notas:

Abra um ficheiro em modo de texto, e no se preocupe agora com a alocao de memria Verifique se o ficheiro foi bem aberto, e se foi dado um argumento com o nome do ficheiro

Execuo de exemplo:
C:\>more more.c 1:#include <stdio.h> 2: 3:#define MAXSTR 1024 4: 5:int main(int argc, char **argv) 6:{ 7: FILE *f; 8: char str[MAXSTR], *pt, c; 9: int count = 0; 10:

152
11: 12: 13: 14: 15: 16: ...

Parte III Memria, Estruturas e Ficheiros


/* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizao: more <ficheiro>\n"); return; }

Pergunta: copie o texto desde "more.c" at s notas acabando em "nome do ficheiro", para um ficheiro de texto (abra no Notepad), e grave. Assegure-se que o ficheiro de texto no Notepad tem apenas 5 linhas (no utilizar o Word wrap), cada linha no comea com espaos (se tiver apagueos), no existindo bullets. Abra de seguida o ficheiro com a aplicao more, e indique na resposta separado por espaos: nmero de linhas (do ficheiro); nmero de linhas (do ficheiro) cortadas; palavras cortadas na mudana de linha por ordem (e separadas por um espao, no incluindo vrgulas ou pontos se existirem).

insere.c
Sobre o cdigo desenvolvido no exerccio adiciona.c, faa uma funo para inserir um elemento numa lista de inteiros, aps um elemento menor e antes de um elemento maior ou igual (insere de forma ordenada). Faa ainda uma outra funo para apagar os elementos de uma lista de inteiros, que sejam iguais a um determinado valor. Notas:

Pode chamar as operaes atmicas desenvolvidas de adio, remoo e nmero de elementos, para implementar a insero ordenada e remoo dos elementos com um determinado valor

Execuo de exemplo:
C:\>insere Elementos apos inserir 1000 elementos: 1 2 3 3 3 6 7 7 8 8 Numero de elementos na lista apos apagar: xxx Elementos apos apagar: 1 3 3 3 7 7 9 11 11 15

Pergunta: insira ordenadamente na lista 1000 elementos aleatrios de valores entre 0 e 999, chamando srand(1), e apague todos os nmeros na lista que sejam pares. Indique como resposta o nmero de elementos da lista.

3) Exerccios

153

insertsort.c
Utilizando as funes desenvolvidas dos exerccios anteriores, nomeadamente a insero de um elemento por ordem, faa uma funo para ordenar uma lista atravs do algoritmo InsertSort: inserir cada elemento ordenadamente numa lista nova. Notas:

Convm ter em ateno para no deixar blocos alocados no libertados. Veja com ateno se todos os blocos alocados no seu cdigo so libertados no final.

Execuo de exemplo:
C:\>insertsort Lista nao ordenada: 249 116 80 686 693 309 598 650 629 676 Lista: 249 Lista: 116 249 Lista: 80 116 249 Lista: 80 116 249 686 Lista: 80 116 249 686 693 Lista: 80 116 249 309 686 693 Lista: 80 116 249 309 598 686 693 Lista: 80 116 249 309 598 650 686 693 Lista: 80 116 249 309 598 629 650 686 693 Lista: 80 116 249 309 598 629 650 676 686 693 Lista ordenada: x x x x x x 7 7 8 8

Pergunta: construa uma lista com elementos aleatrios entre 0 e 999 (chame srand(1)), e ordene no final a lista. Indique na resposta os trs primeiros elementos da lista, separados por espaos.

enesimo.c
Sobre o cdigo desenvolvido no exerccio insertsort.c, faa uma funo para retornar o ensimo elemento numa lista de inteiros. Notas:

Ao reutilizar todo o exerccio insertsort.c, apenas tem de implementar a nova funo.

Execuo de exemplo:
C:\>enesimo elementos 250, 500 e 750: xxx xxx xxx

Pergunta: utilize o vector do exerccio insertsort.c, e indique como resposta os elementos nas posies 250, 500 e 750 separados por espaos.

154

Parte III Memria, Estruturas e Ficheiros

palavras.c
Faa um programa que recebe o nome de um ficheiro, e retorna o nmero de palavras distintas do ficheiro, que tenham apenas letras separadas por espaos, tabs e fins-de-linha. Notas:

Pode utilizar a funo strtok

Execuo de exemplo:
C:\>palavras palavras.c [1] #define MAXSTR 1024 [10] /* funo que dada uma string retorna o nmero de palavras */ [1] int Palavras(char *str) [1] char strBackup[MAXSTR]; ...

Pergunta: faa um ficheiro com o enunciado (desde "palavras.c:" at "fins-de-linha."), e indique quantas palavras so retornadas.

cifracesar.c
Faa um programa que recebe um ficheiro de texto, e uma chave K, e mostra o ficheiro codificado com a cifra de Jlio Csar. Esta cifra substitui cada carcter por K caracteres frente. Aplique a cifra apenas s letras e dgitos, dando a volta tanto nas letras como nos dgitos, isto , 0 com um K de 11, fica 1 (10 dgitos mais 1). As letras maisculas e minsculas no se misturam, do a volta separadamente. Notas:

Identifique a frmula que permite obter o dgito certo, dando a volta.

Execuo de exemplo:
C:\>cifracesar cifracesar.c 13 #vapyhqr <fgqvb.u> #vapyhqr <pglcr.u> #vapyhqr <fgevat.u> #qrsvar ZNKFGE 588 ibvq PvsenQrPrfne(pune *fge, vag punir) { vag v, punirq; ... Pergunta: coloque num ficheiro o texto "lroajlnbja 32101" codificada pela cifra de Csar com o cdigo "21090". Indique na resposta o seu contedo original (utilizar o valor simtrico para

descodificar).

3) Exerccios

155

more.c
Faa agora o exerccio more, mas para modo binrio. Coloque 16 caracteres por linha, na zona da direita coloque os caracteres imprimveis, na zona da esquerda coloque o nmero do byte do primeiro carcter na linha, e na zona central 16*3=48 caracteres com o valor hexadecimal de cada carcter. Notas:
isprint uma funo da biblioteca ctype.h, que pode ser utilizada para saber se um

carcter imprimvel Para imprimir a verso hexadecimal de um nmero, a string de formatao do printf %x

Execuo de exemplo:
C:\>more more.c 0|23 16|68 32|79 48|65 64|0a 80|67 96|0d 112|3b 128|0d ... 69 3e 70 20 69 63 0a 0d 0a 6e 0d 65 4d 6e 2c 7b 0a 20 63 0a 2e 41 74 20 0d 20 20 6c 23 68 58 20 63 0a 20 20 75 69 3e 53 6d 68 20 20 20 64 6e 0d 54 61 61 20 20 69 65 63 0a 52 69 72 20 63 6e 20 6c 0d 20 6e 20 20 68 74 3c 75 0a 31 28 2a 46 61 20 73 64 23 30 69 2a 49 72 69 74 65 64 32 6e 61 4c 20 2c 64 20 65 34 74 72 45 2a 20 69 3c 66 0d 20 67 20 70 6a 6f 63 69 0a 61 76 2a 74 2c 2e 74 6e 0d 72 29 66 3b 20 |#include <stdio. |h> #include <ct |ype.h> #defin |e MAXSTR 1024 | int main(int ar |gc, char **argv) | { FILE *f |; char *pt; | int i, j,

Pergunta: faa um ficheiro com o enunciado do problema (desde "more.c" at "carcter."), e veja o ficheiro com o utilitrio desenvolvido (certifique-se que o ficheiro tem 2 linhas, e cada linha no comea com espaos). Indique na resposta, separado por um espao, a posio no ficheiro das palavras: "more.c"; "Coloque"; "16*3=48"

basededados.c
Faa um programa que guarde num ficheiro os registos de contactos, com nome, email, telefone, cidade e descrio. O programa permite manter os registos entre execues, utilizando um ficheiro binrio, e permite listar registos, adicionar/ver/editar qualquer registo. Notas:

Assuma que as strings tm um valor mximo de 255 caracteres

156 Execuo de exemplo:


C:\>basededados ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 2 Nome: Jos coelho Telefone: 123 456 789 Cidade: Lisboa Descrio: ... ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 1 ######## # Lista: ######## # 0 | Jos coelho ######## ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 4 ID: 0 Nome: Jos Coelho Telefone: 123 456 789 Cidade: Lisboa Descrio: ...

Parte III Memria, Estruturas e Ficheiros

######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 1 ######## # Lista: ######## # 0 | Jos Coelho ######## ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: C:\>basededados ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 2 Nome: Afonso Henriques Telefone: 123 456 789 Cidade: Guimares Descrio: ...

######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 1 ######## # Lista: ######## # 0 | Jos Coelho # 1 | Afonso Henriques ######## ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: 3 ID: 0 ############# # Nome | Jos Coelho # Telefone | 123 456 789 # Cidade | Lisboa # Descrio | ... ############# ######################### # Menu: # ######################### # 1 | LISTAR registos # # 2 | ADICIONAR registo # # 3 | VER registo # # 4 | EDITAR registo # ######################### Opo: C:\>

Pergunta: qual foi o modo de abertura do ficheiro da base de dados, que utilizou para gravar um registo aps ser editado?

mergesort.c
Faa uma funo para ordenar uma lista de inteiros, com o algoritmo do MergeSort: ordenar cada metade da lista (no obrigatoriamente a primeira metade, apenas metade dos elementos), recursivamente, e no final juntar ambas as metades ordenadas, numa s lista ordenada. Notas:

Basta reutilizar as funes de adio e remoo de elementos numa lista, dos exerccios anteriores sobre listas de inteiros. Ateno ao enunciado, esta funo MergeSort em listas mais simples que a correspondente verso em vectores

3) Exerccios Execuo de exemplo:


C:\>mergesort Elemento na posio 1000: x Elemento na posio 10000: xx

157

Pergunta: Adicione 100.000 de elementos, gerados aleatoriamente e com valores entre 0 e 999 (chame srand(1);), e ordene atravs desta funo. Indique como resposta o elemento na posio 1000, seguido do elemento na posio 10.000.

sort.c
Adapte a lista de inteiros para uma lista de strings, e carregue um ficheiro de texto que recebe como argumento do programa. Deve carregar cada linha do ficheiro em cada elemento da lista de strings. Adapte agora o MergeSort de listas de inteiros para listas de strings, e imprima o ficheiro por ordem alfabtica das linhas. Notas:

Utilize strcmp que compara duas strings, devolvendo 0 se as strings so iguais, e valor positivo se uma maior que a outra na ordem alfabtica, e negativo no caso contrrio.

Execuo de exemplo:
C:\>sort sort.c lista = Adiciona(lista, lista1->str); lista = Adiciona(lista, lista2->str); lista = Adiciona(lista, str); lista = Remove(lista); lista1 = Remove(lista1); lista2 = Adiciona(lista2, lista->str); lista2 = Remove(lista2); /* alocar espao para a string, e copiar a string */ ...

Pergunta: execute a aplicao dando como entrada o ficheiro de texto que tem mais mo: cdigo fonte C. Qual o primeiro carcter na ltima linha de praticamente todos os ficheiros com cdigo fonte C?

find.c
Faa um programa que recebe um ficheiro e uma string de procura, com wilcards (* significa zero ou mais caracteres, e ? significa um carcter qualquer), e retorne todas as linhas de match positivas. Considere que um match ocorre apenas dentro de uma s linha. Notas:

Faa a funo Find, que recebe uma string, e um texto de procura, e retorna a primeira ocorrncia desse texto na string. A funo deve aceitar wilcards e poder ser chamada recursivamente. Se no conseguir os wilcards, faa primeiro sem wilcards, e depois adicione o ?, e finalmente o * Pode utilizar como ponto de partida o exerccio 3) more.c

158 Execuo de exemplo:

Parte III Memria, Estruturas e Ficheiros

C:\>find find.c if(*==*) 16: if(find[ifind] == 0) 19: if(find[ifind] == '?') 24: else if(find[ifind] == '*') 33: else if(find[ifind] == str[istr]) 64: if(f == NULL) 86: if(pt == str)

Pergunta: copie a resoluo proposta do exerccio 3) more.c (primeiro exerccio) para um ficheiro de texto, e execute as seguintes procuras sobre esse ficheiro por ordem: "!"; "<*>"; "(*,*,*)"; "[?]". Indique na resposta os nmeros das linhas retornadas, por ordem e separados por espaos.

histograma.c
Faa um programa que recebe um ficheiro, e faa um histograma dos caracteres alfanumricos no ficheiro. As maisculas e minsculas devem ser considerados caracteres distintos. Notas:

Pode reutilizar a funo desenvolvida no exerccio 2) doisdados.c

Execuo de exemplo:
C:\>histograma histograma.c 130| # 121| # 113| # 104| # # 95| # # 87| # # # 78| # # # 69| # # # 61| # # # # 52| # # # # ## ### 43| # # ## # ## ### 35| # # ## # ## ### 26| # # ## ## ## ### 17|# # ####### ### #### 9|### # # # ## # ######## ##### #### 0|############################################################## ++----+----+----+----+----+----+----+----+----+----+----+----+0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz Numero de cardinais no histograma: 164

Pergunta: aproveitando o ficheiro 3) more.c, utilizado no exerccio anterior, faa o seu histograma e indique na resposta: a frequncia absoluta mais alta; letra (ou nmero) com a frequncia mais alta; trs letras minsculas com frequncia nulas (ordem alfabtica). Coloque os trs valores separados por espaos.

3) Exerccios

159

indicador.c
Faa um programa que leia um ficheiro de cdigo em linguagem C, e processe todos os blocos globais, sendo um bloco global todos os pares de chavetas que no esto dentro de nenhum outro par de chavetas. O programa deve retornar o seguinte indicador de complexidade, bem como os seus parciais:

I=
Notas:

(Conds + 2Ciclos + 1)
i i i =1... N

+ Instruesi

Condsi o nmero de condicionais no bloco global i (if/case)


Ciclosi o nmero de ciclos no bloco global i (for/while) Instrues o nmero de instrues no bloco global i (contabilizar os ponto e vrgula). i
O programa deve indicar os parciais por cada bloco global, indicando o nmero de condicionais, ciclos, comandos e o parcial at ao momento aps o processamento desse bloco. Tenha em ateno aos comentrios, strings e caracteres que no so necessrios para o clculo deste indicador

Execuo de exemplo:
C:\>indicador indicador.c conds ciclos comandos 0 0 2 1 0 6 1 0 5 0 1 4 0 3 5 4 3 17 2 2 10 4 6 24 3 3 18 Indicador: 717 parcial 3 13 22 35 89 227 286 599 717

Pergunta: aproveitando o ficheiro 3) more.c, utilizado nos exerccios anteriores, indique na resoluo todos os parciais (4 nmeros por cada bloco), separados por espaos.

PARTE IV ANEXOS

14. Compilador e Editor de C

161

14.

COMPILADOR E EDITOR DE C

Neste anexo esto as instrues para a instalao do compilador e editor aconselhado. Pode naturalmente utilizar qualquer outro. No entanto, de evitar a utilizao de um ambiente de desenvolvimento com o compilador, editor e debugger integrado, como o Visual Studio, Dev-C++, ou o Eclipse. Ter oportunidade de o fazer quando tiver experincia de programao, actualmente um ambiente de desenvolvimento pode ser um factor de distraco, e em nada contribui para a aquisio das competncias: capacidade de implementao de pequenos programas. Aconselha-se um pequeno compilador de linha de comando, e opcionalmente um editor com algumas facilidades de edio e visualizao de cdigo C.

TCC: TINY C COMPILER


URL: http://bellard.org/tcc/ Instalao: 1. 2. 3. 4. Descarregar (Windows binary distribution) do site; Descompactar os ficheiros para "C:\Program Files"; Colocar no path a directoria "C:\Program Files\tcc"; Escrever e executar o programa "Ola Mundo!".

Colocar no path a directoria "C:\Program Files\tcc" "C:\ Files\tcc":

Control Panel System Advanced Environment Variables:

Na dialog das variveis de ambiente, seleccione a varivel Path (cuidado para no apagar o seu contedo):

162

Parte IV Anexos
Ao editar, deve adicionar no final a directoria "C:\Program Files\tcc". Ateno que as directorias tm de estar separadas por um ponto e vrgula.

Exemplo de utilizao:

Abrir uma linha de comandos:


Iniciar Todos os Programas Acessrios Linha de Comandos

Na linha de comandos v para a directoria onde pretende ter os exerccios. Para mudar de directoria utilize o comando cd (change directory), seguido da directoria (neste exemplo, para no indicar qualquer directoria, utiliza-se a directoria raiz, mas deve criar uma directoria para os exerccios):
C:\...>cd \ ENTER cd

Editar e criar um ficheiro de texto com o Notepad:


C:\>notepad ola.c ENTER

Ao abrir o Notepad, como o ficheiro "ola.c" no existe, h uma mensagem a dizer que este no existe e se o quer criar. Responda que sim. Compilar o ficheiro criado:
C:\>tcc ola.c ENTER tcc

Erros possveis:

'tcc' is not recognized as an internal or external command, operable program or batch file. o provavelmente a varivel path no tem a directoria do tcc correctamente configurada. Para confirmar isso, execute o comando "C:\>path e verifique path" path se a directoria do TCC est no path. tcc: file 'ola.c' not found o provavelmente no editou e gravou um ficheiro ola.c com o Notepad, na mesma directoria onde est. No se esquea de copiar exactamente o que est no exemplo, e ao sair do Notepad gravar ola.c:1: ';' expected o provavelmente o ficheiro gravado no est igual ao exemplo. Copie exactamente o texto no exemplo letra por letra e grave ao sair do Notepad.

Se no houve erros, no devolvido texto nenhum e aparece novamente a prompt "C:\>". Nesse caso executar o programa criado:
C:\>ola ENTER ola Ola Mundo! C:>

Nota: criado um executvel com o mesmo nome que o ficheiro fonte, mas com extenso exe. Nesta UC crie programas utilizando apenas um s ficheiro em linguagem C, neste caso o ficheiro ola.c.

14. Compilador e Editor de C

163

NOTEPAD++
URL: http://notepad-plus-plus.org/

Este editor utiliza cores conforme a sintaxe do C (suporta tambm outras linguagens), de forma a evitar alguns erros sintcticos que porventura se podem cometer por engano no Notepad, e que seriam detectados apenas quando se fosse compilar.

Associe o Notepad++ aos ficheiros com extenso C, de forma a poder abrir o ficheiro no Notepad++ escrevendo apenas o nome do ficheiro na linha de comando, ou clicando no ficheiro no Windows. Clique com o boto direito no ficheiro, e abra seleccionando outra aplicao (seleccione o Notepad++, se no estiver listado, utilize o boto "Browse"). No se esquea de seleccionar a caixa de seleco em baixo.

Para abrir o ficheiro ola.c com o Notepad++, basta agora escrever o nome do ficheiro:
C:\>ola.c ENTER

164

Parte IV Anexos

15.

NO CONSIGO PERCEBER O RESULTADO DO PROGRAMA

Ainda no fiz os exerccios verdes, e no consigo perceber o que est a acontecer no programa, e porque no tem o resultado que espero: 1. Faa a execuo passo-a-passo do seu programa. No h nada como o papel e lpis para compreender um conceito. Ir ver que o programa no faz o que pretende, e assim reflectir sobre a mudana que necessita efectuar no cdigo para ter o comportamento esperado. 2. No h segundo ponto, se no conseguir fazer a execuo passo-a-passo, reveja os captulos, ou pea ajuda a terceiros. J fiz exerccios azuis, e tenho um programa que tem um comportamento e/ou resultado incompreensvel, aparenta mesmo ter vontade prpria: 1. Utilizar os parmetros mais simples possvel, se funcionar complicar ligeiramente os parmetros at obter uma situao de erro; 2. Imprimir no ecr os valores atribudos a variveis, na zona suspeita; 3. Comentar cdigo que insuspeito, e verificar se o erro se mantm, continuando at obter o programa mnimo que d o erro; 4. Tentar formalizar a questo para que uma pessoa externa ao projecto possa analisar o problema. Ao utilizar parmetros mais simples e verificar que funciona bem, pode identificar a zona do cdigo onde no h problema, ficando apenas parte do cdigo suspeito. Ao imprimir os valores das variveis, pode detectar o local e a altura em que os valores deixam de estar correctos. Se o programa funcionar comentando o cdigo insuspeito, pode-se confirmar que o problema reside na zona suspeita. Finalmente, ao tentar formalizar a questo, removendo portanto tudo o que especfico do problema e colocando o cdigo no abstracto de forma a se colocar a questo a outra pessoa, pode-se identificar o problema mesmo antes de consultar a pessoa externa ao projecto. Pode acontecer que seja mais simples a estratgia inversa no ponto 3, ou seja, comentar o cdigo suspeito. Se no for simples manter o programa a funcionar comentando o cdigo insuspeito, pode ser mais fcil manter o programa a funcionar comentando o cdigo suspeito, e verificar se o problema se mantm. Se desaparecer, pode-se reduzir a zona do cdigo suspeito a comentar, de forma a localizar o problema. Situao de desespero: estou s voltas com isto, mesmo incompreensvel! No h nada incompreensvel, o computador no tem vontade prpria, nem o problema que est a lidar complicado demais. Apenas devido ao cansao no consegue ver pormenores que so simples mas lhe passam ao lado e lhe permitiriam descobrir a causa do problema. O melhor parar e continuar numa outra altura, aps uma noite de descanso. O mais provvel ser que ao retornar ao problema j tenha a encontrado resposta para o problema sem saber como.

15. No consigo perceber o resultado do programa

165

EXEMPLO DE CDIGO INCOMPREENSIVEL


Este exemplo foi reportado por um estudante18, sendo a parte incompreensvel o facto de existirem valores finais distintos, com e sem o printf na linha 60. Tal no deveria acontecer porque o printf no altera valores de variveis. Ao cdigo apresentado foi cortado o bloco inicial do main que no est relacionada com o problema, e comentrios:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
18

#include <stdio.h> int mdc(int x, int y) { int i, aux; int n = 0; int resto; int mdc1 = 1; int v1[1000]; resto = y % x; if(resto == 0) { mdc1 = x; } else if(resto > 0) { x--; for(i = 1; i < x; i++) { resto = y % i; if(resto == 0) { v1[n] = i; mdc1 = i; n++; } } n--; x++; aux = 0; for(i = 1; i <= n; i++) { resto = x % v1[i]; if(resto == 0) { mdc1 = v1[i]; aux++; } } } if(aux == 0) mdc1 = 1; return mdc1; }

void main() { int x, y, i, j; int mdcr = 0; int mdc1 = 0; int soma = 0; for(i = 1; i <= 100; i++) {

Espao central, UC Programao, 2010/2011

166
57 58 59 60 61 62 63 64 65 66 }

Parte IV Anexos
for(j = i + 1; j <= 100; j++) { soma += mdc(i, j); /* printf(" (%d,%d) mdc =%d\n", i, j, mdc(i, j)); */ } printf("A soma intermedia: %d\n", soma); } printf("A soma dos MDC dos pares de numeros de 1 a 100: %d\n", soma);

Programa 15-1 Cdigo incompreensvel

Execuo de exemplo:
C:\>exemplo1 A soma intermedia: A soma intermedia: A soma intermedia: A soma intermedia: A soma intermedia: A soma intermedia: ... A soma intermedia: A soma intermedia: A soma intermedia: A soma dos MDC dos 99 197 294 414 509 667 10147 10148 10148 pares de numeros de 1 a 100: 10148

Este valor est incorrecto, mas descomentando o printf, o valor fica correcto. Sabendo que este tipo de funcionamento originado por acesso a valores de memria fora dos locais declarados, pode-se analisar com ateno o cdigo, de forma a verificar todos os locais em que tal ocorre:

Utilizao de uma varivel no inicializada: o Todas as variveis declaradas no inicializadas, tm um comando de atribuio antes da sua utilizao o Os elementos do vector, antes de serem utilizados so sempre inicializados Escrita de uma varivel fora da declarao: o O nico cdigo suspeito o vector v1, que poderia inadvertidamente aceder fora do espao declarado, nomeadamente para valores negativos: aps uma inspeco atenta, verifica-se que o vector inicializado at n, e o ciclo seguinte apenas utiliza valores at n, nunca podendo ser negativos

Se quiser pare de ler aqui e tente encontrar o problema com o cdigo acima. Falhando a inspeco ao cdigo, parte-se para a sua alterao, nomeadamente colocando parmetros mais pequenos, cortando cdigo irrelevante, e colocando printfs com valores das variveis, para se poder reduzir o cdigo suspeito. o que feito no cdigo seguinte, em que o valor 100 nos ciclos foi reduzido para 6, o printf com argumentos, foram retirados os argumentos, e adicionados dois printfs na funo mdc, de forma a saber em que valores da funo os valores retornados so distintos:
1 #include <stdio.h> 2 int mdc(int x, int y) 3 { 4 int i, aux; 5 int n = 0; 6 int resto; 7 int mdc1 = 1; 8 int v1[1000]; 9 printf(" (%d,%d)", x, y); 10 resto = y % x; 11 if(resto == 0)

15. No consigo perceber o resultado do programa


12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 { mdc1 = x; } else if(resto > 0) { x--; for(i = 1; i < x; i++) { resto = y % i; if(resto == 0) { v1[n] = i; mdc1 = i; n++; } } n--; x++; aux = 0; for(i = 1; i <= n; i++) { resto = x % v1[i]; if(resto == 0) { mdc1 = v1[i]; aux++; } } } if(aux == 0) mdc1 = 1; printf("=%d", mdc1); return mdc1; } void main() { int x, y, i, j; int mdcr = 0; int mdc1 = 0; int soma = 0; for(i = 1; i < 6; i++) { for(j = i + 1; j <= 6; j++) { printf(" "); soma += mdc(i, j); } printf("A soma intermedia: %d\n", soma); } printf("A soma dos MDC dos pares de numeros de 1 a 100: %d\n", soma); }

167

Programa 15-2 Programa incompreensvel, preparado para debug

Execuo de exemplo com printf na linha 59:


C:\>exemplo2 (1,2)=1 (1,3)=1 (1,4)=1 (1,5)=1 (1,6)=1A soma intermedia: 5 (2,3)=1 (2,4)=2 (2,5)=1 (2,6)=2A soma intermedia: 11 (3,4)=1 (3,5)=1 (3,6)=3A soma intermedia: 16 (4,5)=1 (4,6)=2A soma intermedia: 19 (5,6)=1A soma intermedia: 20 A soma dos MDC dos pares de numeros de 1 a 100: 20

168

Parte IV Anexos

Execuo de exemplo sem printf na linha 59:


C:\>exemplo2 (1,2)=1 (1,3)=1 (1,4)=1 (1,5)=1 (1,6)=1A soma intermedia: 5 (2,3)=1 (2,4)=1 (2,5)=1 (2,6)=1A soma intermedia: 9 (3,4)=1 (3,5)=1 (3,6)=1A soma intermedia: 12 (4,5)=1 (4,6)=2A soma intermedia: 15 (5,6)=1A soma intermedia: 16 A soma dos MDC dos pares de numeros de 1 a 100: 16

Pode-se agora ver o que no era possvel com 100 valores, despistando tambm a situao da funo mdc ser chamada duas vezes no ciclo, a segunda no printf. As diferenas esto nos pares (2,4), (3,6), (2,6), que so 1 em vez de serem 2, 3 e 2. Esta informao limita grandemente o cdigo suspeito, sendo agora a inspeco ao cdigo centrada na execuo da funo mdc com esses valores. Torna-se claro qual o problema: quando o resto da diviso nula, a varivel aux no est inicializada, sendo no entanto utilizada. Se o seu valor desconhecido for nulo, o resultado da funo incorrecto. A inicializao da varivel aux para um valor no nulo, suficiente para resolver este problema.

16. Funes standard mais utilizadas

169

16.
Exemplos de chamadas:

FUNES STANDARD MAIS UTILIZADAS

printf(texto %d %g %s %c, varInt, varDouble, varStr, varChar);


Imprime no ecr uma string formatada, em que substitudo o %d pela varivel inteira seguinte na lista, o %g pela varivel real na lista, o %s pela varivel string na lista, o %c pela varivel carcter na lista.

scanf(%d, &varInt); gets(str);


scanf a funo inversa do printf, l um inteiro e coloca o seu resultado em varInt, cujo endereo fornecido. A funo gets l uma string para str.

Prottipos:
int atoi(char *str); float atof(char *str);
Converte uma string num nmero inteiro/real respectivamente

int strlen(char *str);


Retorna o nmero de caracteres da string str

strcpy(char *dest, char *str); [strcat]


Copia str para dest, ou junta str no final de dest, respectivamente

char *strstr(char *str, char *find); char *strchr(char *str, char find);
Retorna a primeira ocorrncia de find em str, ou NULL se no existe. Na verso strchr find um carcter.

char *strtok(char *string, char *sep); char *strtok(NULL, char *sep);


Retorna um apontador para uma token, delimitada por sep. A segunda chamada retorna a token seguinte, na mesma string, podendo-se continuar a chamar a funo at que retorne NULL, o que significa que a string inicial no tem mais tokens para serem processadas.

sprintf(char *str, ); sscanf(char *str,);


Estas funes tm o mesmo funcionamento de printf/scanf, mas os dados so colocados (ou lidos) em str.

int strcmp(char *str1, char *str2);


Retorna 0 se str1 igual a str2, retornando um valor negativo/positivo se uma string maior/menor que a outra

int isalpha(int c); [isdigit,isalnum,islower,isupper,isprint]


Retorna true se c uma letra / dgito numrico / letra ou dgito / minscula / maiscula / imprimivel.

void *malloc(size_t); free(void *pt);


malloc retorna um apontador para um bloco de memria de determinada dimenso, ou NULL se no h memria suficiente, e a funo free liberta o espao de memria apontado por pt e alocado por malloc

FILE *fopen(char *fich, char *mode); fclose(FILE *f);


fopen abre o ficheiro com nome fich, no modo mode (rt leitura em modo texto, wt escrita em modo texto), e fclose fecha um ficheiro aberto por fopen

fprintf(f,); fscanf(f,); fgets(char *str, int maxstr, FILE *f);


idnticos ao printf/scanf mas direccionados para o ficheiro, e fgets uma verso do gets mas com limite mximo da string indicado em maxstr.

int feof(FILE *f);


feof retorna true se o ficheiro f est no fim, e false c.c.

fseek(f,posicao,SEEK_SET); fwrite/fread(registo,sizeof(estrutura),1,f);
funes de leitura binria (abrir em modo rb e wb). fseek posiciona o ficheiro numa dada posio, fwrite/fread escrevem/lem um bloco do tipo estrutura para o endereo de memria registo.

int rand(); srand(int seed);


rand retorna um nmero pseudo aleatrio e srand inicializar a sequncia pseudo aleatria

time_t time(NULL); clock_t clock();


time retorna um nmero segundos que passaram desde uma determinada data, e clock o nmero de instantes (h CLOCKS_PER_SEC instantes por segundo)

double sin(double x); [cos,log,log10,sqrt] double pow(double x,double y);


Funes matemticas mais usuais, com argumentos e valores retornados a double

170

Parte IV Anexos

17.

ERROS COMUNS

Neste anexo compilam-se os erros mais comuns encontrados em actividades de avaliao, de forma a auxiliar a prpria pessoa possa rever o seu cdigo, bem como o avaliador a corrigir trabalhos, que assim suficiente referir que para o bloco de cdigo X aplica-se o erro Y, no sendo necessrio reproduzir toda a argumentao em volta do erro. Cada erro identificado com um nome, a forma em que ocorre do ponto de vista de quem comete o erro, o problema que o erro implica, e a resoluo a adoptar por quem tenha o cdigo com o erro. No problema indicada a gravidade do erro, um de trs nveis: baixa, moderada, elevada. Este indicador de gravidade foi feito tendo em vista a descontar, num critrio em que estes erros sejam contabilizados, 33% pela existncia de um erro de gravidade elevada, 25% pela existncia de um erro de gravidade moderada, e 10% pela existncia de um erro de gravidade baixa. Apenas os 3 erros mais graves devem ser contabilizados, para que quem tenha cometido apenas erros de gravidade baixa, sofra um desconto mximo de 30%.

Erro
Funes com parmetros no nome

Forma
Para distinguir uma constante importante na funo, basta colocar o valor do parmetro no nome da funo, por exemplo funcao12, para a funo que retorne o resto da diviso por 12. Desta forma evita-se utilizar um argumento. Em vez de utilizar alguns dos argumentos nas funes, declara-se a varivel globalmente em vez de na funo main, de forma a no s ser acessvel a todas as funes, como no ser necessrio pass-la como argumentos nas funes

Problema
Se distingue uma constante importante no nome da funo, ento deve colocar a constante como argumento da funo, ficando a funo a funcionar para qualquer constante. Caso no o faa, corre o risco de ao lado de funcao12, ser necessrio a funcao4, funcao10, etc., ficando com instrues parecidas. No poupa sequer na escrita dado que coloca no nome da funo a constante que utiliza na funo. Gravidade: elevada Ao fazer isto, no s mascara as ms opes na diviso do cdigo por funes, dado que no controla os argumentos da funo, como mais importante, perde principal vantagem das funes: quando implementa/rev a funo, apenas tem de considerar a funo e pode abstrair-se do resto do cdigo. Como h variveis globais, ao implementar a funo tem que se considerar todos os restantes locais que essas variveis so utilizadas (leitura / atribuio), para que o cdigo na funo seja compatvel. Por outro lado, ao no se aperceber quando cria a funo do nmero de argumentos que recebe e nmero de variveis que altera, pode estar a criar funes que reduzem muito pouco a complexidade, sendo a sua utilidade diminuta. Gravidade: elevada Esta situao uma indicao clara que necessita de um vector nome[4]. Se no utilizar o vector no possvel depois fazer alguns ciclos a iterar pelas variveis, e iro aparecer forosamente instrues idnticas repetidas por cada varivel. Gravidade: elevada

Resoluo
Se tem casos destes no seu cdigo, deve fazer uma troca simples: substituir todas as constantes dependentes de 12 pelo argumento, ou uma expresso dependente do argumento.

Variveis Globais

Utilize o seu cdigo com variveis globais para fazer uma reviso geral. Deve remover todas as variveis globais e coloc-las na funo main, deixando apenas as constantes globais que faam sentido estar definidas globalmente, e macros. Depois deve ir refazendo os argumentos das funes, e eventualmente rever as funes que necessita de forma a ter funes com poucos argumentos e com uma funcionalidade clara e simples.

Nomes sem significado

Algumas variveis necessrias tm na verdade o mesmo nome, pelo que se utiliza os nomes nomeA, nomeB, nomeC, nomeD, ou nome1, nome2, nome3, nome4 Atendendo a que um bom nome gasta tempo e irrelevante para o bom funcionamento do cdigo, qualquer nome serve.

Variveis com nomes quase iguais

Deve nesta situao procurar alternativas de fazer cdigo em que esta situao lhe tenha ocorrido. No utilize cdigo de outra pessoa, apenas o seu cdigo com este tipo de erro lhe poder ser til.

O cdigo deve ser lido no s pelo compilador como por outras pessoas, pelo que a m utilizao de nomes revela por um lado a m organizao mental de quem o escreve, e por outro um desrespeito por quem l o cdigo. A troca de todos identificadores por nomes abstractos e sem sentido, pode ser uma forma para tornar o cdigo ilegvel, no caso de se pretender proteg-lo. Pode tambm ser uma situao confundida com uma situao de fraude, resultante de uma tentativa de um estudante alterar os identificadores de um cdigo obtido ilicitamente. Gravidade: elevada

Verificar todos os identificadores de uma s letra, ou muito poucas letras, bem como nomes abstractos, funes sem uma grandeza associada, e procedimentos sem verbo. Se no consegue encontrar nomes claros para um identificador, seja uma varivel, uma funo, o nome de uma estrutura, etc., pense numa palavra em que explique para que a varivel serve, o que a funo/procedimento faz, o que contm a estrutura, etc. Se no consegue explicar isso, ento o identificador no existe.

17. Erros comuns Erro Forma


Numa dada instruo, sabe-se a condio pretendida para voltar para cima ou saltar para baixo. suficiente colocar um label no local desejado, e colocar o goto dentro do condicional. Este tipo de instruo pode ser motivado pelo uso de fluxogramas.

171 Resoluo
Utilizar as estruturas de ciclos disponveis, bem como condicionais, e funes. Se o salto para trs dentro da mesma funo certamente um ciclo o que pretendido, mas se para a frente, provavelmente um condicional. Se um salto para uma zona muito distante, ento provavelmente uma funo que falta. No caso de utilizar fluxogramas, deve considerar primeiro utilizar o seu tempo de arranque vendo execues passo-a-passo de forma a compreender o que realmente um programa, sem segredos, e resolver exerccios.

Problema
Perde-se nada mais nada menos que a estrutura das instrues. A utilizao ou no desta instruo, que define se a linguagem estruturada, ou no estruturada (por exemplo o Assembly). Se de uma linha de cdigo se poder saltar para qualquer outra linha de cdigo, ao analisar/escrever cada linha de cdigo, tem que se considerar no apenas as linhas que a antecedem dentro do bloco actual, como todas as linhas de cdigo, dado que de qualquer parte do programa pode haver um salto para esse local. Esta situao ainda mais grave que a utilizao de variveis globais, dado que para compreender 1 linha de cdigo, no apenas os dados que necessitam de ser considerados, como tambm todas as instrues no programa. A complexidade do programa nesta situao cresce de forma quadrtica com o nmero de linhas. Mesmo sem goto, no caso de variveis globais, a complexidade do programa crescer de forma linear, enquanto sem variveis globais a complexidade igual maior funo, pelo que cresce menos que o nmero de linhas de cdigo. Gravidade: elevada Ao ter uma funo grande, vai ter problemas de complexidade. No s as declaraes de variveis da funo ficam longe do cdigo onde so utilizadas, como no consegue visualizar a funo de uma s vez, sendo muito complicado verificar o cdigo. Esta uma situao clara de m utilizao da principal ferramenta da programao, que lhe permite controlar a complexidade do cdigo: abstraco funcional. Gravidade: moderada

Utilizao do goto

Funes muito grandes

Tudo o que necessrio fazer, vai sendo acrescentado na funo, e esta fica com cada vez uma maior dimenso. Esta situao ocorre devido ao problema ser muito complexo.

Funes com muitos argumentos

Aps criar a funo, ao adicionar como argumentos as variveis que esta utiliza (leitura / atribuio), este valor revela-se elevado, mas o que necessrio.

Se o nmero de argumentos muito elevado, a abstraco que a funo representa fraca, uma vez que para utilizar a funo, necessrio ter muita informao. Para alm disso, se a funo for muito curta, pode dar mais trabalho a arranjar os parmetros certos para chamar a funo, que colocar o seu cdigo, sendo assim a funo mais prejudicial que til. No caso dos diversos argumentos estarem relacionados, significa que no foram agregados numa estrutura. Gravidade: moderada A leitura fica prejudicada se a indentao no for constante. Por um lado no fcil identificar se h ou no algum erro do programador em abrir/fechar chavetas, dado que a indentao pode ser sua opo e no esquecimento de abrir/fechar chavetas. Por outro lado, no possvel a visualizao rpida das instrues num determinado nvel, dado que o nvel varivel, sendo necessrio para compreender algo de o cdigo analis-lo por completo. Com o cdigo indentado, pode-se ver o correcto funcionamento de um ciclo externo, por exemplo, sem ligar ao cdigo interno do ciclo, e assim sucessivamente. Gravidade: moderada

Deve dividir a funo em partes que faam sentido, de forma a ter funes pequenas e com poucos argumentos. Dividir a funo s fatias m ideia, dado que os argumentos necessrios para cada fatia podem ser muitos. Se houver vrias fases, sim, colocar cada fase numa funo, caso contrrio deve verificar se os ciclos interiores podem construir funes que no s utilizem poucos argumentos como tambm tenham possibilidade de ser utilizadas em outras situaes. Se a funo reunir estas condies, ter certamente um nome claro. Deve procurar alternativas na criao de funes. Dividir uma funo existente a meio, provoca situaes destas. Para desagregar uma funo, deve procurar o cdigo repetido, se no existir, o cdigo no interior dos ciclos tipicamente um bom candidato a funo, de forma a manter em cada funo um ou dois ciclos. No caso de as variveis estarem relacionadas, criar uma estrutura para as agregar.

Indentao varivel

Como a indentao ao gosto do programador, cada qual coloca a indentao como lhe d mais jeito ao escrever.

Indente o cdigo com 2 a 8 espaos, mas com o mesmo valor ao longo de todo o cdigo. Se utilizar tabs, deve indicar o nmero de espaos equivalente, no cabealho do cdigo fonte, caso contrrio considera-se que o cdigo est mal indentado. Se pretender subir a indentao com a abertura de uma chaveta, pode faz-lo mas tem de utilizar sempre chavetas, caso contrrio ficam instrues que esto no mesmo nvel mas no cdigo ficam e indentaoes distintas. Qualquer que seja as opes, no pode existir instrues no mesmo alinhamento que pertenam a nveis distintos (ou vice-versa). A posio das chavetas indiferente, mas tem que se apresentar um estilo coerente ao longo do cdigo.

172 Erro
Instrues parecidas seguidas

Parte IV Anexos Forma


Quando necessrio uma instruo que parecida com a anterior, basta seleccion-la e fazer uso de uma das principais vantagens dos documentos digitais: copy/paste.

Problema
O cdigo ficar muito pesado de ler, podendo tambm prejudicar a escrita. sinal que est a falhar um ciclo, em que as pequenas mudanas entre instrues, em vez de serem editadas e alteradas, essa alterao colocada dependente da varivel iteradora, fazendo numa s instruo a chamada a todas as instrues parecidas, debaixo de um ciclo. O cdigo no s fica mais simples de ler, como se for necessria outra instruo basta alterar a expresso lgica de paragem do ciclo, em vez de um copy/paste e edio das alteraes. Gravidade: moderada O copy/paste vai dificultar a leitura, e tambm a escrita tem de ser com muito cuidado, para poder editar as diferenas. Se esta situao ocorrer, uma indicao clara que necessria uma funo com as instrues que so parecidas e devem ser reutilizadas. Nos argumentos da funo deve ir informao suficiente para que a funo possa implementar as diferenas. Gravidade: moderada Quer seja a mudar o cdigo de pgina, quer seja a limpar o texto da consola, ou a copiar um ficheiro de um lado para o outro, qualquer comando que escreva, dependente do sistema operativo. Perde portabilidade e o seu programa em vez de fazer uma funo concreta, no mais que um script que chama outros programas, e para isso todos os sistemas operativos tm uma linguagem de scripting mais adequada. Se quiser utilizar cdigo de outros programas, deve integr-los. Gravidade: moderada Muitas vezes ao utilizar no switch um ndice (ou cadeia if-else), as instrues em cada caso ficam muito parecidas, ficando com cdigo longo e de custo de manuteno elevado. Se essa situao ocorrer deve rever o seu cdigo, dado que est provavelmente a falhar uma alternativa mais potente, que a utilizao de vectores. Gravidade: moderada Esta situao um claro excesso de comentrios, que prejudica a legibilidade. Quem l cdigo C sabe escrever cdigo C, pelo que qualquer comentrio do tipo atribuir o valor X varivel Y, desrespeitoso para quem l, fazendo apenas perder tempo. Ao comentar instrues da linguagem e deixar de fora comentrios mais relevantes, pode indicar que o cdigo foi reformulado por quem na verdade no sabe o que este realmente faz. Gravidade: moderada Se a chamada recursiva est no final, e o cdigo tem de passar sempre por esse condicional, ento pode remover a chamada substituindo por um ciclo. Ao chamar a funo recursivamente, as variveis locais j criadas no vo mais ser necessrias, mas por cada passo do ciclo ficar um conjunto de variveis locais espera de serem destrudas. Se o ciclo for muito grande, o stack ir certamente rebentar. Gravidade: moderada

Resoluo
Se encontra situaes destas no seu cdigo, estude alternativas para utilizar ciclos.

Comentrios M utilizao do Instrues parecidas explicam a linguagem switch ou cadeia Utilizao de system no seguidas C if-else

Quando necessrio uma ou mais instrues que so parecidas com outras que j escritas noutra parte do cdigo, basta seleccionar e fazer uso de uma das principais vantagens dos documentos digitais: copy/paste. Para implementar algumas funes, que existem no sistema operativo, pode-se utilizar a funo system, pelo que h que aproveitar.

Se aconteceu no seu cdigo, deve estudar quais as diferentes alternativas para utilizao de funes que tem, de forma a garantir que no lhe escapam hiptese antes de optar.

Remova todas as chamadas a system, e faa o que pretendido fazer no programa com o seu prprio cdigo.

De forma a executar instrues dependente do valor de um ndice, utilizar um switch colocando as diversas instrues para cada caso do ndice. De forma a explicar tudo o que se passa no programa, e no ser penalizado, por vezes desce-se to baixo que se explica inclusive linguagem C.

Criar um vector com as constantes necessrias para chamar numa s instruo todas as outras dependentes do ndice.

Escreva como se estivesse a escrever a si prprio, supondo que no se lembrava de nada daqui a uns 6 meses.

Uma funo tem um conjunto de instrues que tm de ser executadas vrias vezes. Uma forma de fazer isto no final da funo colocar um teste a controlar o nmero de execues, e chamar a funo recursivamente.

M utilizao da Recurso

Substituir a chamada recursiva por um ciclo e respectivo teste de paragem. Caso a chamada recursiva no seja no final da funo, ou exista mais que uma chamada recursiva, ento sim, prefervel utilizar-se recurso.

17. Erros comuns Erro


Atribuies entre variveis de tipos distintos

173 Resoluo
Se for necessria atribuio entre apontadores de tipos distintos, ao alocar memria por exemplo, ento deve utilizar no cdigo um cast para o tipo de destino, de forma a clarificar que no se trata de um erro. Fora a situao de alocao de memria, no precisa de qualquer outra atribuio entre tipos distintos. Em situaes menos ortodoxas, pode utilizar o tipo union, para fundir dados de mais de um tipo. Verificar se h variveis declaradas que no so utilizadas, e apag-las. Para cada atribuio, seguir o fluxo do cdigo at uma possvel utilizao. Se no existir, apagar a atribuio, reinicializando o processo de verificao. No final o cdigo real poder ser muito mais reduzido, do que o gerado em situaes de stress em modo de tentativa/erro.

Forma
Principalmente quando se utiliza apontadores, por vezes necessrio fazer atribuies entre apontadores de tipos distintos, ou entre apontadores e inteiros. Isso no problema porque um apontador um nmero de 32 bits. Por vezes declaram-se variveis, ou fazem-se atribuies a variveis, um pouco para ver se o programa passa a funcionar. No entanto, aps muito corte e costura, por vezes ficam atribuies a variveis que na verdade nunca so utilizadas, embora tal no afecte o bom funcionamento do programa. Por vezes em situaes de stress tentam-se vrias alternativas, e fica algum cdigo que na verdade nunca tem hiptese de ser executado, mas que tambm no atrapalha.

Problema
Se atribui um apontador de um tipo a outro, este vai aceder ao endereo de memria apontado como se fosse o segundo tipo, quando este foi alocado e tem dados como se fosse o primeiro tipo. Pode ter consequncias imprevisveis, uma vez que os dados ficam com o acesso incoerente. Em caso algum deve assumir o tipo do apontador, uma vez que tal pode variar conforme o compilador/computador em que o cdigo for utilizado. Gravidade: moderada Se h uma varivel declarada que no utilizada, essa varivel pode ser removida e o cdigo fica igual. Se h uma atribuio a uma varivel que depois no utilizada em nenhuma expresso, ento a atribuio desnecessria. Ter variveis, expresses, atribuies a mais, uma situao indesejvel, dado que compromete a leitura do cdigo que realmente funciona. Gravidade: moderada

Declaraes ou atribuies a variveis nunca utilizadas

Linhas de cdigo nunca executadas

Se h cdigo a mais, este deve ser removido sobre pena de perda de legibilidade e manuteno. Esta situao ampliada quando h cdigo de diversas provenincias, em que ao integr-lo ningum se atreve a reeditar cdigo que funciona, para seleccionar a parte do cdigo que deve ser integrado. Gravidade: moderada

A alocao de memria uma operao comum, e se o programa no utiliza grandes quantidades de memria, sempre bem sucedida, pelo que no vale a pena efectuar a verificao j que complica desnecessariamente o cdigo. Para uma situao necessria uma funo concreta, pelo que essa funo que definida, utilizando as constantes necessrias dentro dessa funo. Ao criar uma varivel por cada necessidade de registo de valores, pode acontecer haver variveis sempre com o mesmo valor, mas tal no impede que o programa funcione correctamente.

Alocar memria e no testar se a operao foi bem sucedida

Mesmo que a aplicao utilize pouca memria, a alocao de memria pode falhar se o sistema operativo tiver a memria esgotada por outras aplicaes, ou por outras instncias da mesma aplicao. A no verificao pode provocar acesso a zonas de memria proibidas com resultados imprevisveis. Gravidade: moderada

Identificar as zonas de cdigo que de certeza que no so executadas de modo algum, e apag-las. Tanto pode ser feito por inspeco ao cdigo como atravs de testes, submetendo o cdigo a diversos casos de teste, desde que previamente se tenha colocado informao de debug em cada bloco de cdigo. Os blocos de cdigo em que no tiver sido produzida nenhuma informao de debug, so provavelmente os blocos de cdigo que nunca so executados. Em todas as alocaes de memria, verifique se estas foram bem sucedidas, e no caso de falharem, certifique-se que o programa tem um procedimento vlido.

Se as funes so muito especficas, no podem ser reutilizadas, tanto no mesmo programa, como em outros programas. Quanto mais genrica a funo, maior o seu grau de reutilizao, sendo mais simples de manter e detectar algum problema. Gravidade: baixa Ter muitas variveis com a mesma funo tem o mesmo problema que uma atribuio, uma leitura. Fica o cdigo mais complexo e difcil de ler sem qualquer ganho. Pode acontecer que este problema se reflicta no apenas por ter duas variveis sempre com os mesmos valores, mas terem valores com uma relao simples (o simtrico, por exemplo). Gravidade: baixa

Reveja todas as constantes que tem numa funo especfica, e passe as constantes que sejam passveis de serem mudadas para argumentos da funo, de forma a aumentar as possibilidades da funo ser til sem alteraes em outras situaes. Deve identificar as variveis com atribuies e utilizaes perto uma da outra, eventualmente os nomes, e verificar se pode a cada momento utilizar o valor de uma varivel em vez da outra. Por vezes no simples a identificao/remoo destas situaes quando h muitas variveis. prefervel aplicar primeiramente a remoo de declaraes/atribuies desnecessrias, e aplicar a resoluo do erro uma atribuio, uma leitura, antes deste erro.

Variveis desnecessrias

Funes especficas

174 Erro
Declarao de variveis fora do incio de funes

Parte IV Anexos Forma


As variveis devem ser declaradas o mais perto possvel do local onde so utilizadas, e como alguns compiladores de C permitem a declarao fora do incio das funes, h que aproveitar.

Problema
verdadeira a frase, mas no para quem se inicie na programao, nem para a linguagem C. A generalidade dos compiladores de C permite declarao das variveis em qualquer parte do cdigo, mas nem todos, pelo que perde compatibilidade. Mais relevante a m influncia que tal situao ter, facilitando a existncia de funes grandes. Com as variveis declaradas em cada bloco, no ir sofrer os efeitos de funes grandes to intensamente, o que o pode levar a no sentir a necessidade de criar funes. Gravidade: baixa Se h instrues relacionadas, estas devem ser colocadas na mesma funo, se tal se justificar. Colocar duas ou mais instrues na mesma linha vai prejudicar a legibilidade e no s. O compilador quando encontra algum problema refere a linha do cdigo, e existindo mais que uma instruo nessa linha, a informao menos precisa. Deixa de ser possvel tambm seguir um nvel de indentao para passar por todas as instrues nesse nvel, tem que se fazer uma leitura atenta por todo o cdigo do bloco. Gravidade: baixa O real teste de paragem fica diludo nas instrues que alteram esta varivel, bem como condicionais que utilizem o break, e ter algo em vez de num local em vrios, torna sempre mais complicada a leitura e manuteno de cdigo. Esta situao ocorre porque falhou na identificao do real teste de paragem. pior se necessitar de acompanhar esta varivel com uma outra varivel que altera de valor em cada ciclo (uma varivel iteradora). Gravidade: baixa A leitura severamente penalizada se no existir uma norma clara seguida para os identificadores no cdigo, na separao de palavras e utilizao de abreviaturas. Um identificador com um mau nome pode ser trocado o seu significado sem que o leitor se aperceba, a no ser que tenha muita ateno, resultando em horas perdidas procura de um bug que facilmente encontraria com nomes normalizados. As abreviaturas a utilizar, devem incidir apenas para relacionar funes, por exemplo, e nunca os nomes devem ser apenas abreviaturas ou abstractos. Gravidade: baixa A utilizao de macros tem um custo, que incorre nas linhas de cdigo onde utilizada: no conhecido o valor real da macro. Se esta for utilizada em situaes em que o fluxo de informao seja claro, como um valor inteiro limite sendo os ciclos desde uma constante at esse limite, no h qualquer problema. Se no entanto for utilizada em locais dependentes de outras constantes, sejam em macros sejam no cdigo, ou que seja importante saber o valor da macro para saber qual a sequncia de instrues, ou num caso extremo, o valor da macro necessrio para saber se uma instruo vlida, evidentemente que o valor da macro em si no pode ser alterado sem que o cdigo seja revisto. Perde portanto o sentido, uma vez que prejudica a leitura do cdigo e no facilita qualquer posterior alterao. As macros devem ser independentes entre si e de outras constantes. Gravidade: baixa

Resoluo
Todas as declaraes fora do incio das funes passam para o incio das funes. Reveja os nomes das variveis se necessrio.

Duas instrues na mesma linha

Quando as instrues so pequenas e esto relacionadas, mais simples coloc-las todas na mesma linha. Como uma questo de estilo, no afecta o funcionamento.

Em todas as linhas com mais que uma instruo, colocar uma instruo por linha.

Nomes no normalizados

Esta estratgia consiste em utilizar nos ciclos (normalmente while), uma varivel no teste de paragem, atribuindo o valor verdadeiro e dentro do ciclo, quando for necessrio sair dele, fazer uma atribuio varivel a falso. Os nomes para as variveis, funes, estruturas no afectam o bom funcionamento do cdigo, pelo que no vale a pena perder muito tempo. Ora ficam de uma maneira, ora ficam de outra.

Ciclos contendo apenas uma varivel lgica no teste de paragem

Se optar com frequncia para esta situao, deve rever o seu cdigo procurando alternativas.

Refazer os identificadores para utilizar uma s norma de separao de nomes/abreviaturas. Se houver cdigo de diversas provenincias, aproveitar para rever e reformular de forma a permitir a sua manuteno.

Macros em todas as constantes

Tudo o que constante tem direito a uma macro, de forma a poder ser facilmente alterada

Remova do cdigo as macros que so dependentes entre si, ou que dificilmente podem mudar sem implicar outras alteraes no cdigo.

17. Erros comuns Erro


Comentrios inexistentes Nomes muito em partes complexas longos

175 Resoluo
Deve procurar o essencial da varivel / funo / estrutura, de forma a dar o nome sobre o essencial, e comentar se necessrio na declarao as particularidades que no so cobertas pelo nome do identificador. Utilize no mximo 3 palavras, separadas por maisculas ou por _. Nas zonas menos claras, comentar explicando todos os truques / habilidades aplicadas. Se ficar algum por explicar, ser confundido certamente com cdigo que nem quem o fez sabe realmente porque funciona, e mais vale refazer novamente se houver algum problema.

Forma
Para explicar bem o identificador, este tem que ter trs e quatro palavras, ou mesmo mais. O cdigo deve ser lido por quem perceba no s da linguagem de programao C, como de algoritmos, e que seja suficientemente inteligente para o compreender.

Problema
Com os identificadores muito longos, o cdigo tornase menos legvel, e provavelmente haver mais instrues a ocuparem mais de uma linha de cdigo. Gravidade: baixa

Abrir um ficheiro e Abrir um No libertar Uma atribuio, uma no testar se foi bem ficheiro e no memria aps alocar leitura aberto chegar a fechar

Uma varivel atribuda com um valor, para depois utiliz-lo na instruo seguinte, no sendo mais necessrio o valor da varivel. Por exemplo, para reunir os valores necessrios chamada de uma funo. Atendendo a que no h erros por no libertar memria, e sendo a aplicao de curta durao, justificase a no libertao da memria, j que quando a aplicao terminar tudo libertado. Por vezes abre-se o ficheiro, mas a operao de fechar no causa erros e desnecessria dado que o ficheiro ser fechado quando o programa acabar. Quando se sabe que o ficheiro existe, no necessrio testar se a operao de abertura do ficheiro foi bem sucedida, perda de tempo.

Quem l cdigo C deve naturalmente possuir todas essas caractersticas, mas tal no motivo para no explicar as partes complexas. Os comentrios podem afectar a legibilidade do cdigo, e quem est a ler cdigo em que tem dificuldade em compreender, no ir apreciar a forma rebuscada e hbil de quem o escreveu, mas sim a simplicidade e clareza com que o fez, ou o explica. Se no conseguir compreender algo, ser certamente por culpa de quem escreveu que no sabia o que fazia e l acabou por obter uma soluo funcional em modo de tentativa e erro, de baixa qualidade. Gravidade: baixa Esta situao pode indicar a existncia de uma varivel desnecessria, e quantas mais variveis desnecessrias o cdigo tiver, mais complexo fica, para no falar que ocupam memria desnecessariamente. apenas justificvel no caso de a expresso ser muito complexa, de forma a clarificar o cdigo. Gravidade: baixa

Ao fazer a atribuio do valor varivel, utiliza uma expresso. Essa varivel depois utilizada de seguida tambm uma s vez, pelo que mais vale colocar a prpria expresso no local onde a varivel utilizada.

A aplicao tem os endereos dos blocos de memria que alocou, j no precisa deles, pode libert-los. Se no o fizer, no h garantia da altura em que esses blocos sero libertos, nem se o custo computacional de os libertar o mesmo. O argumento da pequena aplicao no deve ser justificao para qualquer erro, o cdigo que funciona em pequenas aplicaes poder ser reutilizado em outras aplicaes, e se for bem feito, no necessitar de alteraes. Gravidade: baixa O nmero de ficheiros abertos um recurso limitado pelo sistema operativo. Se um ficheiro no mais preciso, deve ser imediatamente fechado de forma a libertar recursos. Se esta prtica no for feita, pode acontecer que pedidos de abertura de ficheiros por esta ou outra aplicao sejam recusados. Gravidade: baixa Pode acontecer mesmo quando h a certeza que o ficheiro existe, que a operao de abertura de ficheiro seja mal sucedida, no caso de estarem demasiados ficheiros abertos, ou por questes de permisses, ou ainda por estarem duas aplicaes a tentar abrir o ficheiro em modo de escrita. As operaes seguintes, que utilizam o apontador retornado para o ficheiro, ficam com um procedimento incerto. Gravidade: baixa

Tenha sempre ateno ao alocar memria do local exacto onde esse bloco ser certamente libertado.

Verificar o ltimo local onde o ficheiro necessrio, e chamar a correspondente funo fclose de forma a fechar o ficheiro.

Testar sempre se o ficheiro foi bem aberto, e apenas utilizar o apontador para o ficheiro no caso de este ser diferente de NULL.

176

Parte IV Anexos

18.

EXERCCIOS: DICAS, RESPOSTAS E RESOLUES

Neste anexo so dadas dicas aos exerccios, forma para se validar a resoluo, e resolues.

DICAS EXTRA
Exerccio / problema 1) soma.c no compreendo a frmula Explicao / sugesto / dica
Na frmula
N

i o
i =1

smbolo

significa que se deve somar a expresso

seguinte, tendo em conta que existe uma varivel que vai variar entre dois valores (valor indicado em baixo, at ao indicado em cima). A varivel utilizada neste caso a varivel i, desde o valor 1 at ao valor N. Se N=4, a frmula acima equivalente a

i = 1 + 2 + 3 + 4
i =1 N

1) produto.c no compreendo a frmula

Na frmula

N!= i o smbolo
i =1

o equivalente ao
4

mas para

produtos. Neste caso, se N=4 a expresso acima fica

4!= i = 1 2 3 4
i =1

1) arranjos.c no compreendo a frmula

frmula

A( N , R ) =

N! = i (N R)! i=+1 N R

utiliza

apenas

elementos

introduzidos nos exerccios anteriores. Neste caso, para N=4 e R=2 a expresso fica:

A(4,2) =

4 4 4! 4! = = i = i = 3 4 (4 2)! 2! i=42+1 i=3

1) arranjos.c d-me 5079110400, que o valor correcto mas no est nas respostas possveis 1) arranjos.c D-me valores estranhos, calculo N! e (N-R)!, e depois divido um valor pelo o outro

O valor correcto de combinaes 20, 8 a 8 realmente esse. No entanto essa no a resposta correcta ao exerccio, que deve ser resolvido com inteiros de 4 bytes. Troque todas as variveis inteiras para inteiros de 4 bytes e execute novamente o programa. Calculando ambos os factoriais est a fazer no s mais contas, como tambm ultrapassa o limite dos inteiros mais cedo, atendendo que o factorial cresce muito rapidamente. Se tiver um valor de R pequeno, pode conseguir calcular arranjos mesmo com um N grande, em que no consegue obter o factorial de N, mas consegue obter arranjos de N, R a R. Utilizar um tipo mais potente, resolve o problema do limite para valores pequenos, mas teria o problema mesma para muitos valores que poderiam ser calculados pela segunda frmula. Ateno que todos os tipos inteiros devem ter 4 bytes, atendendo a que os exerccios esto equilibrados para esse tipo de dados.

18. Exerccios: Dicas, Respostas e Resolues 1) fibonacci.c no compreendo a frmula

177

n ,n 2 F (n ) = F (n 1) + F (n 2) , n > 2
Esta frmula tem uma definio recursiva. Para o valor 1 e 2, retorna 1 e 2 respectivamente (primeiro condicional). Para 3 ou superior, retorna a soma dos dois valores imediatamente anteriores. Exemplos: F 3 = F 2 + F 1 = 2 +1 = 3

( ) ( ) () F (4) = F (3) + F (2) = 3 + 2 = 5 F (5) = F (4) + F (3) = 5 + 3 = 8 F (6) = F (5) + F (4) = 8 + 5 = 13


C (N , R ) =

1) combinacoes.c no compreendo a frmula

N N! = i (N R )!R! i= N R+1

i
i =1

Veja primeiramente as explicaes para 1) arranjos.c. Para N=5, R=3, a expresso acima fica:

3 4 5 1 2 3
1) combinacoes.c obtenho o valor incorrecto 1) euler.c no compreendo a frmula
Veja neste pequeno exemplo, como fica a nota sobre a multiplicao e diviso em simultneo a cada passo. Verifique se no estar a utilizar a frmula incorrecta, por exemplo:
N R

i
i=R

i
i =1

pretendido o clculo do valor da constante de euler atravs da frmula:

e=

1 n=0 n!

Por exemplo, se K=9, a expresso pretendida :

1 1 1 1 + + + ... + 0! 1! 2! 9!
Utilizando apenas as 4 operaes bsicas fica:

1 1 1 1 + + + ... + 1 1 1 2 1 2 3 ... 9
1) trocos.c no percebo a pergunta
Tem de contar o nmero de moedas retornadas. Se fosse pedido o montante do exemplo, isto , 1.79, o valor a colocar na resposta seria 6 (1 moeda de 1, 50, 20 e 5 cntimos e 2 moedas de 2 cntimos).

178 1) trocos.c com o montante 0.02 no d o valor correcto

Parte IV Anexos
Pode estar a utilizar em nmeros reais igualdades. No deve faz-lo, dado que a representao dos reais tem preciso limitada. Veja-se o programa apresentado no frum19:
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> int main() { float montante; printf("Indique um montante igual a 0.02: "); scanf("%f", & montante); if(montante == 0.02) printf("montante = 0.02\n"); else printf("montante diferente de 0.02\n"); printf("montante-0.02= %.16g\n", montante-0.02); } C:\>erro Indique um montante igual a 0.02: 0.02 montante diferente de 0.02 montante-0.02= -4.470348362317633e-010 montante == 0.02 no deve ser feito, quanto

Execuo do programa:

O teste desigualdades:

muito deve-se efectuar duas

montante>=0.01999999 && montante<=0.02000001

1) primo.c no percebo a pergunta 1) pi.c obtenho valores muito diferentes de 1) pi.c obtenho valores muito perto de 2) rand.c por vezes tenho valores negativos

No caso do exerccio dos trocos, para ter em ateno o arredondamento, tem de se adicionar 0.5 valores da unidade que se pretende arredondar. Assim, se pretende cntimos, antes de se efectuar os testes de desigualdades, deve-se somar 0.005 (uma vez que a unidade o 0.01). Em alternativa pode-se converter o valor real para inteiros, portanto cntimos, mas na converso tem que se ter em ateno com o arredondamento, caso contrrio o problema mantm-se, h o risco de 0.02*100 passar a 1 em inteiro. Deve somar apenas os nmeros divisores mnimos, no somando nada para o caso dos nmeros primos. No deve somar todos os nmeros testados. No caso do exemplo do enunciado, a resposta seria 3, dado que do nmero 99 o divisor mnimo o nmero 3, e o nmero 97 primo. Ateno que um dos nmeros na pergunta, apenas divisvel por um nmero superior a 100. Ateno que o factorial de 0 1. Note ainda que a frmula calcula 1

Utilize o tipo double e a string de formatao preciso 17 em notao cientfica.

%.17g

para obter o valor com

Para gerao de nmeros aleatrios devem-se utilizar inteiros positivos:


unsigned int v;

19

Espao central, UC Programao, 2010/2011

18. Exerccios: Dicas, Respostas e Resolues 2) rand.c no percebo o resultado 33


Primeira parte da frmula:

179

2315331282148053+ 82571 = 296859585237820


No entanto o nmero tem de caber num inteiro de 4 bytes, que tem um total de hipteses de:

2 32 = 4294967296
Portanto, o valor que fica guardado no inteiro de 4 bytes ser:

2968595852 37820 % 4294967296 = 35672892


Falta agora efectuar a segunda parte da frmula, o resto da diviso com m:

35672892 % 428573 =101333


Finalmente, como o utilizador quer nmeros entre 0 e 99, tem de efectuar o resto da diviso por 100:

101333 %100 = 33
O valor de seed ficar com 101333, j que esta ltima operao do resto da diviso por 100 foi o utilizador da funo que fez, mas a implementao da funo rand apenas se preocupa com o resto da diviso por m, e retorna 101333. Verifique se a funo rand gera os mesmos valores aleatrios:
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main() { int i; srand(1); for(i=0;i<10;i++) printf("%d ",rand()); } C:\>testerand 41 18467 6334 26500 19169 15724 11478 29358 26962 24464

2) find.c no obtenho os mesmos valores

Execuo do programa:

Se estes valores no coincidirem, ento utilize a seguinte funo para gerar valores aleatrios:
1 unsigned int randaux() 2 { 3 static long seed=1; 4 return(((seed = seed * 214013L + 2531011L) >> 16) & 0x7fff); 5 }

2) find.c pedido a posio do primeiro 2, o valor absoluto ou o dgito 2? 2) maximo.c no primeiro valor do exemplo d-me 5.50 e no 5.47

Se os nmeros a sarem fossem estes: 13 15 678 32 45 26 2 Dever-se-ia retornar 6, e no a posio do 2 no nmero 32.

2) maximo.c obtenho os valores do exemplo correctos, mas a resposta d-me incorrecta

Ateno nota sobre a gerao dos nmeros, no deve gerar valores entre 0 e 9999, porque isso poderia levar a gerar o zero, e no lhe interessa calcular o logaritmo de zero. O primeiro valor gerado da funo rand() 41, para coloc-lo entre 0,0001 e 1,0000, temos de somar 1 (porque a funo rand() pode devolver 0), ficando 42, dividir por 10000, ficando 0,0042, e depois chamar a funo log (ln na calculadora). Ora ln(0.0042)=-5,47267..., enquanto que ln(0.0041)=-5,4967. No se est a esquecer de fazer o resto da diviso por 10000? Est a gerar um vector com 1000 elementos (e no 10.000 elementos)? Pode ainda estar a fazer rand()%9999+1 em vez de rand()%10000+1. Se fizer o resto da diviso por 9999 no obtm os elementos distintos necessrios, j que so precisos 10000 valores distintos (incio em 0 e fim em 9999).

180 2) mdc.c que pares utilizar, por exemplo, os pares (1,1), e (2,2) devem ser utilizados?

Parte IV Anexos
No devem ser utilizados todos os "pares de nmeros" distintos, nem os pares de nmeros iguais: (1,1) e (2,2), etc. Relativamente ao nmeros distintos significa que no devem ser utilizados os pares (1,2) e (2,1), apenas um. Conjunto de pares = {(1,2);(1,3);(1,4)...(1,100); (2,3);(2,4);(2,5)...(2,100); ... (97,98);(97,99);(97,100); (98,99);(98,100); (99,100)} Os elementos so exactamente os mesmos, mas no output so mostrados apenas os 10 primeiros, embora o vector tenha 1000 elementos. O segundo vector est ordenado, da mostrar elementos diferentes, que so os 10 mais baixos. o montante maior possvel, a iterao deve seguir a expresso indicada, at ao valor mximo de 20 euros. No significa que o ltimo elemento ser exactamente 20, significa que o ltimo elemento no ser superior a 20.

2) sort.c qual a relao entre ambos os vectores mostrados? 2) trocos2.c qual o significado do 20? O ltimo montante ou o nmero de montantes? 2) jogodados.c obtenho os mesmos valores entre chamadas, com preciso a duas casas decimais 2) jogodados.c no percebo a pergunta 2) strreplace.c a string pode aumentar de tamanho? 3) adiciona.c o 116 o segundo elemento da lista?

srand(time(NULL)); deve ser chamado uma s vez, no incio do programa.

O gerador de nmeros aleatrios no to aleatrio assim, e normalmente existe correlao dos primeiros nmeros gerados. Por outro lado a funo time retorna o nmero de segundos passados desde uma determinada data, e entre chamadas pode retornar o mesmo valor, levando a que sejam gerados os mesmos nmeros. Com a funo clock seria melhor mas a mesma coisa porque 1/1000 segundos para um computador ainda assim tempo suficiente para muitas instrues. Pretende-se o nmero de lanamentos, e no os seus valores, (ex: "6 7 8" e no "3.08 3.55 4.21"). Sim, se a string a substituir for maior.

O 116 o segundo valor na lista, mas o penltiplo a ser adicionado.

18. Exerccios: Dicas, Respostas e Resolues

181

VERIFICAO DAS RESPOSTAS


Para validar uma resposta de um exerccio, verifique se a mesma est na seguinte lista:
'#' '%' '&' '(' ')' '/' '@' '[' ']' '{' '}' '0 160 241' '0 160 41' '0 2 3 0' '0 60 241' '1 1 3 6' '1 160 41' '1 2 3' '1 2 3 4' '1 3 4 6' '1 4 3 6' '1 60 241' '10400600' '113670040' '12' '12444' '126' '128 554 902' '13015' '14361' '15 2 15 405 5 2 38 531' '15 2 15 415 5 2 18 533' '15 2 16 435 5 2 20 536' '15 2 17 425 5 2 19 532' '15 2 51 405 5 2 28 530' '15 2 51 405 5 2 8 537' '15231' '155' '16' '16442663' '165580141' '169 464 242' '17034' '172' '176324603' '178' '18' '18 89' '184' '19 18' '19 62 48' '2.718281828459' '2.718281828459046' '2.71828182845955' '2.8281828459' '2.8281828459046' '20' '222404595' '225561900' '23 25 12 25 29 40 17 19' '240 404 731' '240 503 731' '240 734 243' '248 496 745' '249' '249 404 902' '249 503 734' '249 503 734' '249 649 574' '249 649 745' '249 764 731' '25' '25 29 13 29 34 47 18 21' '25003' '258 496 574' '258 649 740' '26 30 1 13 30 34 47 19 21' '26 30 1 13 30 34 48 19 20' '26 30 1 14 30 35 48 19 22' '26 30 2 12 30 35 46 19 20' '26 30 2 13 30 35 49 19 21' '26 30 2 15 31 36 50 19 23' '267' '271' '28' '286' '3 2 3 1' '3 5 4' '3.14159265358979' '3.1415926535897931' '3.1415926535897962' '3.15926535897' '3.15926535897931' '3000' '32' '3278' '339' '34' '37' '390' '393' '4 4 3' '4 6 3' '40' '410001344' '420' '423500044' '423501344' '43' '479001600' '488224456' '5 2 18 118' '5 3 linhas que alocao com' '5 3 linhas que com' '5 3 no de nome' '5 3 no linha comprimento de nome' '5 4 linhas alocao com' '5 4 linhas que alocao' '5 5 ecr linha maximo de ficheiro' '5 7 4' '50' '503 702 194' '507' '519 464 48' '55' '59' '59 462 248' '5995' '6000' '603255503' '612' '621' '6222446' '63' '68 s bk' '69 62 48' '695' '698' '7 78' '7 8 3' '7 8 9' '7.82101' '7.82405' '7.82444' '70' '70 s bj' '70 s bjk' '700 404 243' '700 760 491' '702 764 419' '71 r bjk' '714140004' '714443004' '714443104' '72 r bjk' '725' '73712' '74 r bj' '74993' '75 s bk' '7500' '77 r bk' '784143104' '8 8' '8 9 8' '8273' '83311' '83322' '83333' '84' '896' '9' '9 1 9' '9 8 9' '9 98' '91' '924' '94' '942' '95134' '96200' '98023' '998.12321023' '9998.12321234' '99998.1132102111' '99998.123210234' 'aaaa abc' 'abcd' 'argumentos ABC 123 123 ABC' 'argumentos ABC_123 123_ABC' 'argumentos ABC123 123ABC' 'argumentos ABC-123 123-ABC' 'bbb abcd' 'ccc abc' 'cesar 21090 cifra' 'cifra 012210 cesar' 'cifracesar 21090' 'cifra-cesar21090' 'dcba' 'jkdsfdsfd sfsd daqwrewfd sdafakjhds dfdsds dslixozjgkfsdcl' 'prog 1 2 3' 'prog 123' 'prog a b c' 'prog abc' 'r+b' 'r+t' 'r+w' 'r+wb' 'r+wt' 'rb' 'rstux' 'rt' 'rtxuv' 'rw+b' 'rw+t' 'rwb' 'rwt' 'sduxjuckflj suxjucuxjucdfgklj sdfli sdlfkuxjucj erlijs sdf ikxjuxjuculsj uxjuc xdvflkj wsefrlij wefsuxjucld dfg' 'sfgha' 'ssdfd'

182

Parte IV Anexos

RESOLUES DOS EXERCCIOS


Exerccios resolvidos, para comparao aps resolver. Se no conseguir mesmo resolver um exerccio com as dicas disponveis, pea a algum que o ajude, e que em ltimo caso veja o cdigo por si para lhe dar uma dica extra, mas no veja um exerccio sem o resolver. O objectivo dos exerccios coloc-lo perante a realizao de actividades reais, para os quais o texto e exemplos fornecidos nos captulos o ajudam a enfrentar. No entanto, apenas a realizao de exerccios lhe poder dar a experincia de programao que apenas sua e intransmissvel. Se no consegue resolver um exerccio, deve ver essa situao como uma oportunidade de evoluo e procurar eliminar a raiz do problema, e no desperdi-la vendo a resoluo do exerccio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 /* olamundosizeof.c */ int main() { /* texto com acentos: */ printf("\nOl Mundo!"); /* tamanho em bytes dos diversos tipos */ printf("\nsizeof(char): %d", sizeof(char)); printf("\nsizeof(short): %d", sizeof(short)); printf("\nsizeof(int): %d", sizeof(int)); printf("\nsizeof(long): %d", sizeof(long)); printf("\nsizeof(long long): %d", sizeof(long long)); printf("\nsizeof(float): %d", sizeof(float)); printf("\nsizeof(double): %d", sizeof(double)); printf("\nsizeof(long double): %d", sizeof(long double));

} /* soma.c */ int main() { int n, i, soma;

printf("Calculo da soma dos primeiros N numeros.\nIndique N:"); /* ler um nmero inteiro */ scanf("%d", & n); /* na varivel soma, ser acumulado o resultado */ soma = 0; /* a varivel i vai iterar de 1 a N */ i = 1; while(i <= n) { /* a varivel soma vai acumulando o resultado de 1 at i */ soma = soma + i; /* mostrar o resultado parcial */ printf("\n adicionar %d, parcial %d", i, soma); /* incrementar a varivel i */ i = i + 1; } /* mostrar resultado final */ printf("\nTotal: %d\n", soma); } /* hms.c */ int main() { int horas, minutos, segundos; printf("Calculo do numero de segundos desde o inicio do dia.\nHora: "); scanf("%d", & horas); printf("Minuto: "); scanf("%d", & minutos); printf("Segundos: "); scanf("%d", & segundos); printf("Numero de segundos desde o inicio do dia: %d", segundos + 60 *(minutos + 60 *horas)); } /* produto.c */ int main() { int n, i, produto; printf("Calculo do produto dos primeiros N numeros.\nIndique N:"); scanf("%d", & n); produto = 1; i = 1; while(i <= n) { /* utilizao do operador *= em vez da atribuio e multiplicao */ produto *= i; /* resultado parcial */ printf(" Factorial(%d)=%d\n", i, produto); /* utilizao do operador de incremento ++, em vez da atribuio

18. Exerccios: Dicas, Respostas e Resolues


18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 e soma com a unidade. */ i++; } printf("Resultado: %d", produto); } /* arranjos.c */ int main() { int i, n, r, arranjos; printf("Calculo dos arranjos de N, R a R:\nIndique N:"); scanf("%d", & n); printf("Indique R:"); scanf("%d", & r); /* verificao da validade dos argumentos introduzidos */ if(n < r || r < 1) printf("Erro: N tem de ser maior que R e este maior que 0.\n"); else { /* inicializao da varivel iteradora a expresso indicada no enunciado */ i = n - r + 1; arranjos = 1; while(i <= n) { arranjos *= i; /* parcial */ printf(" i=%d; arranjos=%d\n", i, arranjos); i++; } printf("Resultado: %d\n", arranjos); } } /* somadigitos.c */ int main() { /* varivel soma declarada e inicializada no mesmo comando */ int n, soma = 0; printf("Calculo da soma do quadrado dos digitos de um numero:\nNumero: "); scanf("%d", & n); while(n != 0) { /* extrair o dgito mais baixo com o resto da diviso por 10 e som-lo o seu quadrado */ soma += (n % 10) *(n % 10); /* mostrar parcial */ printf(" n=%d; soma=%d\n", n, soma); /* diviso inteira por 10, de forma a que o segundo dgito mais baixo, passe a ser o mais baixo */ n /= 10;

183

printf("Resultado: %d\n", soma); } /* fibonacci.c */ int main() { /* necessrio duas variveis auxiliares para guardar os dois ltimos valores */ int i, n, fib1, fib2, resultado; printf("Calculo do valor da funcao Fibonacci:\nIndique N:"); scanf("%d", & n); if(n < 1) printf("Erro: n tem de ser maior ou igual a 1.\n"); else { /* caso n<=2, o resultado n */ resultado = n; if(n > 2) { /* os dois primeiros valores de fib 1 e 2 */ fib1 = 1; fib2 = 2; /* comear no nmero 3, que o primeiro cujo resultado a soma dos dois anteriores */ i = 3; while(i <= n) { /* aps calcular o fib de i, actualizar as variveis auxiliares com os dois valores anteriores, para o passo seguinte, o fib de i+1 */ resultado = fib1 + fib2; fib1 = fib2; fib2 = resultado; /* mostrar valor actual */ printf(" Fib(%d)=%d\n", i, resultado); i++;

184
36 37 38 39 40 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

Parte IV Anexos
} } printf("Resultado: %d\n", resultado);

} } /* combinacoes.c */ int main() { int i, n, r, combinacoes; printf("Calculo das combinacoes de N, R a R:\nIndique N:"); scanf("%d", & n); printf("Indique R:"); scanf("%d", & r); if(n < r || r < 1) printf("Erro: N tem de ser maior que R e este maior que 0.\n"); else { /* no efectuar as multiplicaes e depois as divises, c.c. rapidamente se atinge o limite do inteiro, tem de se multiplicar e dividir ao mesmo tempo */ i = 1; combinacoes = 1; while(i <= r) { /* mostrar valor actual */ printf(" %d", combinacoes); /* a iterao em R, o numerador tem de ser modificado para ir de N-R+1 (quando i=1) at N (quando i=R) */ combinacoes *= (n - r + i); /* mostrar valor aps produto */ printf("*%d=%d", n - r + i, combinacoes); combinacoes /= i; /* mostrar valor aps diviso */ printf("/%d=%d\n", i, combinacoes); i++; /* Nota: possvel a diviso inteira, dado que quando i=2 j dois nmeros foram multiplicados, portanto um deles par, quando i=3 j trs nmeros foram multiplicados, portanto um deles multiplo de 3, e assim sucessivamente */ } printf("Resultado: %d\n", combinacoes); }

} /* euler.c */ #define ITERACOES 20

int main() { int i = 0; /* atendendo a que se pretende alguma preciso, utilizar reais de preciso dupla: double ao criar uma varivel para o factorial do passo i, evita-se ter de calcular de i em cada passo, basta multiplicar a varivel factorial por i */ double resultado = 0, factorial = 1; while(i <= ITERACOES) { /* graas varivel factorial, o nmero de operaes a realizar apenas um produto, uma diviso e uma soma por cada iterao */ if(i > 1) factorial *= i; resultado += 1 / factorial; /* mostrar com preciso 16 em notao cientfica: %.16g */ printf(" %d: %.5g\n", i, resultado); i++; } printf("Resultado: %.16g", resultado); } /* trocos.c */ int main() { float montante; int count; printf("Introduza um montante em euros, podendo ter centimos: "); /* receber um nmero real do utilizador, com %f */ scanf("%f",&montante); /* arrendondar para o cntimo mais prximo */ montante+=0.005; /* contar o nmero de vezes que cabe a moeda maior no remanescente */ count=(int)(montante/2.); montante-=2*count; if(count>0) printf("2 euros: %d\n",count); /* efectuar com o montante restante o mesmo, para as restantes moedas */ count=(int)(montante); montante-=count; if(count>0) printf("1 euro: %d\n",count); count=(int)(montante/.5); montante-=0.5*count;

18. Exerccios: Dicas, Respostas e Resolues


23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 1 2 3 if(count>0) printf("50 centimos: %d\n",count); count=(int)(montante/.2); montante-=0.2*count; if(count>0) printf("20 centimos: %d\n",count); count=(int)(montante/.1); montante-=0.1*count; if(count>0) printf("10 centimos: %d\n",count); count=(int)(montante/.05); montante-=0.05*count; if(count>0) printf("5 centimos: %d\n",count); count=(int)(montante/.02); montante-=0.02*count; if(count>0) printf("2 centimos: %d\n",count); count=(int)(montante/.01); montante-=0.01*count; if(count>0) printf("1 centimo: %d\n",count); /* o cdigo acima algo repetido, resultando desnecessariamente em cdigo muito extenso para a funo implementada. No mdulo seguinte este exerccio ser pedido novamente, de forma a que possa ser resolvido sem repeties, fazendo uso das as novas ferramentas leccionadas. */

185

/* primo.c */ int main() { int divisor, n; printf("Funcao que verifica se um numero N e' primo:\nIndique N:"); scanf("%d", & n); if(n < 1) printf("Erro: o numero tem de ser maior que zero.\n"); else { /* a varivel divisor vai iterar at que o seu quadrado seja maior que o nmero (evitando a utilizao da raiz quadrada) */ divisor = 2; while(divisor *divisor < n) { /* o nmero divisivel se o resto da diviso for nula */ if(n % divisor == 0) { printf("\nNumero divisivel por %d\n", divisor); return; } /* mostrar iterao */ printf("%d ", divisor); divisor++; } printf("\nNumero primo!\n"); }

} /* triplasoma.c */ int main() { int i, j, n, count;

printf("Escreva um numero para decompor em somas de tres parcelas.\nNumero:"); scanf("%d", & n); count = 0; /* percorrer todas as possveis permutaes (apenas dois ciclos aninhados), assumindo que os nmeros mais altos aparecem sempre primeiro */ i = n - 2; while((n - i) <= i *2) { /* este condicional garante que h parcelas para alm desta, mais pequenas ou iguais, pelo menos duas iguais a i */ j = (n - i) - 1; if(j > i) j = i; while((n - i - j) <= j) { /* encontrado i+j+(n-i-j), imprimir */ printf(" %d+%d+%d\n", i, j, n - i - j); count++; j--; } i--; } printf("Numero de somas: %d\n", count); } /* pi.c */ #define ITERACOES 3

186
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

Parte IV Anexos
int main() { int i = 0, j; double resultado, factor = 0, parcela; resultado = 2. *sqrt(2.) / 9801.; while(i < ITERACOES) { parcela = 1103 + 26390 *i; /* (4k)! */ j = 4 *i; while(j > 0) { parcela *= j; j--; } /* /(k!)^4 */ j = i; while(j > 0) { parcela /= j *j *j *j; j--; } /* /(396)^(4k) */ j = 4 *i; while(j > 0) { parcela /= 396; j--; } i++; factor += parcela; } resultado *= factor; resultado = 1 / resultado; printf("\nValor de PI (%d iteracoes): %.17g\n", ITERACOES, resultado); } /* formularesolvente.c */ /* listar os coeficientes de funes inteiros, entre -4 e 4, que tm apenas raizes inteiras */ int main() { /* variveis so agora reais, do tipo float */ float a, b, c, delta; /* verso inteira para resposta pergunta */ int ia, ib, ic, idelta, isqrt, k, count; printf("Equacao do segundo grau a*x^2+b*x+c=0.\nIndique a b c: "); /* leitura dos trs argumentos em conjunto */ scanf("%f %f %f", & a, & b, & c); /* calcular primeiramente o interior da raiz quadrada, dependente do sinal deste valor, haver nmero de razes distintas */ delta = b *b - 4 *a *c; /* mostrar parcial */ printf("Delta: %f\n", delta); if(delta < 0) printf("A equacao nao tem raizes reais.\n"); else if(delta == 0) printf("A equacao tem uma raiz unica, x=%f\n", - b / (2 *a)); else /* pode-se colocar logo o resto da expresso nos argumentos do printf */ printf("O valor de x pode ser %f ou %f\n", (- b + sqrt(delta)) / (2 *a),(- b - sqrt(delta)) / (2 *a)); /* resposta pergunta */ printf("\nCalculo de coeficientes entre -K e K inteiros nao nulos, com raizes inteiras."); printf("\nIntroduza K:"); scanf("%d", & k); printf("\nCoeficientes de -%d a %d inteiros nao nulos, com raizes inteiras:\n", k, k); count = 0; for(ia =- k; ia <= k; ia++) for(ib =- k; ib <= k; ib++) for(ic =- k; ic <= k; ic++) { if(ia == 0 || ib == 0 || ic == 0) continue; idelta = ib *ib - 4 *ia *ic; if(idelta == 0 && ia != 0 && ib % (2 *ia) == 0) { count++; printf(" [%d %d %d]", ia, ib, ic); } else if(idelta > 0) { /* verificar se um quadrado perfeito */ isqrt = sqrt(idelta); if(isqrt *isqrt == idelta && ia != 0 &&

18. Exerccios: Dicas, Respostas e Resolues


55 56 57 58 59 60 61 62 63 64 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1 2 3 4 ((- ib + isqrt) % (2 *ia) == 0 && (- ib - isqrt) % (2 *ia) == 0)) { count++; printf(" (%d %d %d)", ia, ib, ic);

187

} /* argumentos.c */ int main(int argc, char **argv) { /* argumentos da funo main: argc - nmero de argumentos; argv - vector de strings com os argumentos */ int i; for(i = 0; i < argc; i++) /* acesso string na posio i do vector argv: argv[i] */ printf("Argumento %d: %s\n", i, argv[i]); } /* inverte.c */ /* definir o tamanho mximo de uma string em macros, para assim ser fcil de alterar este valor sem ter de ver todo o cdigo */ #define MAXSTR 255 /* funes sobre strings, mantendo a norma utilizada para os nomes em string.h o nome da funo fica com trs letras str, seguido de trs letras indicando a operao, neste caso a inverso: strinv */ void strinv(char *str) { int len, i; char aux; len = strlen(str); /* necessrio percorrer apenas metade do tamanho da string */ for(i = 0; i < len / 2; i++) { /* trocar este caracter pelo seu simtrico */ aux = str[i]; str[i] = str[len - 1 - i]; str[len - 1 - i] = aux; /* mostrar a iterao */ printf(" %d: %s\n", i, str); } } int main() { char str[MAXSTR]; printf("Inversao de um texto.\nTexto: "); /* ler a string atravs da funo gets */ gets(str); /* inverter a string */ strinv(str); /* aps a execuo da funo anterior, a string ficou invertida */ printf("Texto invertido: %s", str); } /* rand.c */ /* as constantes utilizadas, podem ser definidas como macros, de forma a aparecerem no incio do programa. */ #define RAND_A 231533 #define RAND_B 82571 #define RAND_M 428573 /* varivel global, com a semente/valor da ltima gerao */ unsigned int seed = 0; /* definio de uma funo rand. J existe uma funo rand nas bibliotecas, que pode ser utilizada nos exerccios seguintes. */ unsigned int rand() { seed = (RAND_A *seed + RAND_B) % RAND_M; return seed; } int main() { int n, i; printf("Gerador de numeros aleatorios inteiros.\nValor maximo: "); scanf("%d", & n); n++; /* inicializar a semente com o tempo */ seed = time(NULL); /*seed=1;*/ printf("seed=%d: ", seed); for(i = 0; i < 10; i++) printf("%d ", rand() % n); printf("\n"); } /* find.c */ /* na declarao do vector v, no se deve colocar o nmero de elementos, para funcionar para qualquer dimenso */ int Find(int v[], int n, int elemento)

} } } printf("\nTotal: %d", count);

188
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 1 2 3 4 5 6 7 8 9 10 11 12 {

Parte IV Anexos

int i; for(i = 0; i < n; i++) if(v[i] == elemento) /* caso se encontre o valor correcto, retornar da funo mesmo com o ciclo a meio */ return i; /* percorreu-se todos os elementos e no se encontrou nenhum igual, retornar -1 */ return - 1; } /* funo que mostra no ecran os primeiros elementos de um vector */ void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } int main() { /* declarao do vector v com 1000 elementos (de 0 a 999) */ int v[1000], i; /* fixar a semente a 1 */ srand(1); /* inicializar o vector, com valores aleatrios de 0 a 999 */ for(i = 0; i < 1000; i++) /* acesso posio i do vector v: v[i] */ v[i] = rand() % 1000; /* mostrar os 10 primeiros elementos */ PrintInts(v, 10, "Vector: "); /* chamada da funo find no prprio argumento do printf */ printf("\nPosicao de 2: %d", Find(v, 1000, 2)); } /* maximo.c */ float Maximo(float v[], int n) { int i; float resultado = 0; for(i = 0; i < n; i++) /* actualizar o resultado se for o primeiro elemento, ou o valor registado for inferior ao elemento actual */ if(i == 0 || v[i] > resultado) { resultado = v[i]; /* mostrar iterao */ printf("\n %d: %.2f", i, resultado); } return resultado; } void PrintFloats(float v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%.2f ", v[i]); } int main() { int i; float v[1000]; srand(1); for(i = 0; i < 1000; i++) v[i] =- log((1 + rand() % 10000) / 10000.); PrintFloats(v, 10, "Vector: "); printf("\nValor maximo: %.6g", Maximo(v, 1000)); } /* mdc.c */ int mdc(int x, int y) { /* caso y=0, retornar x */ if(y == 0) return x; /* c.c. retornar mdc(y,mod(x,y)), exactamente o comando seguinte aps substituir o mod pelo comando do resto da diviso % */ return mdc(y, x % y); } int main()

18. Exerccios: Dicas, Respostas e Resolues


13 14 15 16 17 18 19 20 21 22 23 24 25 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { int x, y, i, j, soma; printf("Calculo do maximo divisor comum entre dois numeros.\nIndique x e y:"); scanf("%d %d", & x, & y); printf("MDC: %d\n", mdc(x, y)); /* calcular o valor da resposta pedido no exerccio */ soma = 0; for(i = 1; i < 100; i++) for(j = i + 1; j <= 100; j++) soma += mdc(i, j); printf("A soma dos MDC dos pares de numeros de 1 a 100: %d\n", soma);

189

} /* sort.c */ void Sort(int v[], int n) { /* duas variveis iteradoras, j que se tem de percorrer todos os pares */ int i, j, aux; for(i = 0; i < n; i++) /* como a ordem indiferente, a segunda varivel comea logo com o valor acima da primeira, desta forma apenas analisado o par i=10, j=35, e nunca o par i=35, j=10, dado que quando i=35, o j comea logo em 36 */ for(j = i + 1; j < n; j++) if(v[i] > v[j]) { /* pares pela ordem incorrecta, trocar recorrendo a uma varivel auxiliar */ aux = v[i]; v[i] = v[j]; v[j] = aux; } } void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } int main() { int v[1000], i; srand(1); for(i = 0; i < 1000; i++) v[i] = rand() % 1000; PrintInts(v, 10, "\nVector antes de ser ordenado: "); Sort(v, 1000); PrintInts(v, 10, "\nVector depois de ser ordenado: ");

printf("\nQuartil 25: %d\nQuartil 50: %d\nQuartil 75: %d\n", v[250], v[500], v[750]); } /* removedups.c */ int RemoveDups(int v[], int n) { int i, j; /* como o vector est ordenado, os duplicados esto seguidos, pelo que necessrio saber se o elemento seguinte igual */ for(i = 0; i < n - 1; i++) /* enquanto o elemento seguinte for igual, apag-lo */ while(i < n - 1 && v[i] == v[i + 1]) { /* para apagar o elemento actual, tem de se copiar todos os elementos seguintes uma posio para trs. */ for(j = i; j < n - 1; j++) v[j] = v[j + 1]; /* no esquecer de actualizar a dimenso do vector */ n--; } return n; } /* Nota: numa verso mais eficiente, mas requerendo matria do bloco seguinte, poderia-se criar um vector novo e ir colocando os elementos distintos nesse vector. Esse mtodo faz uma s passagem pelo vector, independentemente do nmero de elementos duplicados, enquanto que a funo acima percorre o resto do vector por cada elemento duplicado. */ void Sort(int v[], int n) { int i, j, aux; for(i = 0; i < n; i++) for(j = i + 1; j < n; j++)

190
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 if(v[i] > v[j]) { aux = v[i]; v[i] = v[j]; v[j] = aux; }

Parte IV Anexos

void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } int main() { int v[1000], i; srand(1); for(i = 0; i < 1000; i++) v[i] = rand() % 1000; PrintInts(v, 10, "\nVector inicial: "); Sort(v, 1000); printf("\nElementos que permanecem no vector: %d", RemoveDups(v, 1000)); PrintInts(v, 10, "\nVector final: "); } /* somanumeros.c */ #define MAXSTR 255 int main() { char numeros[MAXSTR], *pt; double soma = 0; printf("Introduza um conjunto de numeros reais separados por espacos.\n"); gets(numeros); /* pt fica a apontar para a primeira token (neste caso nmero real)*/ pt = (char *) strtok(numeros, " "); while(pt != NULL) { /* converter a string para o seu valor numrico */ soma += atof(pt); /* mostrar parcial */ printf(" %.2f (%s)\n", soma, pt); /* passar para a prxima token */ pt = (char *) strtok(NULL, " "); } printf("A soma dos numeros e' %.15g.\n", soma); } /* trocos2.c */ int trocos(float montante, int mostrar) { int count, i, total; /* vectores com informao esttica */ float moedas[] = { 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01, 0 }; char *smoedas[] = { "2 euros", "1 euro", "50 centimos", "20 centimos", "10 centimos", "5 centimos", "2 centimos", "1 centimo" }; total = 0; montante += 0.005; /* arrendondar para o cntimo mais prximo */ /* processar a informao esttica, em vez de duplicar o cdigo utilizando constantes para cada possvel valor do vector. */ for(i = 0; moedas[i] > 0; i++) { count = (int)(montante / moedas[i]); montante -= moedas[i] *count; if(count > 0 && mostrar) printf("%s: %d\n", smoedas[i], count); total += count; } return total;

18. Exerccios: Dicas, Respostas e Resolues


35 36 37 38 39 40 41 42 43 44 45 46 47 48 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 } int main() { float montante; int total = 0; printf("Introduza um montante em euros, podendo ter centimos: "); scanf("%f", & montante); trocos(montante, 1); /* resposta pergunta */ for(montante = 0.01; montante < 20; montante = montante *2 + 0.01) total += trocos(montante, 0); printf("\nTotal moedas: %d", total); } /* jogodados.c */ #define ITERACOES 100000 #define MAXLANCAMENTOS 10 /* simulao de um jogo, dado um nmero fixo de lanamentos */ int JogoDados(int lancamentos, int mostraDado) { int dado,i,anterior=-1,pontos=0; /* executar o nmero de lanamentos pedido, assumindo que o dado anterior -1, de forma a no dar derrota no primeiro lanamento, nem ser necessrio fazer um teste especial. */ for(i=0;i<lancamentos;i++) { dado=1+rand()%6; /* mostrar o dado */ if(mostraDado) printf("%d ",dado); pontos+=dado; if(dado==anterior) /* caso de derrota retorna logo, com a pontuao negativa */ return -pontos; /* actualizar a varivel anterior, com o valor correcto para o prximo ciclo */ anterior=dado; } /* foi possvel efectuar todos os lanamentos, retornar os pontos positivos. */ return pontos; } int main() { int n,i,j,pontos; srand(time(NULL)); printf("Jogo do lancamento de dados.\nIndique numero de lancamentos: "); scanf("%d",&n); printf("Pontos: %d\n\nValores medios dos pontos:\n",JogoDados(n,1)); /* simulao de vros jogos */ for(i=1;i<MAXLANCAMENTOS;i++) { /* contabilizar os pontos ao longo de todos os jogos*/ pontos=0; for(j=0;j<ITERACOES;j++) pontos+=JogoDados(i,0); /* fornecer a pontuao mdia */ printf("Com %d lancamento(s): %.2f\n", i,(float)pontos/ITERACOES); } } /* baralhar.c */ void Baralhar(int v[], int n) { int i, j, aux; /* processar todos os elementos */ for(i = 0; i < n - 1; i++) { /* gerar um valor aleatrio para sortear o elemento do vector a ficar na posio i (entre i e n-1). */ j = i + rand() % (n - i); aux = v[i]; v[i] = v[j]; v[j] = aux; } } void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } int main() { int v[1000], i; srand(1); /* desta vez inicializar o vector com a funo identidade */ for(i = 0; i < 1000; i++) v[i] = i;

191

192
32 33 34 35 36 37 38 39 40 41 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 1 2 3 4 5 6 7 8 9 10 11 12 13

Parte IV Anexos

PrintInts(v, 10, "Vector identidade: "); Baralhar(v, 1000); PrintInts(v, 10, "\nVector baralhado: "); printf("\nNa posicao 250, 500, 750: %d %d %d", v[250], v[500], v[750]);

} /* mergersort.c */ #define MAXVECTOR 1000000

/* funo auxiliar para juntar duas partes do vector ordenadas */ void MergeSort2(int v[], int a, int meio, int b) { /* juntar ambas as metades no vector auxiliar */ static int aux[MAXVECTOR]; int i, j, k; /* i primeira parte do vector, j segunda parte, k vector ordenado */ i = a; j = meio; k = a; /* k avana de a a b */ while(k < b) /* vector auxiliar fica com o menor dos valores, a no ser que uma das partes j tenha acabado, e ao mesmo tempo incrementa as respectivas variveis iteradoras */ if(i < meio && (j >= b || v[i] <= v[j])) aux[k++] = v[i++]; else aux[k++] = v[j++]; /* copiar o vector auxiliar para o vector em ordenao */ for(k = a; k < b; k++) v[k] = aux[k]; /* mostrar indicao que j est ordenado o vector de a a b */ if(b - a > 100000) printf(" Ordenado de %d a %d\n", a, b - 1); } /* funo auxiliar para ordenar de a at b */ void MergeSort1(int v[], int a, int b) { if(b - a < 2) /* um ou zero elementos para ordenar, retornar */ return; /* dividir em dois grupos, ordenar separadamente */ MergeSort1(v, a,(a + b) / 2); MergeSort1(v,(a + b) / 2, b); /* juntar os dois grupos ordenados */ MergeSort2(v, a,(a + b) / 2, b); } void MergeSort(int v[], int n) { MergeSort1(v, 0, n); } int main() { int i; /* keyword static fora a que esta varivel seja na realidade global, e mantem os valores entre chamadas da funo. Neste caso a funo main, que no ser chamada segunda vez, mas a keyword static permite que o compilador utilize outra zona de memria que no o stack. */ static int v[MAXVECTOR]; srand(1); for(i = 0; i < MAXVECTOR; i++) v[i] = rand() % 1000; MergeSort(v, MAXVECTOR); printf("Quantis: %d %d %d\n", v[250000], v[500000], v[750000]); } /* binarysearch.c */ /* funo auxiliar, procura num intervalo de a a b (inclusiv) */ int BinarySearch1(int v[], int a, int b, int elemento) { /* mostrar informao */ printf(" Procurar %d entre %d e %d\n", elemento, a, b); /* caso a seja j maior que b, retornar fracasso */ if(a > b) return - 1; /* testar o elemento do meio, e se for igual, retornar esta posio */ if(v[(a + b) / 2] == elemento) return(a + b) / 2;

18. Exerccios: Dicas, Respostas e Resolues


14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /* caso seja menor, chamar recursivamente esta funo para o segmento da esquerda, c.c. chamar para o segmento da direita. */ if(v[(a + b) / 2] < elemento) return BinarySearch1(v,(a + b) / 2 + 1, b, elemento); /* notar que no h necessidade de else nos ifs, dado que se est a retornar logo da funo dentro dos ifs*/ return BinarySearch1(v, a,(a + b) / 2 - 1, elemento); } int BinarySearch(int v[], int n, int elemento) { return BinarySearch1(v, 0, n - 1, elemento); } #define MAXVECTOR 1000000 void MergeSort2(int v[], int a, int meio, int b) { static int aux[MAXVECTOR]; int i, j, k; i = a; j = meio; k = a; while(k < b) if(i < meio && (j >= b || v[i] <= v[j])) aux[k++] = v[i++]; else aux[k++] = v[j++]; for(k = a; k < b; k++) v[k] = aux[k]; } void MergeSort1(int v[], { if(b - a < 2) return; MergeSort1(v, a,(a + MergeSort1(v,(a + b) MergeSort2(v, a,(a + } int a, int b)

193

b) / 2); / 2, b); b) / 2, b);

void MergeSort(int v[], int n) { MergeSort1(v, 0, n); } int main() { int i; static int v[MAXVECTOR]; srand(1); for(i = 0; i < MAXVECTOR; i++) v[i] = rand() % 1000; MergeSort(v, MAXVECTOR); printf("Posicao BinarySearch(v, BinarySearch(v, BinarySearch(v, de 250, 500 e 750: %d %d %d\n", MAXVECTOR, 250), MAXVECTOR, 500), MAXVECTOR, 750));

} /* numeracaoromana.c */ #define MAXROMANA 24 /* Este exerccio tem tambm a questo da repetio de cdigo do exerccio dos trocos */ void ArabeParaRomana(int arabe, char romana[], int mostrar) { static int valor[] = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1, 0 }; static char *texto[] = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I", "" }; int i; strcpy(romana, ""); /* processar todos os valores, do maior para o menor, e ir concatenando as respectivas strings, idntico ao exerccio dos trocos */ for(i = 0; valor[i] > 0; i++) while(arabe >= valor[i]) { strcat(romana, texto[i]); arabe -= valor[i]; /* mostrar esta operao */ if(mostrar) printf(" %d %s\n", arabe, romana); } } int RomanaParaArabe(char romana[], int mostrar) {

194
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

Parte IV Anexos
/* estas variveis estticas esto declarads em duas funes, poderiam ser globais, mas como so de interesse apenas para duas funes optou-se pela sua duplicao localmente. */ static int valor[] = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1, 0 }; static char *texto[] = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I", "" }; int arabe = 0, i; char *pt; pt = romana; while(pt[0] != 0) for(i = 0; valor[i] > 0; i++) /* tentar encontrar na string as letras romanas (1 e 2 caracteres) */ if(texto[i][1] == 0 && pt[0] == texto[i][0]) { /* 1 caracter, match perfeito */ pt++; arabe += valor[i]; /* mostrar esta operao */ if(mostrar) printf(" %d %s\n", arabe, pt); /* o valor rabe actualizado, avanar para o prximo caracter e voltar a procurar por match com as letras romanas*/ break; } else if(texto[i][1] != 0 && pt[0] == texto[i][0] && pt[1] == texto[i][1]) { /* 2 caracteres, match perfeito */ pt += 2; arabe += valor[i]; /* mostrar esta operao */ if(mostrar) printf(" %d %s\n", arabe, pt); /* idntico, mas avana dois caracteres */ break; } return arabe; } int main() { int numero, i, soma; char romana[MAXROMANA]; printf("Conversao de numeracao arabe para romana e vice-versa.\n"); printf("Numero arabe: "); gets(romana); numero = atoi(romana); ArabeParaRomana(numero, romana, 1); printf("Resultado: %s\nNumero romano: ", romana); gets(romana); printf("Resultado: %d\n", RomanaParaArabe(romana, 1)); /* resposta pergunta */ soma = 0; for(i = 1; i < 1000; i++) { ArabeParaRomana(i, romana, 0); soma += strlen(romana); } printf("Caracteres dos numeros de 1 a 1000, em numeracao romana: %d", soma); } /* pokerdados.c */ void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } char *poker5dados(int dados[5], int mostra) { int count[6], i, j, max; /* contar quantos dados existem de cada tipo */ max = 0; for(i = 0; i < 6; i++) count[i] = 0; for(i = 0; i < 5; i++) { count[dados[i] - 1]++; if(max < count[dados[i] - 1]) max = count[dados[i] - 1]; } /* mostra contagem */ if(mostra) PrintInts(count, 6, "\n Frequencia dos valores: ");

18. Exerccios: Dicas, Respostas e Resolues


26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /* verificar primeiro as situaes de maior valor */ if(max == 5) return "poker real"; if(max == 4) return "poker quadruplo"; if(max == 3) { /* se existir um nmero com contagem 2, ento temos um fullen */ for(i = 0; i < 6; i++) if(count[i] == 2) return "fullen"; } if(max == 1) { /* verificar a sequncia */ if(count[0] == 0 || count[5] == 0) return "sequencia"; } if(max == 3) /* agora a nica hiptese o trio */ return "trio"; if(max == 2) { /* se existir dois nmeros com contagem 2, temos dois pares */ for(i = 0, j = 0; i < 6; i++) if(count[i] == 2) j++; if(j == 2) return "duplo par"; else return "par"; } return "nada"; } int main() { int dados[5], i, j, nada, par, duplopar; char reter[80], *resposta; /* simulao de um jogo, lanando 5 dados */ srand(time(NULL)); for(i = 0; i < 5; i++) dados[i] = 1 + rand() % 6; PrintInts(dados, 5, "Valor dos dados lancados: "); printf("(%s)\nReter (10100 reter o primeiro e o dado central): ", poker5dados(dados, 1)); gets(reter); /* lanar os dados no retidos, e apresentar resultado final */ for(i = 0; i < 5 && reter[i]; i++) if(reter[i] != '1') dados[i] = 1 + rand() % 6; PrintInts(dados, 5, "Valor dos dados finais: "); printf("(%s)\n", poker5dados(dados, 1)); /* cdigo para responder pergunta */ srand(1); nada = par = duplopar = 0; for(i = 0; i < 1000; i++) { for(j = 0; j < 5; j++) dados[j] = 1 + rand() % 6; resposta = poker5dados(dados, 0); if(strcmp(resposta, "nada") == 0) nada++; else if(strcmp(resposta, "par") == 0) par++; else if(strcmp(resposta, "duplo par") == 0) duplopar++; } printf("nada: %d\npar: %d\nduplo par: %d\n", nada, par, duplopar); } /* strreplace.c */ #define MAXSTR 1024 int strreplace(char *str, char *find, char *replace) { char *pt; int szfind, szreplace, i, count = 0; szfind = strlen(find); if(szfind == 0) return; szreplace = strlen(replace); pt = str; while((pt = strstr(pt, find)) != NULL) { count++; /* substituir find por replace */

195

196
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

Parte IV Anexos
if(szfind >= szreplace) { /* h espao */ for(i = 0; i < szreplace; i++) pt[i] = replace[i]; if(szfind > szreplace) { /* reposicionar o resto da string */ for(i = szreplace; pt[i + szfind - szreplace]; i++) pt[i] = pt[i + szfind - szreplace]; pt[i] = 0; } } else { /* no h espao, reposicionar o resto da string */ for(i = strlen(pt); i > 0; i--) pt[i - szfind + szreplace] = pt[i]; /* copiar o replace */ for(i = 0; i < szreplace; i++) pt[i] = replace[i]; } /* importante que pt avane o tamanho do replace, para no efectuar substituies na prpria substituio */ pt += szreplace; /* mostrar operao */ printf(" %s\n", str); } } return count;

int main() { char str[MAXSTR], find[MAXSTR], replace[MAXSTR]; int count; printf("Substituir num texto, as ocorrencias de uma string A \ por uma string B.\nTexto: "); gets(str); printf("String A: "); gets(find); printf("String B: "); gets(replace); count = strreplace(str, find, replace); printf("Resultado (%d trocas): %s\n", count, str); } /* ndamas.c */ #define MAXDAMAS 16 int total; void MostraDamas(int linhasDamas[MAXDAMAS], int n) { int i, j; static int mostrar = 1; /* contar as posies e mostrar apenas a primeira vez */ total++; if(mostrar == 0) return; /* como a varivel mostrar esttica, da prxima vez que esta funo for chamada ser 0, e esta parte do cdigo no ser executada */ mostrar = 0; /* barra horizontal superior */ printf("\n+"); for(i = 0; i < n; i++) printf("---"); printf("+"); for(i = 0; i < n; i++) { /* barra vertical esquerda */ printf("\n|"); /* casas vazias esquerda da dama desta linha */ for(j = 0; j < linhasDamas[i]; j++) printf(" . "); /* dama nesta linha */ printf(" # "); /* casas vazias direita da dama desta linha */ for(j = linhasDamas[i] + 1; j < n; j++) printf(" . "); /* barra vertical direita */ printf("|"); } /*barra horizontal inferior */ printf("\n+"); for(i = 0; i < n; i++) printf("---"); printf("+"); } /* com base nas linhas das damas j colocadas, colocar uma dama em coluna */

18. Exerccios: Dicas, Respostas e Resolues


46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void ColocarDama(int linhasDamas[MAXDAMAS], int coluna, int n) { int i, j; /* percorrer todas as linhas para tentar colocar a dama */ for(i = 0; i < n; i++) { for(j = 0; j < coluna; j++) if(linhasDamas[j] == i || /* mesma linha */ linhasDamas[j] + coluna - j == i || /* mesma diagonal */ linhasDamas[j] + j - coluna == i) break; /* se no houve uma paragem porque esta linha i possvel */ if(j == coluna) { linhasDamas[coluna] = i; /* mostrar as damas se esta a ltima coluna, c.c. chamar recursivamente esta funo para a coluna seguinte */ if(coluna + 1 == n) MostraDamas(linhasDamas, n); else ColocarDama(linhasDamas, coluna + 1, n); } /* notar que mesmo aps ter colocado uma dama na linha i, o algoritmo continua a explorar o i+1, e prossegue at n-1, explorando todas as hiptese possveis. */ } } int main() { int n; int linhasDamas[MAXDAMAS]; printf("Conta quantas posicoes existem, num tabuleiro de NxN, "); printf("para colocacao de N damas sem que estas se ataquem "); printf("mutuamente.\nIndique N (maximo %d): ", MAXDAMAS); scanf("%d", & n); total = 0; ColocarDama(linhasDamas, 0, n); printf("\nTotal: %d\n", total); } /* doisdados.c */ #define ITERACOES 10000 #define MAXELEMENTOS 100 #define MAXVALORES 22 /* mostra um grfico com tantas colunas quantos os elementos, e 16 linhas sendo os valores re-escalados. Retorna o nmero de cardinais impressos (para responder pergunta) */ int GraficoDeBarras(int serie[], int n) { char grafico[MAXVALORES][MAXELEMENTOS]; int i, j, maximo, count = 0; float factor; if(n > MAXELEMENTOS) return; /* determinar valor mximo da srie */ for(i = 0; i < n; i++) if(i == 0 || maximo < serie[i]) maximo = serie[i]; /* obter o factor para re-escalar os valores para 16 linhas */ factor = 15. / maximo; /* desenhar os eixos do grfico */ sprintf(grafico[0], ""); sprintf(grafico[1], " +"); /* barra horizontal */ for(i = 0; i < n; i++) strcat(grafico[1], "-"); /* barra vertical */ for(i = 2; i < 18; i++) sprintf(grafico[i], "% 5d|%*s ",(int)((i - 2) / factor + 0.5), n, " "); /* desenhar a srie para o array */ for(i = 0; i < n; i++) for(j = 0; j < 16; j++) if(serie[i] *factor >= j) { grafico[j + 2][i + 6] = '#'; count++; } /* imprimir todo o array (aqui considerado um vector de strings) */ for(i = 17; i >= 0; i--) printf("\n%s", grafico[i]); return count; } void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++)

197

198
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 printf("%d ", v[i]); }

Parte IV Anexos

int main() { int soma[11], i, count; srand(1); printf("Simulacao de %d lancamento de dois dados:\n", ITERACOES); for(i = 0; i < 11; i++) soma[i] = 0; for(i = 0; i < ITERACOES; i++) soma[(1 + rand() % 6) + (1 + rand() % 6) - 2]++; count = GraficoDeBarras(soma, 11); PrintInts(soma, 11, " 23456789DOD\n\nValores: "); printf("\nNumero de # presentes no histograma: %d", count); } /* calendario.c */ /* teste de ano bissexto */ int bissexto(int ano) { if(ano % 400 == 0) return 1; if(ano % 100 == 0) return 0; if(ano % 4 == 0) return 1; return 0; } /* retorna o nmero de dias do ms, a contar com os anos bissextos */ int DiasDoMes(int ano, int mes) { static int diasDoMes[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if(mes == 2) return diasDoMes[1] + bissexto(ano); else if(mes >= 1 && mes <= 12) return diasDoMes[mes - 1]; return 0; } /* retorna a diferena entre duas datas*/ int DiasDeDiferenca(int ano1, int mes1, int dia1, int ano2, int mes2, int dia2) { int dias = 0, i; /* somar os dias de diferena por anos */ for(i = ano1; i < ano2; i++) dias += 365 + bissexto(i); /* subtrair os dias desde o incio do ano at mes1/dia1 */ for(i = 0; i < mes1 - 1; i++) dias -= DiasDoMes(ano1, i + 1); dias -= dia1 - 1; /* somar os dias desde o incio do ano at mes2/dia2 */ for(i = 0; i < mes2 - 1; i++) dias += DiasDoMes(ano2, i + 1); dias += dia2 - 1; } return dias;

/* retorna o dia da semana de uma data */ int DiaDaSemana(int ano, int mes, int dia) { /* ver os dias de diferena entre um dia conhecido, e fazer o resto da diviso por 7 */ return(6 + DiasDeDiferenca(2000, 1, 1, ano, mes, dia)) % 7; } /* determinao do ms/dia da Pscoa */ void Pascoa(int ano, int *mes, int *dia) { /* cdigo de acordo com o algoritmo */ int a, b, c, d, e, f, g, h, i, k, l, M; a = ano % 19; b = ano / 100; c = ano % 100; d = b / 4; e = b % 4; f = (b + 8) / 25; g = (b - f + 1) / 3; h = (19 *a + b - d - g + 15) % 30; i = c / 4; k = c % 4; l = (32 + 2 *e + 2 *i - h - k) % 7; M = (a + 11 *h + 22 *l) / 451; *mes = (h + l - 7 *M + 114) / 31;

18. Exerccios: Dicas, Respostas e Resolues


77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 *dia = ((h + l - 7 *M + 114) % 31) + 1; } /* esta funo adiciona/subtrai dias apenas no mesmo ano */ void AdicionarDias(int *ano, int *mes, int *dia, int ndias) { /* se h mais dias a subtrair que o ms, ento subtrair um ms inteiro */ while(ndias > 0 && ndias > DiasDoMes(*ano, *mes) -*dia) { ndias -= DiasDoMes(*ano, *mes); (*mes)++; } /* situao inversa */ while(ndias < 0 &&- ndias >*dia - 1) { ndias += DiasDoMes(*ano, *mes - 1); (*mes)--; } /* j h dias suficientes, subtrair apenas os dias*/ *dia += ndias; } /* coloca os dias de feriado para um dado ano/ms */ void Feriados(int ano, int mes, int dias[32]) { int i, pmes, pdia, fmes, fdia; static int deltaPascoa[] = { - 48, - 47, - 2, + 60 }; static int mesFeriadoFixo[] = { 1, 4, 5, 6, 10, 12, 12 }; static int diaFeriadoFixo[] = { 1, 25, 1, 10, 5, 1, 25 }; for(i = 0; i < 32; i++) dias[i] = 0; Pascoa(ano, & pmes, & pdia); if(pmes == mes) dias[pdia] = 1; for(i = 0; i < 4; i++) { fmes = pmes; fdia = pdia; AdicionarDias(& ano, & fmes, & fdia, deltaPascoa[i]); if(fmes == mes) dias[fdia] = 1; } for(i = 0; i < 7; i++) if(mesFeriadoFixo[i] == mes) dias[diaFeriadoFixo[i]] = 1; } /* imprime um ms formatadamente */ void PrintMes(int ano, int mes) { static char *nomesDosMeses[] = { "Janeiro", "Fevereiro", "Marco", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" }; int diasDoMes, i = 1, j; int dias[32]; Feriados(ano, mes, dias); diasDoMes = DiasDoMes(ano, mes); printf("\n%s de %d:", nomesDosMeses[mes - 1], ano); printf("\n#######################"); printf("\n# D S T Q Q S S #"); printf("\n#######################"); /* processar todos os dias, de semana a semana */ while(diasDoMes > 0) { /* incio de uma nova semana */ printf("\n#"); j = DiaDaSemana(ano, mes, i); /* na primeira semana pode no comear logo no domingo */ if(j > 0) printf("%*s", 3 *j, " "); do { if(dias[i] == 1) printf(" F "); else printf("%2d ", i);

199

200
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

Parte IV Anexos
i++; diasDoMes--; j = DiaDaSemana(ano, mes, i);

} while(j > 0 && diasDoMes > 0); /* se a semana acabou e no foi ao sbado (ltima semana), completar */ if(j > 0) printf("%*s", 3 *(7 - j), " "); printf("#"); } printf("\n#######################"); } int main() { int ano, mes, resultado, i; int dias[32]; printf("Calendario mensal, com indicacao de feriados.\nIndique ano mes:"); scanf("%d %d", & ano, & mes); PrintMes(ano, mes); /* cdigo para resposta pergunta */ resultado = 0; for(ano = 2000; ano < 2100; ano++) { /* feriados */ Feriados(ano, 2, dias); for(i = 0; i < 32; i++) resultado += dias[i]; /* dias do ms */ resultado += DiasDoMes(ano, 2); /* dia da semana do dia 1 */ resultado += DiaDaSemana(ano, 2, 1); } printf("\nResposta: %d", resultado); } /* editdistance.c */ #define MAXSTR 256 void PrintInts(int v[], int n, char *nome) { int i; printf("%s", nome); for(i = 0; i < n; i++) printf("%d ", v[i]); } void MostraMatriz(int[MAXSTR][MAXSTR], char *, char *, int, int); void MostraOperacoes(int[MAXSTR][MAXSTR], char *, char *, int, int); /* ver http://en.wikipedia.org/wiki/Levenshtein_distance */ int EditDistance(char *s1, char *s2) { int szs1, szs2, i, j, aux; /* vector bidimensional, para suporte ao algoritmo fornecido */ int m[MAXSTR][MAXSTR]; /* calcular o tamanho das strings */ szs1 = strlen(s1); szs2 = strlen(s2); /* caso fora dos limites, retornar */ if(szs1 >= MAXSTR || szs2 >= MAXSTR) return - 1; /* inicializaes */ for(i = 0; i <= szs1; i++) m[i][0] = i; for(j = 0; j <= szs2; j++) m[0][j] = j; /* calcular a matriz por ordem, at szs1, szs2 isto pode ser feito uma vez que a expresso fornecida calcula i,j com base em ndices inferiores, portanto j calculados. */ for(i = 1; i <= szs1; i++) for(j = 1; j <= szs2; j++) if(s1[i - 1] == s2[j - 1]) m[i][j] = m[i - 1][j - 1]; else { aux = m[i - 1][j] + 1; // apagar if(m[i][j - 1] + 1 < aux) // inserir aux = m[i][j - 1] + 1; if(m[i - 1][j - 1] + 1 < aux) // substituir aux = m[i - 1][j - 1] + 1; m[i][j] = aux; } MostraMatriz(m, s1, s2, szs1, szs2); MostraOperacoes(m, s1, s2, szs1, szs2); /* retornar a distncia de edio, na ltima posio do vector */ return m[szs1][szs2];

18. Exerccios: Dicas, Respostas e Resolues


56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 } void MostraMatriz( int m[MAXSTR][MAXSTR], char *s1, char *s2, int szs1, int szs2) { int i; char str[MAXSTR]; /* mostrar a matriz de base comparao */ printf(" Matriz de distancias:\n "); for(i = 0; i < szs2; i++) printf("%c ", s2[i]); for(i = 0; i <= szs1; i++) { if(i > 0) sprintf(str, "\n %c ", s1[i - 1]); else strcpy(str, "\n "); PrintInts(m[i], szs2 + 1, str); }

201

void MostraOperacoes( int m[MAXSTR][MAXSTR], char *s1, char *s2, int szs1, int szs2) { int i, j, k, aux, apagar, substituir, inserir; char str[MAXSTR]; printf("\n Operacoes:"); strcpy(str, s1); apagar = substituir = inserir = 0; for(i = szs1, j = szs2; i > 0 || j > 0;) { printf("\n %s ", str); if(i > 0 && j > 0 && s1[i - 1] == s2[j - 1]) { printf("(%c) %s", s1[i - 1], str); i--; j--; } else { if(i > 0) aux = m[i - 1][j]; // apagar else aux = m[i][j]; if(j > 0 && m[i][j - 1] < aux) // inserir aux = m[i][j - 1]; if(j > 0 && i > 0 && m[i - 1][j - 1] < aux) // substituir aux = m[i - 1][j - 1]; if(i > 0 && aux == m[i - 1][j]) // apagar { i--; k = i - 1; while(str[++ k]) str[k] = str[k + 1]; printf("[-%c] %s", s1[i], str); apagar++; } else if(j > 0 && aux == m[i][j - 1]) /* inserir */ { j--; k = i; while(str[k++]); while(k > i) { str[k] = str[k - 1]; k--; } str[i] = s2[j]; printf("[+%c] %s", s2[j], str); inserir++; } else if(i > 0 && j > 0) { /* substituir */ str[-- i] = s2[-- j]; printf("[%c/%c] %s", s1[i], s2[j], str); substituir++; }

202
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 else break;

Parte IV Anexos

} printf("\nApagar: %d, Inserir: %d, Substituir: %d\n", apagar, inserir, substituir); } int main() { char s1[MAXSTR], s2[MAXSTR]; printf("Calculo da distancia de edicao entre duas strings.\nIndique s1: "); gets(s1); printf("Indique s2: "); gets(s2); printf("Resultado: %d", EditDistance(s1, s2)); } /* adiciona.c */ /* declarao da estrutura SLista, com typedef para Lista */ typedef struct SLista { /* variveis pertencentes estrutura */ int valor; /* apontador para o elemento seguinte da lista, ou NULL se no existirem mais elementos */ struct SLista *seg; } Lista; /* funo adiciona um elemento ao topo da lista, retornando o novo incio da lista */ Lista *Adiciona(Lista *lista, int valor) { /* alocar espao para um novo elemento */ Lista *elemento = (Lista *) malloc(sizeof(Lista)); if(elemento != NULL) { /* atribuir o valor do novo elemento */ elemento->valor = valor; /* o elemento seguinte o actual primeiro elemento da lista */ elemento->seg = lista; } /* em caso de falha, retorna NULL, c.c. retorna o novo incio da lista, que o elemento criado */ return elemento; } /* remover o primeiro elemento da lista, retornando para o segundo, agora primeiro elemento da lista */ Lista *Remove(Lista *lista) { Lista *aux; if(lista != NULL) { /* guarda o elemento a retornar */ aux = lista->seg; /* liberta o espao alocado por este elemento */ free(lista); /* retornar o segundo, agora primeiro elemento da lista */ return aux; } return NULL; } /* contar o nmero de elementos na lista */ int Elementos(Lista *lista) { int count = 0; /* pode-se utilizar o prprio parmetro para iterar pela lista */ while(lista != NULL) { lista = lista->seg; count++; } return count; } /* verso de PrintInts para listas */ void PrintListaInts(Lista *lista, int n, char *nome) { printf("%s", nome); while(lista != NULL && n-->0) { printf("%d ", lista->valor); lista = lista->seg; } } int main() {

18. Exerccios: Dicas, Respostas e Resolues


74 75 76 77 78 79 80 81 82 83 84 85 86 87 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int i; Lista *lista = NULL; srand(1); /* adicionar 1000 elementos lista */ for(i = 0; i < 1000; i++) lista = Adiciona(lista, rand() % 1000); /* retornar os valores pedidos */ printf("Numero de elementos: %d\n", Elementos(lista)); PrintListaInts(lista, 10, "Elementos: "); /* libertar toda a lista antes de sair */ while(lista != NULL) lista = Remove(lista); } /* more.c */ #define MAXSTR 1024 int main(int argc, char **argv) { FILE *f; char str[MAXSTR], *pt, c; int count = 0; /* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizao: more <ficheiro>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rt"); if(f == NULL) { printf("Ficheiro %s no existe.\n", argv[1]); return; } while(! feof(f)) { count++; /* contador da linha */ if(fgets(str, MAXSTR, f) != NULL) { /* caso a string tenha menos de 75 caracteres, arrumar a questo */ if(strlen(str) <= 75) printf("%4d:%s", count, str); else { /* strings com mais de uma linha, utilizar um apontador */ pt = str; while(strlen(pt) > 75) { /* finalizar a string para ter 75 caracteres */ c = pt[75]; pt[75] = 0; /* na primeira linha, colocar o nmero da linha do ficheiro */ if(pt == str) printf("%4d:%s", count, pt); else printf(" %s", pt); /* repr o caracter retirado ao cortar a string */ pt[75] = c; /* avanar 75 caracteres */ pt += 75; } /* colocar o resto da string */ printf(" %s", pt); } } }

203

} /* insere.c */ typedef struct SLista { int valor; struct SLista *seg; } Lista;

Lista *Adiciona(Lista *lista, int valor) { Lista *elemento = (Lista *) malloc(sizeof(Lista)); if(elemento != NULL) { elemento->valor = valor; elemento->seg = lista; } return elemento; }

204
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 1 2 3 4 5 6 7

Parte IV Anexos

Lista *Remove(Lista *lista) { Lista *aux; if(lista != NULL) { aux = lista->seg; free(lista); return aux; } return NULL; } int Elementos(Lista *lista) { int count = 0; while(lista != NULL) { lista = lista->seg; count++; } return count; } Lista *Insere(Lista *lista, int valor) { /* caso a lista esteja vazia, ou o primeiro elemento igual ou maior, adicionar o elemento no incio */ if(lista == NULL || lista->valor >= valor) return Adiciona(lista, valor); /* caso a lista no seja vazia e o primeiro elemento menor, ento inserir a partir do segundo elemento */ lista->seg = Insere(lista->seg, valor); /* tendo sido inserido o elemento a partir do segundo elemento, o valor da lista no se altera */ return lista; } Lista *Apaga(Lista *lista, int valor) { /* apagar todos os elementos iniciais iguais ao valor */ while(lista != NULL && lista->valor == valor) lista = Remove(lista); /* se a lista ficou vazia, retornar */ if(lista == NULL) return NULL; /* repetir para os elementos seguintes */ lista->seg = Apaga(lista->seg, valor); } return lista;

void PrintListaInts(Lista *lista, int n, char *nome) { printf("%s", nome); while(lista != NULL && n-->0) { printf("%d ", lista->valor); lista = lista->seg; } } int main() { int i; Lista *lista = NULL; srand(1); for(i = 0; i < 1000; i++) lista = Insere(lista, rand() % 1000); PrintListaInts(lista, 10, "Elementos apos inserir 1000 elementos: "); for(i = 0; i < 1000; i += 2) lista = Apaga(lista, i); printf("\nNumero de elementos na lista apos apagar: %d\n", Elementos(lista)); PrintListaInts(lista, 10, "Elementos apos apagar: "); while(lista) lista = Remove(lista); } /* insertsort.c */ typedef struct SLista { int valor; struct SLista *seg; } Lista;

18. Exerccios: Dicas, Respostas e Resolues


8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

205

void PrintListaInts(Lista *lista, int n, char *nome) { printf("%s", nome); while(lista != NULL && n-->0) { printf("%d ", lista->valor); lista = lista->seg; } } Lista *Adiciona(Lista *lista, int valor) { Lista *elemento = (Lista *) malloc(sizeof(Lista)); if(elemento != NULL) { elemento->valor = valor; elemento->seg = lista; } return elemento; } Lista *Remove(Lista *lista) { Lista *aux; if(lista != NULL) { aux = lista->seg; free(lista); return aux; } return NULL; } Lista *Insere(Lista *lista, int valor) { if(lista == NULL || lista->valor >= valor) return Adiciona(lista, valor); lista->seg = Insere(lista->seg, valor); return lista; } /* ordenar a lista inserindo os elementos de forma ordenada */ Lista *InsertSort(Lista *lista) { int count = 0; /* criar uma lista vazia onde se vai colocando os elementos */ Lista *aux = NULL; while(lista != NULL) { /* inserir o novo elemento */ aux = Insere(aux, lista->valor); /* apagar o elemento da lista antiga, e avanar para o prximo */ lista = Remove(lista); /* mostrar este passo durante as primeiras iteraes */ if(count++< 10) PrintListaInts(aux, 10, "\n Lista: "); } /* retornar a nova lista, a antiga j no existe */ return aux; } int main() { int i; Lista *lista = NULL; srand(1); for(i = 0; i < 1000; i++) lista = Adiciona(lista, rand() % 1000); PrintListaInts(lista, 10, "Lista nao ordenada: "); lista = InsertSort(lista); PrintListaInts(lista, 10, "\nLista ordenada: "); while(lista != NULL) lista = Remove(lista); } /* enesimo.c */ typedef struct SLista { int valor; struct SLista *seg; } Lista; Lista *Adiciona(Lista *lista, int valor) { Lista *elemento = (Lista *) malloc(sizeof(Lista)); if(elemento != NULL) { elemento->valor = valor; elemento->seg = lista; }

206
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 return elemento; } Lista *Remove(Lista *lista) { Lista *aux; if(lista != NULL) { aux = lista->seg; free(lista); return aux; } return NULL; }

Parte IV Anexos

Lista *Insere(Lista *lista, int valor) { if(lista == NULL || lista->valor >= valor) return Adiciona(lista, valor); lista->seg = Insere(lista->seg, valor); return lista; } Lista *InsertSort(Lista *lista) { Lista *aux = NULL; while(lista != NULL) { aux = Insere(aux, lista->valor); lista = Remove(lista); } return aux; } /* retorna o valor em uma determinada posio, ou 0 se no existirem elementos suficientes */ int Valor(Lista *lista, int posicao) { /* avanar na lista at atingir a posio especificada */ while(posicao > 1 && lista != NULL) { posicao--; lista = lista->seg; } /* se a lista n acabou antes de se obter a posio pretendida, retornar o valor actual */ if(lista != NULL) return lista->valor; } return 0;

int main() { int i; Lista *lista = NULL; srand(1); for(i = 0; i < 1000; i++) lista = Adiciona(lista, rand() % 1000); lista = InsertSort(lista); printf("elementos 250, 500 e 750: %d %d %d", Valor(lista, 250), Valor(lista, 500), Valor(lista, 750)); while(lista != NULL) lista = Remove(lista); } /* palavras.c */ #define MAXSTR 1024 /* funo que dada uma string retorna o nmero de palavras */ int Palavras(char *str) { char strBackup[MAXSTR]; char *pt = str; int count = 0; strcpy(strBackup, str); pt = strtok(pt, " \t\n\r"); while(pt != NULL) { count++; /* uma palavra s com letras partida */ while(pt[0] != 0) if(! isalpha(pt[0])) { /* afinal a palavra no tem apenas letras */ count--; break; } else pt++; pt = strtok(NULL, " \t\n\r");

18. Exerccios: Dicas, Respostas e Resolues


25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 1 2 } /* mostrar resultado se existirem palavras */ if(count > 0) printf(" [%d] %s", count, strBackup); return count; } int main(int argc, char **argv) { FILE *f; char str[MAXSTR], *pt, c; int count = 0; setlocale(LC_ALL, ""); /* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizao: palavras <ficheiro>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rt"); if(f == NULL) { printf("Ficheiro %s no existe.\n", argv[1]); return; } while(! feof(f)) if(fgets(str, MAXSTR, f) != NULL) count += Palavras(str); printf("\nO ficheiro %s contem %d palavras.\n", argv[1], count); } /* cifracesar.c */ #define MAXSTR 255 void CifraDeCesar(char *str, int chave) { int i, chaved; chaved = chave; /* chave negativa, somar a diferena at ser positiva */ while(chave < 0) chave += 'z' - 'a' + 1; while(chaved < 0) chaved += '9' - '0' + 1; /* processar todos os caracteres */ for(i = 0; str[i] != 0; i++) if(str[i] >= 'a' && str[i] <= 'z') /* novo valor ser: a + (k-a + chave mod z-a+1) */ str[i] = 'a' + (str[i] - 'a' + chave) % ('z' - 'a' + 1); else if(str[i] >= 'A' && str[i] <= 'Z') str[i] = 'A' + (str[i] - 'A' + chave) % ('Z' - 'A' + 1); else if(str[i] >= '0' && str[i] <= '9') str[i] = '0' + (str[i] - '0' + chaved) % ('9' - '0' + 1); } int main(int argc, char **argv) { FILE *f; int chave; char str[MAXSTR]; if(argc != 3) { printf("Indicar o nome do ficheiro e chave.\n"); return; } chave = atoi(argv[2]); f = fopen(argv[1], "rt"); if(f == NULL) { printf("No foi possivel abrir para leitura o ficheiro %s.\n", argv[1]); return; } while(fgets(str, MAXSTR, f) != NULL) { CifraDeCesar(str, chave); printf("%s", str); } fclose(f); } /* more.c */ #define MAXSTR 1024

207

208
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

Parte IV Anexos

int main(int argc, char **argv) { FILE *f; char *pt; int i, j, tamanho; /* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizacao: more <ficheiro>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rb"); if(f == NULL) { printf("Ficheiro %s nao existe.\n", argv[1]); return; } fseek(f, 0, SEEK_END); tamanho = ftell(f); pt = (char *) malloc(tamanho); if(pt == NULL) return; fseek(f, 0, SEEK_SET); fread(pt, tamanho, 1, f); fclose(f); /* processar todos os caracteres do ficheiro */ for(i = 0; i < tamanho; i += 16) { printf("\n%6d|", i); /* colocar os caracteres em hexadecimal */ for(j = 0; j < 16 && i + j < tamanho; j++) printf("%02x ",(unsigned char) pt[i + j]); /* colocar espaos no final da linha */ while(j++< 16) printf(" "); /* colocar os caracteres em texto, se possvel */ printf("|"); for(j = 0; j < 16 && i + j < tamanho; j++) if(isprint(pt[i + j])) printf("%c", pt[i + j]); else printf(" "); } free(pt); } /* basededados.c */ #define MAXSTR 255 /* nome do ficheiro da BD fixo, para no ter de o estar a introduzir ao entrar na aplicao */ #define NOME_FICHEIRO "basededados.dat" /* estrutura definda com os campos necessrios para cada registo */ typedef struct { int id; char nome[MAXSTR]; char telefone[MAXSTR]; char cidade[MAXSTR]; char descricao[MAXSTR]; } SContacto; /* funo para gravar este novo registo num no ficheiro */ void Gravar(SContacto *registo) { FILE *f = fopen(NOME_FICHEIRO, "r+b"); /* se no abrir, tentar criar o ficheiro de raiz */ if(f == NULL) f = fopen(NOME_FICHEIRO, "w+b"); if(f != NULL) { /* posicionar o ficheiro na posio do registo */ fseek(f,(long)(registo->id) *sizeof(SContacto), SEEK_SET); /* gravar sobrepondo ou gravando um novo registo */ fwrite(registo, sizeof(SContacto), 1, f); fclose(f); } else printf("\nErro: nao foi possivel abrir o ficheiro."); } /* ler um registo do ficheiro */ void Ler(SContacto *registo)

18. Exerccios: Dicas, Respostas e Resolues


40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 { FILE *f = fopen(NOME_FICHEIRO, "rb"); if(f != NULL) { /* posiciona e l o registo com base no id */ fseek(f,(long)(registo->id) *sizeof(SContacto), SEEK_SET); fread(registo, sizeof(SContacto), 1, f); fclose(f); } else printf("\nErro: nao foi possivel abrir o ficheiro."); } /* retorna o nmero de registos, obtido atravs do ficheiro */ int Registos() { int n; FILE *f = fopen(NOME_FICHEIRO, "rb"); if(f != NULL) { fseek(f, 0, SEEK_END); n = ftell(f); fclose(f); return n / sizeof(SContacto); } return 0; } /* pedir ao utilizador para introduzir os dados de um registo */ void Carregar(SContacto *registo) { printf("Nome: "); gets(registo->nome); printf("Telefone: "); gets(registo->telefone); printf("Cidade: "); gets(registo->cidade); printf("Descrio: "); gets(registo->descricao); } int Menu() { char str[MAXSTR]; printf("\n#########################"); printf("\n# Menu: #"); printf("\n#########################"); printf("\n# 1 | LISTAR registos #"); printf("\n# 2 | ADICIONAR registo #"); printf("\n# 3 | VER registo #"); printf("\n# 4 | EDITAR registo #"); printf("\n#########################"); printf("\nOpo: "); gets(str); return atoi(str); } void ListarRegistos() { int i, total; SContacto registo; total = Registos(); printf("\n########"); printf("\n# Lista:", total); printf("\n########"); for(i = 0; i < total; i++) { registo.id = i; Ler(& registo); printf("\n# %4d | %s", registo.id, registo.nome); } printf("\n########\n"); } void VerRegisto() { char str[MAXSTR]; SContacto registo; printf("ID: "); gets(str); registo.id = atoi(str); Ler(& registo); printf("\n#############"); printf("\n# Nome | %s", printf("\n# Telefone | %s", printf("\n# Cidade | %s", printf("\n# Descrio | %s", printf("\n#############\n"); } int main() {

209

registo.nome); registo.telefone); registo.cidade); registo.descricao);

210
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 int opcao, i; char str[MAXSTR]; SContacto registo; while(1) { opcao = Menu(); if(opcao == 1) { ListarRegistos(); } else if(opcao == 2) { /* adicionar registo */ registo.id = Registos(); Carregar(& registo); Gravar(& registo); } else if(opcao == 3) { VerRegisto(); } else if(opcao == 4) { /* editar registo */ printf("ID: "); gets(str); registo.id = atoi(str); Carregar(& registo); Gravar(& registo); } else break; } } /* mergesort.c */ typedef struct SLista { int valor; struct SLista *seg; } Lista;

Parte IV Anexos

Lista *Adiciona(Lista *lista, int valor) { Lista *elemento = (Lista *) malloc(sizeof(Lista)); if(elemento != NULL) { elemento->valor = valor; elemento->seg = lista; } return elemento; } Lista *Remove(Lista *lista) { Lista *aux; if(lista != NULL) { aux = lista->seg; free(lista); return aux; } return NULL; } Lista *MergeSort(Lista *lista) { Lista *lista1 = NULL, *lista2 = NULL; /* se a lista tiver apenas um elemento, retornar */ if(lista == NULL || lista->seg == NULL) return lista; /* dividir a lista a meio */ while(lista != NULL) { lista1 = Adiciona(lista1, lista->valor); lista = Remove(lista); /* elementos pares para a lista 2 */ if(lista != NULL) { lista2 = Adiciona(lista2, lista->valor); lista = Remove(lista); } } /* neste momento j no h lista original, apenas lista1 e lista2 */ /* ordenar ambas as metades */ lista1 = MergeSort(lista1); lista2 = MergeSort(lista2);

18. Exerccios: Dicas, Respostas e Resolues


57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

211

/* juntar ambas as listas ordenadas */ while(lista1 != NULL && lista2 != NULL) { if(lista1->valor < lista2->valor) { lista = Adiciona(lista, lista1->valor); lista1 = Remove(lista1); } else { lista = Adiciona(lista, lista2->valor); lista2 = Remove(lista2); } } /* adicionar os restantes elementos */ if(lista1 == NULL) lista1 = lista2; while(lista1 != NULL) { lista = Adiciona(lista, lista1->valor); lista1 = Remove(lista1); } /* inverter a lista, j que os elementos mais baixos so colocados primeiro, mas ficam em ltimo */ while(lista != NULL) { lista1 = Adiciona(lista1, lista->valor); lista = Remove(lista); } return lista1; } int main() { int i; Lista *lista = NULL; srand(1); for(i = 0; i < 100000; i++) lista = Adiciona(lista, rand() % 1000); lista = MergeSort(lista); /* remover os primeiros 999 elementos, para aceder posio 1000 */ for(i = 0; i < 999; i++) lista = Remove(lista); printf("Elemento na posio 1000: %d\n", lista->valor); /* remover os primeiros 9000 elementos, para aceder posio 10000 */ for(i = 0; i < 9000; i++) lista = Remove(lista); printf("Elemento na posio 10000: %d", lista->valor); while(lista != NULL) lista = Remove(lista); } /* sort.c */ #define MAXSTR 1024 typedef struct SListaSTR { char *str; struct SListaSTR *seg; } ListaSTR; ListaSTR *Adiciona(ListaSTR *lista, char *str) { ListaSTR *elemento = (ListaSTR *) malloc(sizeof(ListaSTR)); if(elemento != NULL) { /* alocar espao para a string, e copiar a string */ elemento->str = (char *) malloc(strlen(str) + 1); strcpy(elemento->str, str); elemento->seg = lista; } return elemento; } ListaSTR *Remove(ListaSTR *lista) { ListaSTR *aux; if(lista != NULL) { aux = lista->seg; /* libertar no s a estrutura como a string */ free(lista->str); free(lista); return aux; } return NULL; }

212
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

Parte IV Anexos
ListaSTR *MergeSort(ListaSTR *lista) { ListaSTR *lista1 = NULL, *lista2 = NULL; if(lista == NULL || lista->seg == NULL) return lista; while(lista != NULL) { lista1 = Adiciona(lista1, lista->str); lista = Remove(lista); if(lista != NULL) { lista2 = Adiciona(lista2, lista->str); lista = Remove(lista); } } lista1 = MergeSort(lista1); lista2 = MergeSort(lista2); /* tudo igual at aqui, excepto utilizar str em vez de valor. Agora, ao juntar ambas as listas ordenadas, utilizao de strcmp */ while(lista1 != NULL && lista2 != NULL) { if(strcmp(lista1->str, lista2->str) < 0) { lista = Adiciona(lista, lista1->str); lista1 = Remove(lista1); } else { lista = Adiciona(lista, lista2->str); lista2 = Remove(lista2); } } if(lista1 == NULL) lista1 = lista2; while(lista1 != NULL) { lista = Adiciona(lista, lista1->str); lista1 = Remove(lista1); } while(lista != NULL) { lista1 = Adiciona(lista1, lista->str); lista = Remove(lista); } return lista1; } int main(int argc, char **argv) { FILE *f; char str[MAXSTR], *pt, c; ListaSTR *lista = NULL, *aux; /* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizao: sort <ficheiro>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rt"); if(f == NULL) { printf("Ficheiro %s no existe.\n", argv[1]); return; } while(! feof(f)) if(fgets(str, MAXSTR, f) != NULL) lista = Adiciona(lista, str); lista = MergeSort(lista); /* mostrar a lista ordenada */ aux = lista; while(aux != NULL) { printf("%s", aux->str); aux = aux->seg; } /* libertar a lista */ while(lista != NULL)

18. Exerccios: Dicas, Respostas e Resolues


130 131 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 lista = Remove(lista); } /* find.c */ #define MAXSTR 1024 int Find(char *str, char *find) { int ifind, istr, i; /* verificar se a string em find possvel de corresponder string de str, desde i */ for(i = 0; str[i] != 0; i++) { ifind = 0; istr = i; do { if(find[ifind] == 0) return i; /* se se pretende ignorar um caracter, avanar ifind e istr */ if(find[ifind] == '?') { ifind++; istr++; } else if(find[ifind] == '*') { /* pretende-se ignorar zero ou mais caracteres: verificar se o resto da string pode-se obter com o resto da string de procura*/ if(Find(str + istr, find + ifind + 1) >= 0) return i; break; } else if(find[ifind] == str[istr]) { /* match de dois caracteres, avanar */ ifind++; istr++; } else /* caracteres distintos, parar */ break; } while(find[ifind] == 0 || str[istr] != 0); } return - 1; } int main(int argc, char **argv) { FILE *f; char str[MAXSTR], *pt, c; int count = 0; /* verificar se foi dado um argumento */ if(argc <= 2) { printf("Utilizao: find <ficheiro> <procura>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rt"); if(f == NULL) { printf("Ficheiro %s no existe.\n", argv[1]); return; } /* adaptado de more.c */ while(! feof(f)) { count++; /* apresenta a linha apenas se h match */ if(fgets(str, MAXSTR, f) != NULL && Find(str, argv[2]) >= 0) { if(strlen(str) <= 75) printf("%4d:%s", count, str); else { pt = str; while(strlen(pt) > 75) { c = pt[75]; pt[75] = 0; if(pt == str) printf("%4d:%s", count, pt); else printf(" %s", pt); pt[75] = c; pt += 75;

213

214
91 92 93 94 95 96 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 1 2 3 4 } printf(" } } } } /* histograma.c */ #define MAXELEMENTOS 100 #define MAXVALORES 22 #define MAXSTR 255

Parte IV Anexos

%s", pt);

/* funo reutilizada do exerccio doisdados.c da AF7 */ int GraficoDeBarras(int serie[], int n) { char grafico[MAXVALORES][MAXELEMENTOS]; int i, j, maximo, count = 0; float factor; if(n > MAXELEMENTOS) return; for(i = 0; i < n; i++) if(i == 0 || maximo < serie[i]) maximo = serie[i]; factor = 15. / maximo; sprintf(grafico[0], ""); sprintf(grafico[1], " +"); for(i = 0; i < n; i++) if(i % 5 == 0) strcat(grafico[1], "+"); else strcat(grafico[1], "-"); for(i = 2; i < 18; i++) sprintf(grafico[i], "% 5d|%*s ",(int)((i - 2) / factor + 0.5), n, " "); for(i = 0; i < n; i++) for(j = 0; j < 16; j++) if(serie[i] *factor >= j) { grafico[j + 2][i + 6] = '#'; count++; } for(i = 17; i > 0; i--) printf("%s\n", grafico[i]); return count; } int main(int argc, char **argv) { int histograma[256], i, j, count; char str[MAXSTR], schar[256]; FILE *f; if(argc != 2) { printf("Deve indicar um ficheiro para fazer o histograma.\n"); return; } f = fopen(argv[1], "r"); if(f == NULL) { printf("Nao foi possivel abrir para leitura o ficheiro %s.\n", argv[1]); return; } /* primeiro fazer o histograma completo */ for(i = 0; i < 256; i++) histograma[i] = 0; while(fgets(str, MAXSTR, f) != NULL) for(i = 0; str[i] != 0; i++) histograma[(unsigned char) str[i]]++; /* concatenar e construir a string de valores */ for(i = 0; i < 256; i++) schar[i] = i; /* utilizar apenas os caracteres alfa-numricos */ for(i = 0, j = 0; i < 256; i++) if(schar[i] >= 'a' && schar[i] <= 'z' || schar[i] >= 'A' && schar[i] <= 'Z' || schar[i] >= '0' && schar[i] <= '9') { histograma[j] = histograma[i]; schar[j] = schar[i]; j++; } schar[j] = 0; count = GraficoDeBarras(histograma, j); printf(" %s\nNumero de cardinais no histograma: %d", schar, count); } /* indicador.c */ #define MAXSTR 1024 typedef struct SListaSTR

18. Exerccios: Dicas, Respostas e Resolues


5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 { char *str; struct SListaSTR *seg; } ListaSTR; ListaSTR *Adiciona(ListaSTR *lista, char *str) { ListaSTR *elemento = (ListaSTR *) malloc(sizeof(ListaSTR)); if(elemento != NULL) { elemento->str = (char *) malloc(strlen(str) + 1); strcpy(elemento->str, str); elemento->seg = lista; } /* em caso de falha, retorna NULL */ return elemento; } ListaSTR *Remove(ListaSTR *lista) { if(lista != NULL) { ListaSTR *aux = lista->seg; free(lista->str); free(lista); return aux; } return NULL; } ListaSTR *Inverte(ListaSTR *lista) { ListaSTR *aux = NULL; while(lista != NULL) { aux = Adiciona(aux, lista->str); lista = Remove(lista); } return aux; } char *EspacosCodigo(char *codigo) { char *pt; /* colocar todos os caracteres brancos em espaos */ while((pt = strchr(codigo, '\n')) != NULL) *pt = ' '; while((pt = strchr(codigo, '\r')) != NULL) *pt = ' '; while((pt = strchr(codigo, '\t')) != NULL) *pt = ' '; } return codigo;

215

void RemoverStrings(ListaSTR *lista) { char *pt, tipo = ' ', lastchar = ' ', llastchar = ' '; while(lista != NULL) { /* apagar as macros */ if(lista->str[0] == '#') { pt = (char *) malloc(1); *pt = '\0'; free(lista->str); lista->str = pt; continue; } pt = lista->str; while(*pt != '\0') { /* verificar se h incio de string entre aspas/plicas/comentrios */ if(tipo != ' ' || pt[0] == '\'' || pt[0] == '"' || pt[0] == '/' && pt[1] == '*') { if(tipo == ' ') { tipo = pt[0]; pt++; } while(*pt != '\0' && (pt[0] != tipo || tipo == '/' && lastchar != '*' || (*pt == '\'' ||*pt == '"') && lastchar == '\\' && llastchar != '\\')) { llastchar = lastchar;

216
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

Parte IV Anexos
lastchar = pt[0]; /* o contedo anulado e colocado c, de forma a no influenciar o indicador */ *pt = 'c'; pt++; } if(*pt != '\0') { tipo = ' '; pt++; } } else pt++; } lista = lista->seg; } } void InicioFimBlocos(ListaSTR *lista) { static char *inicioFimBlocos = "{}"; char *pt; int i; while(lista != NULL) { for(i = 0; inicioFimBlocos[i] != '\0'; i++) { /* no pode estar nada antes do incio/fim do bloco */ if((pt = strchr(lista->str, inicioFimBlocos[i])) != NULL && pt != lista->str) { lista->seg = Adiciona(lista->seg, pt); *pt = 0; } /* no pode estar nada depois do incio/fim do bloco */ if(lista->str[0] == inicioFimBlocos[i] && strlen(lista->str) > 1) { lista->seg = Adiciona(lista->seg, lista->str + 1); lista->str[1] = 0; } } lista = lista->seg; } } int Indicador(ListaSTR *lista) { int chavetasAbertas = 0, i; int resultado = 0, conds = 0, ciclos = 0, comandos = 0; char *pt; static char *condsSTR[] = { "if", "case", NULL }; static char *ciclosSTR[] = { "for", "while", NULL }; /* processar todos os blocos globais */ printf("\n conds ciclos comandos parcial"); while(lista != NULL) { /* contar os blocos */ if(lista->str[0] == '{') chavetasAbertas++; else if(lista->str[0] == '}') { /* fim de bloco */ chavetasAbertas--; if(chavetasAbertas == 0) { /* contabilizar o bloco global */ resultado += (conds + 2 *ciclos + 1) *(conds + 2 *ciclos + 1) + comandos; /* mostrar parcial */ printf("\n%8d %8d %8d %8d", conds, ciclos, comandos, resultado); /* zerar os valores para o prximo bloco */ conds = ciclos = comandos = 0; } else if(chavetasAbertas < 0) chavetasAbertas = 0; } else { /* nmero de comandos */ pt = lista->str; while((pt = strchr(pt + 1, ';')) != NULL) comandos++;

18. Exerccios: Dicas, Respostas e Resolues


189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

217

/* nmero de conds */ for(i = 0; condsSTR[i] != NULL; i++) { pt = lista->str; while((pt = strstr(pt, condsSTR[i])) != NULL) { conds++; pt++; } } /* nmero de ciclos */ for(i = 0; ciclosSTR[i] != NULL; i++) { pt = lista->str; while((pt = strstr(pt, ciclosSTR[i])) != NULL) { ciclos++; pt++; } }

lista = lista->seg; } return resultado; } int main(int argc, char **argv) { FILE *f; int i; char str[MAXSTR], *pt, c; ListaSTR *lista = NULL, *aux, *buffer; /* verificar se foi dado um argumento */ if(argc <= 1) { printf("Utilizacao: indicador <ficheiro>\n"); return; } /* abrir o ficheiro em modo de texto*/ f = fopen(argv[1], "rt"); if(f == NULL) { printf("Ficheiro %s nao existe.\n", argv[1]); return; } while(! feof(f)) if(fgets(str, MAXSTR, f) != NULL) lista = Adiciona(lista, str); lista = Inverte(lista); /* acertar os espaos nas linhas */ aux = lista; while(aux != NULL) { aux->str = EspacosCodigo(aux->str); aux = aux->seg; } /* congelar strings de comentrios, strings e plicas */ RemoverStrings(lista); /* incio/fim de blocos em linhas isoladas */ InicioFimBlocos(lista); /* calcular indicador */ printf("\nIndicador: %d", Indicador(lista)); /* libertar a lista */ while(lista != NULL) lista = Remove(lista); }

NDICE DE PROGRAMAS
Programa 1-1 Ol Mundo ..................................................................................................................................................... 7 Programa 2-1 Troca o valor de duas variveis............................................................................................................ 9 Programa 2-2 Troca o valor de duas variveis sem varivel auxiliar .............................................................. 10 Programa 2-3 Imprime o nmero de bytes que um inteiro ocupa ................................................................... 11 Programa 2-4 Imprime diferentes tipos de variveis ............................................................................................ 12 Programa 2-5 Pede valores de variveis de diferentes tipos que depois imprime .................................... 13 Programa 3-1 Determina se um nmero par ou impar ...................................................................................... 16 Programa 3-2 Determina se um ano normal ou bissexto .................................................................................. 18 Programa 3-3 Determina o nmero de dias de um ms/ano .............................................................................. 19 Programa 4-1 Soma dos primeiros 4 quadrados naturais ................................................................................... 23 Programa 4-2 Soma dos primeiros 4 quadrados naturais utilizando um ciclo ........................................... 24 Programa 4-3 Imprime o cdigo numrico correspondente a cada letra ...................................................... 25 Programa 4-4 Calculo do nmero de pares de inteiros que respeitam uma determinada condio . 26 Programa 5-1 Escolha de opes num menu ............................................................................................................. 40 Programa 5-2 Escolha de opes num menu, utilizando uma funo ............................................................. 42 Programa 5-3 Determina se um ano normal ou bissexto utilizando funes ........................................... 44 Programa 5-4 Determina o nmero de dias de um ms/ano utilizando funes ....................................... 45 Programa 6-1 Rplica do Programa 4-2....................................................................................................................... 49 Programa 6-2 Soma dos primeiros 4 quadrados naturais com o ciclo for ................................................. 49 Programa 6-3 Utilizao alternativa do ciclo for na soma dos 4 quadrados naturais........................... 50 Programa 6-4 Verso com ciclos for do Programa 4-4 ...................................................................................... 50 Programa 6-5 Verso com ciclos do-while do Programa 5-2 .......................................................................... 52 Programa 6-6 Verso removendo a funo Menu do Programa 6-5 ................................................................ 53 Programa 6-7 Verso com switch do Programa 6-6 ........................................................................................... 54 Programa 6-8 Funo DiasDoMes com switch do Programa 5-4................................................................... 55 Programa 7-1 Calculo do total e mdia de um indicador por cada dia da semana .................................... 57 Programa 7-2 Calculo do total e mdia, verso com vectores ............................................................................ 58 Programa 7-3 Confirmao que uma string um vector de caracteres ......................................................... 60 Programa 7-4 Calcular o tamanho de uma string .................................................................................................... 61 Programa 7-5 Funo DiasDoMes com um vector inicializado na declarao............................................. 62 Programa 7-6 Exemplificao das diferentes maneiras de se inicializar strings........................................ 62 Programa 7-7 Calculo do total e mdia, sem cdigo repetido, utilizando vectores ................................... 63 Programa 7-8 Leitura de argumentos passados ao programa ........................................................................... 64 Programa 7-9 Soma de duas matrizes .......................................................................................................................... 64 Programa 8-1 Tentativa de implementao de uma funo para trocar o valor de duas variveis.... 69 Programa 8-2 Funo Troca que realmente troca o valor de duas variveis ............................................... 70 Programa 8-3 Verso final da funo de troca de duas variveis...................................................................... 71 Programa 8-4 Exemplo de utilizao de variveis globais ................................................................................... 72 Programa 8-5 Exemplo de utilizao de variveis estticas (static) ............................................................ 73 Programa 8-6 Procura a primeira ocorrncia de uma string em outra (strstr) .......................................... 74 Programa 9-1 Factorial, verso iterativa e recursiva ............................................................................................. 78 Programa 9-2 Torres de Hanoi ........................................................................................................................................ 80 Programa 10-1 Generalizao do Programa 7-2, calculando a mdia e o desvio padro .................... 101 Programa 10-2 Rplica do Programa 9-1 ................................................................................................................. 103 Programa 10-3 Calculo da mdia e desvio padro, alocando apenas a memria necessria ............. 104 Programa 10-4 Concatena as strings introduzidas, utilizando apenas a memria necessria .......... 106

Programa 10-5 Gerao de um vector aleatrio.................................................................................................... 107 Programa 11-1 Rplica do Programa 5-4 ................................................................................................................. 113 Programa 11-2 Calculo dos dias de um ms/ano, utilizando estruturas .................................................... 115 Programa 11-3 Registo de uma estrutura com dados de diferentes tipos de pessoas .......................... 116 Programa 11-4 Verso do registo de pessoas, mais legvel e eficiente ........................................................ 118 Programa 11-5 Estrutura de um vector com os valores e a sua dimenso ................................................ 119 Programa 11-6 Tipo abstracto de dados TVectorInt ........................................................................................... 121 Programa 11-7 Verso de TVectorInt com apontadores, e controle do tempo ........................................ 123 Programa 11-8 Ajustes feitos no tipo abstracto de dados TVectorInt, para melhoria da eficincia 125 Programa 11-9 Tipo abstracto de dados TListaInt............................................................................................... 128 Programa 12-1 Nmero de dias dado ms/ano, lendo dados a partir do ficheiro in.txt .................. 134 Programa 12-2 Nmero de dias do ms dado ms/ano lendo dados de in.txt e escrevendo para out.txt ................................................................................................................................................................................... 135 Programa 12-3 Ficha de pessoa, com gravao e leitura do ficheiro ............................................................ 138 Programa 12-4 Actualizao do tipo abstracto TVectorInt para ler/gravar para ficheiro, em modo binrio ..................................................................................................................................................................................... 141 Programa 13-1 Verso condensada do nmero de dias de um dado ms/ano ........................................ 143 Programa 13-2 Verso ainda mais condensada do nmero de dias de um dado ms/ano ................. 144 Programa 13-3 Instruo de shift vs multiplicao/diviso por 2 ................................................................ 145 Programa 13-4 Representao binria de um nmero real ............................................................................. 146 Programa 13-5 Verso do Programa 13-4 utilizando a estrutura union ................................................. 147 Programa 13-6 Utilizao do formato do nmero real para aumentar a eficincia de algumas operaes ............................................................................................................................................................................... 148 Programa 13-7 Exemplo de utilizao de vrias variveis booleanas numa s varivei inteira ...... 149 Programa 15-1 Cdigo incompreensvel .................................................................................................................. 166 Programa 15-2 Programa incompreensvel, preparado para debug ............................................................ 167

NDICE REMISSIVO
"(null)" ................................. 75, 108 #define ......................................... 65 #include ......................................... 7 conjunto de conjuntos de caracteres ............................. 63 conjunto de strings .................. 64 conjuntos de variveis ............. 57 continue ...................................... 55 copy / paste................................. 41 funes muito grandes ................ 76 Funes standard mais utilizadas .............................................. 169

A
abrir um ficheiro e no chegar a fechar .................................... 142 abrir um ficheiro e no testar se foi bem aberto ........................... 142 abstraco de dados .................. 120 abstraco funcional .................... 43 adiciona ..................................... 151 alocar memria e no testar se a operao foi bem sucedida ... 112 apontadores................................. 74 argumentos ................................. 83 argv .............................................. 64 arranjos ....................................... 31 atribuies ..................................... 9 atribuies entre variveis de tipos distintos ................................ 112

G
gets .............................................. 60

D
declarao de variveis ................. 9 declaraes de variveis fora do incio das funes ................... 47 declaraes ou atribuies a variveis nunca utilizadas ....... 13 doisdados ................................... 94 double ......................................... 11 do-while ...................................... 51 duas instrues na mesma linha . 21

H
Heap .......................................... 104 histograma ................................ 158 hms ............................................. 30

I
if... ............................................... 16 indentao varivel ..................... 20 indicador.................................... 159 inicializao de strings ................. 62 inicializao de um vector ........... 62 insere ......................................... 152 insertsort ................................... 153 instrues parecidas no seguidas ................................................ 46 instrues parecidas seguidas ..... 27 int... ............................................. 11 inverte ........................................ 83

E
editdistance................................ 96 else .............................................. 16 enesimo..................................... 153 erros comuns .............................. 13 estrutura de dados .................... 128 Estruturas .................................. 113 euler............................................ 33 execuo alternativa ................... 16 execuo do programa................ 13 execuo passo-a-passo .............. 10 Exemplo de cdigo incompreensivel ............................................. 165 expresso lgica .....................16, 20

B
baralhar ...................................... 89 basededados .............................. 155 binarysearch ............................... 91 bits num nmero real ................ 146 bloco de cdigo ............................ 19 break ............................................ 55

J
jogodados ................................... 88

C
calendario ................................... 94 cast............................................. 105 char .............................................. 11 ciclo dentro de outro ciclo ........... 26 Ciclos ............................................ 23 ciclos contendo apenas uma varivel lgica no teste de paragem .................................. 55 cifracesar ................................... 154 combinacoes ............................... 33 comentrios ao cdigo .................. 9 comentrios explicam a linguagem C .............................................. 14 comentrios inexistentes em partes complexas ............................. 150 compilador ..................................... 7 compilador e Editor de C ........... 161 compile time .............................. 100 Condicinais ................................... 16

L F
fibonacci ..................................... 32 ficheiros..................................... 130 find.......................................84, 157 float ............................................. 11 fopen / fprintf / fscanf / fgets / feof / fclose .................................. 133 for................................................ 49 formularesolvente ..................... 36 fread / fwrite ............................. 140 free ............................................ 104 fseek / ftell ................................ 140 funo ......................................... 40 funes com muitos argumentos 77 funes com parmetros no nome ............................................... 46 funes especficas ..................... 47 linguagem C ................................... 7 linguagem de programao ........... 7 linguagem imperativa .................... 7 linhas de cdigo nunca executadas ................................................ 21 listas .......................................... 126 long .............................................. 11 long long ...................................... 11

M
m utilizao da recurso ............ 82 m utilizao do switch ou cadeia if-else ...................................... 66 macro.......................................... 65 macros em todas as constantes .. 67

main ............................................... 8 mais ciclos e condicionais ............ 49 malloc ........................................ 104 matrizes ....................................... 64 maximo ....................................... 85 mdc .............................................. 85 Memria .................................... 100 mergesort ........................... 90, 156 modo binrio ............................. 139 modo de texto ........................... 139 more .................................. 151, 155

P
palavras ..................................... 154 passagem por referncia............. 71 pi. ............................................. 35 pokerdados ................................ 92 pr-processamento ....................... 7 Primeiro Programa ........................ 7 primo .......................................... 34 printf ............................................. 8 Procedimentos ............................ 69 produto....................................... 31 programa....................................... 7 programar ..................................... 7

switch .......................................... 53

T
TCC: Tiny C Compiler ................. 161 tempo de compilao ................ 100 tempo de corrida ....................... 100 time(NULL); ................................. 84 time.h .......................................... 84 tipos abstractos de dados.......... 120 tipos elementares........................ 11 torres de Hanoi ............................ 79 triplasoma .................................. 35 trocos .......................................... 34 trocos2 ........................................ 88 truques ...................................... 143 typedef ...................................... 117

N
No consigo perceber o resultado do programa ......................... 164 no libertar memria aps alocar .............................................. 112 ndamas ........................................ 93 nomes sem significado................. 76 Notepad++ ................................. 163 NULL..................................... 75, 105 numeracaoromana ..................... 91

R
rand ............................................ 84 Recurso...................................... 78 removedups ............................... 87 run time .................................... 100

U
uma atribuio, uma leitura ........ 13 union ......................................... 147 utilizao do goto ........................ 27 utilizar atribuies como expresses ............................ 144

S O
ol mundo ...................................... 7 olamundosizeof .......................... 29 operaes binrias lgicas ......... 146 operador "." ............................... 116 operador & .................................. 71 operador * ........................... 71, 116 operador ? ................................. 143 operador -> ................................ 116 operador >> >>= << <<= ............. 145 operador de linha de comando | .............................................. 132 operador de linha de comando >> .............................................. 132 operadores | & ^ ~..................... 146 operadores || && !................... 17 operadores + - * / % .................. 17 operadores ++ -- .......................... 24 operadores += -= *= /= %= ........... 24 operadores == != > >= < <= ...... 17 operadores binrios ................... 145 operadores de linha de comando < e > ......................................... 130 scanf ............................................ 12 short ............................................ 11 situao de desespero .............. 164 sizeof ........................................... 11 soma ........................................... 30 somadigitos ................................ 32 somanumeros ............................ 87 sort ......................................86, 157 srand ........................................... 84 Stack .......................................... 102 static............................................ 72 stdin .......................................... 135 stdlib.h ........................................ 84 stdout ........................................ 136 strcat ........................................... 74 strcpy .......................................... 74 string ......................................10, 59 string de formatao ................... 11 string.h ........................................ 74 strlen ........................................... 60 strreplace ................................... 93 strstr ............................................ 74 strtok ........................................... 87 struct .................................... 114

V
Variveis ........................................ 9 variveis com nomes quase iguais ................................................ 66 variveis desnecessrias .............. 14 variveis estticas........................ 72 variveis globais.................. 72, 76 varivel .......................................... 9 varivel iteradora ........................ 24 vector de caracteres ................ 59 Vectores ...................................... 57 vectores de vectores ................... 64 vectores multi-dimensionais ....... 64

W
while ............................................ 23

You might also like