Você está na página 1de 198

PROGRAMAO DE COMPUTADORES

DISCIPLINAS REGULARES

1o Mdulo:

Tcnicas de Programao

NDICE Captulo 1 ...... Introduo Linguagem C.........................2 Captulo 2 ...... Sada de Dados .........................................8 Captulo 3 ...... Tipos de Dados em C ................................14 Captulo 4 ...... Operadores e Expresses .........................24 Captulo 5 ...... Entrada de Dados ......................................34 Captulo 6 ...... Desvio Condicional ....................................39 Captulo 7 ...... Laos .........................................................49 Captulo 8 ...... Funes .....................................................59 Captulo 9 ...... Vetores.......................................................92 Captulo 10 .... Ponteiros ....................................................122 Captulo 11 .... Estruturas e Unies....................................158 Captulo 12 .... Operaes com Arquivos ...........................183

CAPTULO 1 - INTRODUO LINGUAGEM C


Um breve histrico das Linguagens de Programao
As linguagens de programao passaram por uma dramtica evoluo desde que os primeiros computadores foram desenvolvidos para auxiliar os clculos de telemetria durante a segunda guerra mundial. Nos primrdios da computao os programadores usavam a interface mais primitiva para lidar com a mquina: a linguagem de mquina, uma longa seqncia de zeros e uns que controlavam diretamente o hardware da mquina. Um pouco mais tarde foi desenvolvido o assembler para mapear instrues de mquina em uma forma mais compreensvel e de mais fcil memorizao para humanos, tais como MOV e ADD. Na seqncia do desenvolvimento surgiram as primeiras linguagens de alto nvel tais como BASIC e COBOL. Elas permitiram aos programadores trabalhar com instrues prximas a palavras e frases tais como: faa I = 100. Estas instrues eram traduzidas novamente para linguagem de mquina por interpretadores ou compiladores. Por muitos anos, o principal objetivo dos programadores foi o de escrever programas pequenos e rpidos. Os programas precisavam ser pequenos porque memria era um recurso caro e, por este motivo, limitado. Alm disso, o poder de processamento das mquinas ento disponveis era, provavelmente, muito menor do que o de uma simples calculadora de bolso com a qual estamos acostumados nos dias de hoje. No raras eram as aplicaes (que hoje seriam consideradas pequenas e simples) em que o computador processava por dias at gerar resultados teis. Por este motivo, os programadores tinham de preocupar-se com a otimizao do cdigo gerado a fim de que a aplicao executasse no menor tempo possvel. Estas prioridades se alteraram medida em que os computadores tornaram-se menores, mais rpidos e mais baratos e o custo da memria caiu. Nos dias de hoje, o custo dos programadores supera em muito o custo da maioria dos computadores usados na indstria e no comrcio. Nesse sentido, o conceito de programa bem escrito moveu-se para programas fceis de manter, isto , programas fceis de alterar ou expandir.

Resolvendo Problemas
Os problemas que os programadores de computadores vm sendo chamados a resolver vm mudando com o tempo. H 20 anos atrs os programas eram criados para processar uma grande quantidade de dados numricos. As pessoas que escreviam os programas e os usurios eram todos profissionais de computao ou engenheiros. As entradas de dados para os programas eram, freqentemente, arquivos contendo a descrio do problema e alguns comandos esotricos que selecionavam o processamento a ser aplicado aos dados. Nos dias de hoje, os programas de computador so usados por um nmero muito maior de pessoas, algumas das quais com pouco ou nenhum entendimento
2

sobre o funcionamento de computadores. Estes usurios esto mais interessados em resolver os seus problemas do que em entender o funcionamento da mquina. Ironicamente, para facilitar o uso dos programas por parte deste novo pblico, estes se tornaram muito mais complexos e sofisticados. Os usurios hoje em dia esto familiarizados com janelas, menus, caixas de dilogo, botes e uma srie de mecanismos que visam tornar mais amigvel a interface com o computador. Os programas escritos para suportar estas facilidades so muito mais complexos do que os programas escritos h 10 ou 20 anos atrs. Na medida em que as exigncias mudaram, mudaram tambm as linguagens e as tcnicas usadas para escrever programas.

Linguagens procedurais ou estruturadas


O principal conceito na programao estruturada a tcnica de dividir para conquistar. Pode-se pensar em um programa de computador como sendo constitudo por uma srie de tarefas. Qualquer tarefa que seja muito complexa para ser resolvida pode ser quebrada em um conjunto de tarefas menores at que cada uma das tarefas seja pequena e simples o suficiente para que ela possa ser compreendida e resolvida. Tomemos como exemplo o clculo do salrio mdio em uma companhia. Suponha que esta seja uma tarefa por demais complexa para resolver. No entanto, ela pode ser quebrada nas seguintes subtarefas: 1. 2. 3. 4. Descubra quanto ganha cada pessoa Conte quantos empregados voc tem Totalize os salrios Divida o total pelo nmero de empregados que voc tem.

Totalizar os salrios, por sua vez, pode ser dividido em: 1. 2. 3. 4. Acesse o registro de cada empregado Leia o campo salrio Some o salrio ao total at o momento Acesse o registro do prximo empregado

Acessar o registro de cada empregado pode, por sua vez, ser quebrado em: 1. Abra o arquivo de empregados 2. V para o registro correto 3. Leia os dados do disco. A programao estruturada continua sendo um sucesso at os dias de hoje para resolver problemas complexos. No entanto, no final da dcada de 80, algumas de suas limitaes tinham j tornado-se evidentes. Em primeiro lugar, a separao entre os dados e as funes que manipulam os dados torna difcil a compreenso e a manuteno do programa. Alm disso, os programadores, freqentemente, tm de inventar novas solues para velhos problemas. A busca de solues para estas limitaes levou ao
3

desenvolvimento das chamadas linguagens orientadas a objeto que no so, no entanto, escopo deste livro.

A Linguagem C
A linguagem C tem sido fortemente associada ao sistema operacional UNIX por ter sido desenvolvida nesse sistema e por ser o prprio UNIX escrito em C. No entanto, a linguagem no amarrada a nenhuma mquina ou sistema operacional em particular. Do mesmo modo, C no dedicada a nenhuma rea de aplicao especfica, tendo sido usada com sucesso em aplicaes numricas, processamento de texto, software bsico e banco de dados. Apesar de ser uma linguagem estruturada e modular, C uma linguagem relativamente de baixo nvel, isto , ela possibilita operaes com os dados, normalmente s disponveis em assembler, permitindo assim que o programador auxilie o compilador na tarefa de gerar cdigos bastante otimizados. As principais caractersticas da linguagem so: alto grau de portabilidade, expresses compactas, um grande conjunto de operadores, poderosas estruturas de dados e mecanismos de controle de fluxo bastante eficientes.

Um exemplo:
Seguindo a tradio. o seu primeiro programa em C ser o Al mundo! encontrado na referncia clssica de C The C programming Language de Kernighan and Ritchie. Digite o seguinte programa:

#include <stdio.h> main() /* primeiro programa */ { printf("Alo mundo!"); }

Sada:
Alo mundo!

Anlise: Este programa composto de uma nica funo chamada main( ). A funo main( ) o mdulo principal do programa e a primeira a ser chamada no incio da execuo do mesmo. Por este motivo, a funo main( ) deve estar obrigatoriamente presente em algum lugar do seu programa.
4

Os parnteses aps o nome indicam que main( ) uma funo. Toda funo em C deve ser iniciada por uma chave de abertura ({) e finalizada por uma chave de fechamento (}). As chaves em C so semelhantes ao par begin-end do Pascal. No interior das chaves encontram-se as instrues da funo. As instrues so executadas na ordem em que as escrevemos. As instrues em C so sempre encerradas por um ponto e vrgula (;). No nosso exemplo, a funo main( ) contm somente uma instruo que a chamada a uma outra funo: printf( ). Sabemos que printf( ) uma funo por causa dos parnteses que a seguem. printf( ) uma das funes de entrada/sada que podem ser usadas em C. Ela simplesmente imprime na sada padro (o terminal de vdeo, no nosso caso) os caracteres entre aspas. Vale salientar que a entrada/sada em C, ao contrrio do que acontece na maioria das outras linguagens, no efetuada atravs de comandos, mas sim processada na forma de funes que, juntas, compem a biblioteca padro. Isso se deve filosofia de portabilidade da linguagem, uma vez que as funes da biblioteca padro so dependentes da mquina e, por esse motivo, podem ser facilmente reprogramadas quando necessrio. Por este motivo, escrevemos no incio do programa a diretiva #include <stdio.h> que instrui o compilador a incluir as funes da biblioteca padro (entre as quais printf( )) no nosso programa. Nos exemplos futuros , omitiremos a diretiva #include <stdio.h>. No entanto, ela ser sempre necessria quando usarmos funes da biblioteca padro. Os delimitadores /* e */ identificam comeo e fim de comentrio. Todo texto que estiver entre eles ignorado pelo compilador C. Este tipo de comentrio chamado de comentrio estilo C. A todo /* deve existir um */ correspondente que encerra o comentrio. Um outro tipo de comentrio tambm usado em C o comentrio estilo C++ composto por uma barra dupla (//). A barra dupla instrui o compilador a ignorar tudo que se segue at o final da linha. Os comentrios estilo C++ so usados tambm em C, uma vez que eles so aceitos pela maioria dos compiladores atuais. No entanto, eles no so parte da definio oficial da linguagem.

Erros de Compilao
Erros de compilao podem acontecer por uma srie de razes. Normalmente eles so resultado de erros de datilografia ou outros pequenos erros. Os bons compiladores indicam no somente a existncia de erros no programa como tambm o lugar onde eles ocorreram. Alguns chegam mesmo a sugerir o procedimento para corrigir o erro. Vamos verificar as mensagens de erro do compilador introduzindo intencionalmente alguns erros no programa Al mundo!. Remova, por exemplo, a ltima chave do programa anterior e o recompile
//Meu primeiro programa #include <stdio.h> main() { printf("Alo mundo!");

Sada:
Compiling \MUNDO.C: Error \MUNDO.C 5: Compound statement missing }

Estilos de Programao
Em C no h um estilo obrigatrio. Desse modo, voc pode inserir espaos, caracteres de tabulao e pular linhas vontade pois o compilador ignora esses caracteres. Assim, nosso programa poderia ser escrito como:
main() { printf("Alo mundo!");

ou,
main () printf } { ("Alo mundo!");

Com o tempo voc desenvolver o seu prprio estilo de programao.

Os elementos bsicos em programao


O objetivo da maioria dos programas resolver um problema. Programas resolvem problemas manipulando informaes ou dados. De alguma forma tem de ser possvel:

Trazer as informaes a serem processadas para dentro do programa; Armazen-las em algum lugar; Dizer ao computador o que fazer com os dados; Apresentar os resultados em algum lugar para o usurio.

As suas instrues ao computador podem ser organizadas de forma que:

Algumas so executadas somente quando uma condio verdadeira;


6

Outras so repetidas um determinado nmero de vezes; Outras podem ser separadas em mdulos que podem ser executados em diferentes localizaes do seu programa.

Ns acabamos de descrever os elementos bsicos em programao: entrada de dados, variveis, operaes, sada de dados, desvio condicional, laos e funes. A maioria das linguagens de programao incorpora todas essas caractersticas. Algumas, como C, tm outras caractersticas adicionais. No entanto, quando voc deseja aprender uma nova linguagem rapidamente, em geral, suficiente aprender como a linguagem implementa os elementos acima e depois evoluir a partir deste conjunto bsico.

CAPTULO 2 - SADA DE DADOS


Pode parecer curioso comear os nossos estudos pela sada de dados, mas um programa que, de alguma forma, no externa resultados no muito til. Essa sada de dados normalmente se d atravs da tela do computador, ou atravs de um dispositivo de armazenamento de dados (discos rgidos ou flexveis) ou ainda, atravs de uma porta de entrada/sada (sadas seriais, impressoras, etc.).

A funo printf( )
No captulo anterior, voc observou um exemplo de utilizao da funo printf( ). O propsito de printf( ) permitir que os programas construdos por voc escrevam resultados na tela do computador. Seu formato ao mesmo tempo simples e flexvel:
printf("expresso de controle", arg1, arg2, ...);

Um exemplo:
main() { printf("Este e' o Capitulo %d (dois)", 2); }

Sada:
Este e' o Capitulo 2 (dois)

A expresso de controle:
A expresso de controle consiste de uma cadeia de caracteres delimitada por aspas ("como agora"). O objetivo da funo printf( ) escrever a expresso de controle na tela do computador. Antes porm, ela substitui os cdigos de formatao (iniciados por %) pelos parmetros arg1, arg2, etc., na ordem em que eles aparecem. No exemplo anterior, o cdigo de formatao %d solicita printf( ) imprimir o primeiro argumento (o nmero dois) em formato decimal. Deve haver exatamente um argumento para cada cdigo de formatao existente na expresso de controle. Os argumentos podem ser variveis, constantes, expresses, chamadas de funes, ou qualquer coisa que fornea um valor compatvel com o cdigo de formatao correspondente.

O cdigo %d no exemplo anterior diz que o argumento correspondente deve ser um inteiro decimal. A seguir, encontram-se alguns outros cdigos de formatao habitualmente usados: %u (inteiro decimal sem sinal) %c (caracter simples) %d (inteiro decimal com sinal) %e (real em notao cientfica) %f (real em ponto flutuante) %s (cadeia de caracteres) %x (inteiro em base hexadecimal)
%o (inteiro em base octal)

Imprimindo cadeia de caracteres


Vamos estudar mais um dos cdigos de formatao atravs do seguinte exemplo:
main() { printf("%s | Nucleo\n", "NCE"); printf(" | de Computacao\n }

| Eletronica");

Sada:
NCE | Nucleo | de Computacao | Eletronica

Anlise: O \n no um cdigo de formatao. Ele um caracter especial que informa printf( ) que o restante da impresso deve ser feito em nova linha. Observe no exemplo anterior: Que se no houver nenhum cdigo de formatao na expresso de controle, nenhum argumento passado funo printf( ), alm da prpria expresso de controle. Que a funo printf( ) no muda de linha automaticamente ao final da impresso da expresso de controle. Se voc quiser mudar de linha deve inserir explicitamente um caracter \n.

Os caracteres, tais como o \n (caracter de mudana de linha), que no podem ser obtidos diretamente do teclado, so escritos em C como a combinao do sinal \ (barra invertida) com outros caracteres. A tabela seguinte mostra outros cdigos de C para tais caracteres: \n \r \t \b \f \0 nova linha (LF - 0x0a) retorno de carro (CR - 0x0d) tabulao (TAB - 0x09) back space (BS - 0x08) pula pgina (FF - 0x0c) caracter nulo

\xhh insere o caracter representado pelo cdigo ASCII hh, onde hh representa o cdigo do caracter em notao hexadecimal \nnn representao de um byte em base octal

Formatao dos resultados de sada


Muitas vezes desejamos apresentar os resultados de uma forma organizada em colunas, com ttulos e legendas. Para isto necessrio definir o tamanho dos campos onde os dados sero escritos. Este parmetro adicional pode ser usado juntamente com o cdigo de formatao de cada argumento a ser impresso, e diz quantos caracteres devem ser reservados para a impresso deste dado. No caso geral temos:
%[-][tamanho][.preciso]{d,o,x,u,c,s,e,f}

os itens entre [ ] so opcionais e as letras entre { } representam o tipo do dado sendo impresso (decimal, octal, etc.) e apenas uma deve ser escolhida. No caso geral, os valores so escritos em um campo de largura mnima [tamanho] alinhados pela direita, isto , precedidos de um nmero suficiente de brancos. Por exemplo, a seqncia de comandos
printf("123456789012345678901234567890\n"); printf("%10s%10c%10s\n", "Ano", ' ', "Valor"); printf("%9d%11c%10d\n", 1, ' ', 1000); printf("%9d%11c%10d\n", 2, ' ', 2560); printf("%9d%11c%10d\n", 3, ' ', 6553);

deve gerar uma tela de sada com o seguinte formato:


10

123456789012345678901234567890 Ano Valor 1 1000 2 2560 3 6553

Nos casos em que a expresso do tipo real, o parmetro [.preciso] define com quantas casas decimais o nmero deve ser escrito. Exemplo:
printf("1234567890\n"); printf("%4.2f\n", 3456.78); printf("%3.2f\n", 3456.78); printf("%3.1f\n", 3456.78); printf("%10.3f\n", 3456.78);

Sada:
1234567890 3456.78 3456.78 3456.8 3456.780

O sinal de menos [-] precedendo a especificao do tamanho do campo justifica os campos esquerda, como mostra o prximo exemplo:
printf("123456789012345678901234567890\n"); printf("%-10s%10c%-10s\n", "Ano", ' ', "Valor"); printf("%-9d%11c%-10d\n", 1, ' ', 1000); printf("%-9d%11c%-10d\n", 2, ' ', 2560); printf("%-9d%11c%-10d\n", 3, ' ', 6553);

Sada:
123456789012345678901234567890 Ano Valor 1 1000 2 2560 3 6553

Alm de especificar o tamanho do campo, podemos preencher o campo com zeros esquerda. Para isto, devemos especificar o parmetro [tamanho] precedido de um zero. Observe o exemplo a seguir:
11

printf("1234567890"); printf("\n%04d", 21); printf("\n%06d", 21);

Sada:
1234567890 0021 000021

Outras Funes de Sada: puts( ) e putchar( )


Existem duas outras funes para a sada de dados que podem ser teis na construo de programas: a funo puts( ) que imprime uma string na tela do computador e a funo putchar( ) que imprime um nico caracter.

A funo puts( )

<STDIO.H>

puts imprime uma string em stdout (e insere um caracter de nova linha ao final). O endereo da string deve ser passado para puts() como argumento Declarao:
int puts(const char *s);

Valor de Retorno: Em caso de sucesso, puts() retorna um valor no negativo. Em caso de erro, puts() retorna o valor de EOF.

Exemplo:
puts("NCE | Nucleo"); puts(" | de Computacao"); puts(" | Eletronica");

Sada: NCE | Nucleo | de Computacao | Eletrnica

12

Observe que no foi acrescentado o caracter de nova linha (\n) ao final de cada string a ser impressa. Isto no necessrio uma vez que puts( ) automaticamente muda de linha ao final da impresso. Assim, as duas instrues seguintes so equivalentes:
puts("string"); printf("%s\n", "string");

A funo putchar( )

<STDIO.H>

putchar() uma macro que escreve um caracter em stdout.

Declarao:
int putchar(int ch);

Valor de Retorno: Em caso de sucesso, putchar() retorna o caracter ch Em caso de erro, putchar() retorna EOF.

As duas instrues seguintes so equivalentes:


putchar('c'); printf("%c", 'c');

Voc deve estar se perguntando porque usar puts( ) ou putchar( ) ao invs de printf( ). Uma boa razo que a rotina printf( ) muito grande. Dessa forma, a menos que voc realmente precise de printf( ) (para sadas numricas por exemplo), voc pode ter um programa muito menor e de execuo muito mais rpida usando puts( ) e putchar( ).

13

CAPTULO 3 - Tipos de dados em C


Quando voc escreve um programa, voc est lidando com algum tipo de informao. Esta informao pode ser classificada, na maioria dos casos, em 4 grandes grupos: inteiros, nmeros em ponto flutuante, texto e ponteiros. Inteiros so os nmeros que usamos para contar ou enumerar um conjunto (1, 2, -56,
735, por exemplo)

Nmeros em ponto flutuante so nmeros que tm uma parte fracionria (3.1415) e/ou um expoente (1.0E+24). So tambm chamados de nmeros reais. Texto composto de caracteres ('a', 'f', '%') e cadeia de caracteres ("Isto e' uma cadeia de caracteres") Ponteiros no contm informao propriamente dita. Eles contm o endereo de memria onde est armazenada a informao de interesse do programa. As informaes manipuladas pelo programa, tambm chamadas de dados, podem ser constantes ou variveis, como veremos a seguir.

Constantes e Variveis
Constantes As constantes tm valor fixo e inaltervel. Nos exemplos do Captulo anterior, mostramos o uso de constantes numricas e constantes cadeia de caracteres.
printf("Este e o Capitulo %d (dois)", 2); printf("%s | Nucleo\n", "NCE");

Os tipos de constantes em C so: a) Constantes caracteres Uma constante caracter um nico caracter escrito entre plicas (') o qual pode participar normalmente de expresses aritmticas. O seu valor o correspondente ao seu cdigo ASCII. Como exemplo temos:

14

'a' 'A' '\'' '\\' '\n' '\0' '\033'

(caracter a) (caracter A) (caracter ') (caracter \) (caracter mudana de linha) (caracter nulo) (caracter escape em octal)

b) Constantes cadeias de caracteres Alm de caracteres isolados podemos representar cadeias de caracteres como uma seqncia de zero ou mais caracteres entre aspas. Exemplos:
"NCE" "Este e' o Captulo 3"

c) Constantes inteiras So valores numricos sem ponto decimal, precedidos ou no de sinal. Se uma constante inteira comea com 0x ou 0X ela hexadecimal. Se ela comea com o dgito 0, ela octal. Em caso contrrio ela uma constante decimal. As declaraes seguintes so equivalentes:
28 0x1C 034 1982 -76 // decimal 28 // representao hexadecimal do decimal 28 // representao octal do decimal 28

Outros exemplos:
// Constantes Decimais 10 132 32179 // Constantes Octais 077 010 03777 // Constantes Hexadecimais 0xFFFF 0x7db3 0x10

d) Constantes inteiras longas


15

So constantes armazenadas com o dobro do nmero de bytes das constantes inteiras1 capazes, por este motivo, de armazenar valores maiores. Para diferenci-las das constantes inteiras, os valores longos tm como sufixo a letra L ou l. Alguns exemplos de constantes inteiras longas:
// Constantes decimais longas 10L 79L // Constantes octais longas 012L 0115L // Constantes hexadecimais longas 0xaL ou 0xAL 0x74fL ou 0x4FL

g) Constantes em ponto flutuante Constantes com ponto decimal, precedidas ou no do sinal negativo, e seguidas ou no da notao exponencial.
3.1415926 3.14e0 -10.0e-02 100.0F 100.0L

Uma constante em ponto flutuante sem os sufixos L, l, F ou f tem o tipo double (8 bytes). Se o sufixo for F ou f, a constante do tipo float (4 bytes). Se o sufixo for L ou l a constante do tipo long double (10 bytes no compilador Borland C, 8 bytes no compilador gcc em ambiente UNIX).

Constantes Enumeradas As constantes enumeradas permitem a criao de novos tipos e a declarao de variveis pertencentes a estes tipos cujos valores esto restritos a um conjunto de valores possveis. Pode-se por exemplo declarar COR como uma enumerao e definir que COR pode assumir 5 valores diferentes: VERMELHO, AZUL, VERDE, BRANCO e PRETO.

Esta informao verdadeira para o compilador Borland C rodando em PCs. Para outros compiladores a afirmao pode no ser correta. Para o compilador gcc, por exemplo, rodando em ambientes UNIX, as constantes inteiras e as constantes longas tm o mesmo tamanho em bytes.

16

A sintaxe para constantes enumeradas consiste na palavra reservada enum seguida pelo nome do tipo e, em seguida, a lista dos valores possveis, entre chaves e separados por vrgulas. Exemplo:
enum COR {VERMELHO, AZUL, VERDE, BRANCO, PRETO}

A declarao acima tem dois efeitos: Ela cria um novo tipo, COR, que consiste em uma enumerao. Ela cria uma constante simblica VERMELHO com o valor 0, uma constante simblica AZUL com o valor 1, VERDE com o valor 2 e assim por diante.

Toda constante enumerada tem um valor inteiro. Se nada for especificado, a primeira constante tem o valor 0, a segunda tem o valor 1 e as demais seguem seqencialmente a partir da. No entanto, qualquer das constantes pode ser inicializada com um valor em particular, e aquelas que no forem inicializadas tm o seu valor obtido pelo incremento das constantes anteriores. Exemplo:
enum COR {VERMELHO=100, AZUL, VERDE=500, BRANCO, PRETO=700 };

VERMELHO tem o valor 100, AZUL o valor 101, VERDE o valor 500, BRANCO o valor 501 e PRETO o valor 700. Voc pode declarar variveis do tipo COR, e estas variveis deveriam assumir somente valores correspondentes a uma das constantes enumeradas (no caso do exemplo, VERMELHO, AZUL, VERDE, BRANCO ou PRETO). Voc pode atribuir qualquer uma das cores a uma varivel do tipo COR. Na verdade, voc pode atribuir qualquer nmero inteiro a uma varivel do tipo COR, ainda que este nmero no corresponda a uma cor vlida (ainda que um bom compilador deva emitir uma mensagem de advertncia se voc fizer esta atribuio). importante entender que as variveis enumeradas so, de fato, variveis unsigned int e que as constantes enumeradas correspondem a constantes inteiras. , no entanto, muito conveniente poder dar nomes a estas constantes quando se est trabalhando com cores, dias da semana ou conjuntos similares de valores. O programa abaixo mostra um exemplo do uso de constantes enumeradas.

17

1. enum Dias {Seg, Ter, Qua, Qui, Sex, Sab, Dom}; 2. void main() 3. Dias Folga; 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.} {

puts("Que dia voce quer sua folga (0-6)?"); puts("0 - Segunda"); puts("1 - Terca"); puts("2 - Quarta"); puts("3 - Quinta"); puts("4 - Sexta"); puts("5 - Sabado"); puts("6 - Domingo"); printf("Sua escolha: "); scanf("%d", &Folga); if (Folga==Sab || Folga==Dom) puts("Voce nao trabalha nos fins de semana!"); else puts("Ok, vou anotar sua folga.");

Anlise A constante enumerada Dias definida na linha 1. O usurio escolhe um dia de folga na linha 13. O valor escolhido, um nmero entre 0 e 6, comparado na linha 14 com as constantes enumeradas para Sbado e Domingo e a ao apropriada tomada. Voc no pode digitar a palavra Sab quando o programa pede o dia de folga. O programa no sabe como associar os caracteres em Sab a uma das constantes enumeradas.

Variveis As variveis so o aspecto fundamental de qualquer linguagem de programao. Uma varivel um espao de memria reservado para armazenar um certo tipo de dado e tendo um nome para referenciar o seu contedo, o qual, ao contrrio das constantes, pode variar ao longo da execuo do programa. Vamos estudar o seguinte exemplo:
1. 2. 3. 4. 5. 6. 7. main() { int count; count = 2; printf("Este e' o numero dois: %d\n", count); count = 3; printf("e este e' o numero tres: %d", count); }

Sada:
Este e' o numero dois: 2 e este e' o numero tres: 3

18

Anlise: A declarao na linha 2 um exemplo de declarao de varivel. Ela instrui o compilador a reservar espao em memria, suficiente para armazenar uma varivel do tipo inteiro. Alm disso, qualquer referncia futura varivel cout, no corpo do programa, acessar esta mesma posio de memria. Em C todas as variveis devem ser declaradas. Se voc tiver mais de uma varivel do mesmo tipo, poder declar-las de uma nica vez, separando o seu nome por vrgulas. Exemplo:
int bananas, laranjas, peras;

A instruo na linha 3 do nosso programa exemplo atribui o valor 2 varivel count, isto , em tempo de execuo, o inteiro 2 armazenado na posio de memria reservada pelo compilador para a varivel count. A instruo seguinte printf( ) j nossa conhecida. Ela imprime o contedo da varivel count na tela do computador. A instruo na linha 5 altera o contedo da varivel count, a qual novamente impressa pela instruo printf( ) seguinte.

Tipos de Variveis
Como foi visto anteriormente, os dados manipulados por um programa podem ser classificados em quatro grandes grupos: inteiros, nmeros em ponto flutuante, texto (caracteres e cadeia de caracteres) e ponteiros. Cada um desses tipos ser visto com mais detalhes a seguir.

Variveis ponto flutuante Observe o seguinte exemplo:


main() { int a, b; float ratio; a = 10; b = 3; ratio = a/b; printf("O resultado da divisao e': %f", ratio); }

Sada:
3.000000

19

Voc provavelmente esperava que o resultado da diviso desse 3.333333. Por que o resultado encontrado? Porque a e b so inteiros. A diviso efetuada entre dois nmeros inteiros obtendo um resultado inteiro (3) o qual posteriormente convertido para float e atribudo varivel ratio. Rode o programa no seu computador trocando o tipo das variveis a e b para float e observe que agora obtm-se o resultado esperado. Os nmeros em ponto flutuante so representados internamente na forma:
[+/-] [mantissa] E [expoente],

e ocupam 4 bytes de memria no compilador Borland C: 1 para o expoente e 3 para o sinal e mantissa. Existe ainda um tipo estendido de variveis em ponto flutuante, o tipo double que, como o nome indica, ocupa o dobro de bytes na memria. Isto significa que a mantissa possui mais algarismos significativos do que no tipo float e que o expoente pode variar numa faixa maior.

Variveis inteiras Em adio ao tipo int, C suporta tambm os tipos short int e long int, geralmente abreviados como short e long. O nmero de bytes ocupados por cada um desses tipos depende do compilador e da mquina usados, mas, no Compilador da Borland para PCs, eles ocupam 2 bytes (short), 2 bytes (int) e 4 bytes (long)

Variveis do tipo char Ocupam 1 byte na memria. Assim como as constantes caracteres, as variveis do tipo char tambm podem participar normalmente de expresses aritmticas. O valor armazenado no byte de memria corresponde ao cdigo ASCII do caracter representado. Assim, as atribuies abaixo so todas equivalentes:
char ch ch ch ch ch = = = = = ch; 'a'; 97; 0x61; 0141; '\x61'; /* /* /* /* /* caracter a */ codigo ASCII em decimal */ codigo ASCII em hexa */ codigo ASCII em octal */ caracter cujo codigo ASCII e' 0x61 */

Variveis sem sinal C permite que voc declare certos tipos de variveis ( char, short, int, long) com o modificador unsigned. Isto quer dizer que as variveis declaradas com este modificador podem assumir somente valores no negativos.
20

Variveis sem sinal podem assumir valores maiores do que variveis com sinal. Por exemplo, uma varivel do tipo short pode assumir, no compilador Borland C, valores entre -32768 e 32767 enquanto que uma varivel unsigned short pode conter valores entre 0 e 65535. Ambas ocupam o mesmo espao de memria. Elas apenas o usam de maneira diferente. Observe o seguinte exemplo:
1. 2. 3. 4. 5. 6. 7. main() { unsigned short j; short i; j=65000; i=j; printf("i = %d\nj = %u", i, j); }

Sada:
i = -536 j = 65000

Anlise: Na linha 4, quando o valor 65000 atribudo varivel i, o compilador interpreta este valor como um nmero negativo. (65000 a representao em complemento a 2 de -536)

Ponteiros e Cadeia de caracteres Em C no existe um tipo string como o do Turbo Pascal para manipular cadeias de caracteres. No entanto, a linguagem oferece duas maneiras diferentes de lidar com strings. A primeira delas declarar a cadeia de caracteres como um vetor de caracteres:
char cadeia[80];

Esta declarao aloca 80 bytes consecutivos de memria para armazenar a cadeia de caracteres. Cada byte armazena o cdigo ASCII de um caracter da cadeia. A segunda forma declarar um ponteiro para uma rea de memria onde sero armazenados os caracteres da string.
char *ptr;

O asterisco (*) na frente de ptr informa ao compilador que ptr um ponteiro para um caracter, ou, em outras palavras, ptr contm um endereo de memria, onde est armazenado um caracter. O compilador, no entanto, no aloca espao para a cadeia de caracteres, nem inicializa ptr com nenhum valor em particular.
21

O estudo mais detalhado de cadeias de caracteres ser visto mais adiante, quando estudarmos ponteiros e vetores.

Inicializando Variveis
possvel combinar uma declarao de varivel com o operador de atribuio (=) para que a varivel seja criada em memria e o seu contedo inicializado antes do incio da execuo do programa propriamente dito. Observe o programa abaixo:
1. 2. 3. 4. 5. main() { short short unsigned short char printf("%d\n", printf("%d\n", printf("%o\n", printf("%o\n",

count=5, sx=-8; ux=-8; ch='5'; count); ch); sx); ux);

6. 7. 8. 9. 10. }

Sada:
5 53 177770 177770

Anlise: Nas linhas 2 e 3 as variveis count e sx so inicializadas durante a declarao das mesmas com os valores 5 e -8 (0xFFF8) respectivamente. Na linha 4 a varivel unsigned ux inicializada com o valor -8, o qual interpretado pelo compilador como o inteiro positivo 65528 (0xFFF8). Na linha 5 a varivel do tipo char ch inicializada com o cdigo ASCII do caracter 5 (53). Observe que o contedo das posies de memria ocupadas por sx e ux o mesmo. A diferena consiste em que, possivelmente, este contedo ser interpretado de maneira diferente pelo compilador quando as variveis forem acessadas uma vez que sx uma varivel com sinal e ux uma varivel unsigned. Nas linhas 6-9 os contedos das variveis so impressos. Observe que a sada ocasionada pelas linhas 8 e 9 a mesma uma vez que em ambas as instrues as variveis so interpretadas como um nmero na base octal.

Nomes de Variveis
22

Existem algumas regras em C que devem ser seguidas para dar nome s variveis: a) Todos os identificadores devem comear com uma letra (a..z, A..Z) ou um underscore (_). b) O restante do identificador pode conter letras, underscores ou dgitos. c) Em C, letras maisculas e minsculas so diferentes. Por exemplo, os identificadores count, Count e COUNT, so tratadas como variveis distintas pelo compilador.

Alm das regras de sintaxe, devemos escolher nomes para as variveis que indiquem o contedo armazenado por estas variveis. Assim, por exemplo, uma varivel que armazene o salrio de um empregado deve se chamar salrio, e no xpto.

23

CAPTULO 4 - Operadores e expresses


Uma vez que as variveis do seu programa contm os dados a serem processados, o que fazer com elas? Provavelmente combin-las em expresses envolvendo operadores. Esse ser o assunto desse captulo.

Operadores e Expresses
As expresses combinam operandos e operadores para produzir um nico resultado. Os operandos podem ser constantes, variveis ou valores fornecidos por funes. Por exemplo, se A = 3 e B = 8 ento a expresso (A + B) d como resultado o valor 11. O resultado de uma expresso tambm constitui um tipo que, em geral, o mesmo dos operandos envolvidos nesta expresso.

Precedncia de operadores
As expresses so avaliadas de acordo com o que se chama de precedncia dos operadores. A introduo de operadores com precedncias distintas altera a ordem de avaliao da expresso, sendo que os de maior precedncia sero avaliados antes dos de menor. Exemplo: A + B2C D 1* 3 1 24 4 13 2 14 244 4 3
3

a seguinte a precedncia dos operadores em C: Os operadores unrios (categoria #2), condicionais (categoria #14), e de atribuio (categoria #15) associam os operandos da direita para a esquerda; todos os demais operadores associam os operandos da esquerda para a direita.

24

Categoria
1. Prioridade mais alta

Operador
( ) [ ] .

O que ele (ou faz)


Chamada de funo ndice de vetor Acesso indireto a membro de estrutura Acesso direto a membro de estrutura Negao Lgica (NOT) Negao bit a bit Pr-incremento ou Ps-incremento Pr-decremento ou Ps-decremento Endereo Indireo tamanho do operando em bytes Multiplicao Diviso Resto da diviso inteira Soma binria Subtrao binria Deslocamento esquerda Deslocamento direita Menor do que Menor ou igual Maior do que Maior ou igual Igual a Diferente AND bit a bit XOR bit a bit OR bit a bit AND lgico
25

2. Unrios

! ~ ++ -& * sizeof

4. Multiplicativos

* / %

5. Aditivos

+ -

6. Deslocamento

<< >>

7. Relacionais

< <= > >=

8. Igualdade

== !=

9. 10. 11. 12.

& ^ | &&

13. 14. Condicional 15. Atribuio

|| ?: = *= /= %= += = &= = |= <<= >>=

OR lgico a ? x : y (se a ento x, seno y) Atribuio simples Produto e atribuio Diviso e atribuio Resto da diviso inteira e atribuio Soma e atribuio Subtrao e atribuio AND bit a bit e atribuio XOR bit a bit e atribuio OR bit a bit e atribuio Deslocamento esquerda e atribuio Deslocamento direita e atribuio Conectivo de expresses

16. Vrgula

Os operadores
O operador de atribuio: O operador mais bsico em C o operador de atribuio:
soma = a + b; fator = 3.0;

o valor da expresso direita atribudo varivel esquerda. Em C pode-se fazer vrias atribuies em uma nica linha de cdigo:
a = b = c = 3.0;

aps a atribuio, as trs variveis teriam o mesmo valor (3.0).

Os operadores aritmticos:
26

C suporta o conjunto usual dos operadores aritmticos: * (multiplicao) / (diviso) % (mdulo) + (adio) (subtrao)

O operador mdulo (resto da diviso inteira) no pode ser aplicado a variveis do tipo float nem double. possvel incluir expresses envolvendo operadores aritmticos (ou qualquer outro tipo de operadores) diretamente em printf( ). Exemplo:
main() { int i=3; int j=4; printf("Soma = %d\n", i+j); }

Em C, qualquer atribuio entre parnteses considerada como uma expresso que tem o valor da atribuio sendo feita. Exemplo:
a = 3+2*(b=7/2);

uma construo vlida, equivalente a:


b = 7/2; a = 3+2*b;

Operadores de incremento e decremento: A linguagem C possui alguns operadores que permitem escrever uma instruo de forma muito compacta, gerando, assim, um cdigo bastante otimizado. Um exemplo de tais operadores so os operadores unrios de incremento (++) e decremento (). Tais operadores incrementam ou decrementam os seus operandos de 1 unidade. A adio ou subtrao pode ser efetuada no meio de uma expresso e pode-se mesmo escolher se o

27

incremento/decremento ser feito antes ou depois do clculo da expresso. Sejam os seguintes exemplos:
m = 3.0 * n++; m = 3.0 * ++n;

A primeira instruo diz: Multiplique n por 3.0; atribua o resultado a m e ento incremente n de 1 unidade. A segunda diz: Incremente n de 1 unidade; multiplique n por 3.0 e atribua o resultado a m. Os operadores de incremento/decremento podem aparecer sozinhos em uma instruo:
i++; ++i;

Nesse caso indiferente se o operador colocado antes ou depois do operando. As instrues anteriores so equivalentes a:
i = i + 1;

Operadores em bits: Para operaes ao nvel de bits, C dispe dos seguintes operadores: & (AND lgico) uso: op1 & op2; descrio: feito um AND lgico dos bits correspondentes de op1 e op2 usando a seguinte tabela verdade: op1 0 0 1 1 exemplo: 0x32a & 0xa3b4 = 0x320 op2 0 1 0 1 & 0 0 0 1

| (ou lgico) uso: op1 | op2; descrio: feito um ou lgico dos bits correspondentes de op1 e op2 usando a seguinte tabela verdade:
28

op1 0 0 1 1 exemplo: 0x32a | 0xa3b4 = 0xa3be

op2 0 1 0 1

| 0 1 1 1

(ou exclusivo) uso: op1 op2; descrio: feito um ou exclusivo entre os bits correspondentes de op1 e op2 usando a seguinte tabela verdade: op1 0 0 1 1 exemplo: 0x32a 0xa3b4 = 0xa09e op2 0 1 0 1 0 1 1 0

(negao ou complemento) uso: op1; descrio: todos os bits de op1 so invertidos segundo a seguinte tabela verdade: op1 0 1 exemplo: 0x32a = 0xfcd5 1 0

< < (deslocamento esquerda) uso: op1 << k; descrio: os bits de op1 so deslocados k bits esquerda. exemplo: 0x0010 << 2 = 0x0040

> > (deslocamento direita) uso: op1 >> k; descrio: os bits de op1 so deslocados k bits direita.
29

exemplo:

0x0010 >> 2 = 0x0004

Observaes: a) Nos deslocamentos direita em variveis unsigned e nos deslocamentos esquerda, os ltimos bits contrrios ao deslocamento so preenchidos com zeros. b) Nos deslocamentos direita em variveis signed, os ltimos bits contrrios ao deslocamento so preenchidos com o valor do bit mais significativo da varivel. c) Um deslocamento de um bit direita equivalente a uma diviso por 2. Da mesma forma, deslocar um bit esquerda equivalente a uma multiplicao por 2. Assim, as instrues seguintes so equivalentes:
a = a * 16; a = a << 4;

Voc deve estar se perguntando porque ento usar os operadores de deslocamento. Isto se deve ao fato de que um deslocamento direita ou esquerda uma operao rapidssima existente, em geral, no assembly da mquina. Por outro lado, multiplicaes ou divises de inteiros so instrues complexas que demandam vrios ciclos de mquina para serem executadas. d) Os operadores de deslocamento de bits no tm sentido se aplicados em operandos do tipo float ou double.

Operadores de atribuio composta: Em C, qualquer expresso da forma:


<varivel> = <varivel> <operando> <expresso>;

pode ser compactada na forma:


<varivel> <operando>= <expresso>;

A seguir, encontram-se alguns exemplos de tais expresses e como elas podem ser compactadas:
a = a + b; a = a - b; a = a * b; a += b; a -= b; a *= b;

30

a = a / b; a = a % b; a = a << b; a = a >> b; a = a & b; a = a | b; a = a ^ b;

a /= b; a %= b; a <<= b; a >>= b; a &= b; a |= b; a ^= b;

Expresses usando operadores de atribuio composta so mais compactas e, normalmente, geram um cdigo mais eficiente.

Operadores de endereo: C suporta dois operadores que lidam com endereos: o operador devolve endereo (&), e o operador de acesso indireto (*). A expresso &varivel retorna o endereo do primeiro byte onde varivel est armazenada. O programa seguinte imprime o contedo e o endereo da varivel inteira x.
main() { int x=2; printf("valor = %d\nendereco = %u", x, &x); }

Um endereo de memria tratado como um inteiro sem sinal. A sada desse programa varia conforme a mquina e o endereo de memria onde o programa carregado. Uma sada possvel :
valor = 2 endereco = 1370

A expresso *ptr devolve o contedo da posio de memria apontada por ptr. Este operador ser visto com mais detalhes quando estudarmos ponteiros.

Converso de tipos
31

Alm da prioridade dos operadores, quando avaliarmos uma expresso devemos levar em conta tambm a converso de tipos que se d quando os operadores so de tipos diferentes. Esta converso segue algumas regras bsicas: a) Expresses envolvendo variveis char e short so sempre convertidas para int. b) A converso de tipos obedece seguinte precedncia entre os operandos:
double float long unsigned int, char ou short

Isto , se, por exemplo, um dos operandos for do tipo double, toda a expresso ser convertida e o resultado ser do tipo double.

Converso forada de tipos


possvel em C forar a mudana do tipo de uma expresso ou varivel. Para isto, a expresso (varivel) precedida pelo tipo desejado, escrito entre parnteses. Exemplo:
1. 2. 3. 4. main() { int a=10; int b=3; float c; c=a/b; printf(Resultado = %f\n, c); c=(float)a/b; printf(Resultado = %f\n, c);

5. 6. 7. 8. 9. }

Sada:
Resultado = 3.000000 Resultado = 3.333333

Anlise: Na linha 5 a expresso inteira a/b avaliada e produz um resultado inteiro (3) o qual , posteriormente, convertido para float e armazenado na varivel c. Na linha 7, atravs da converso forada de tipo, a expresso convertida para uma expresso em ponto

32

flutuante a qual avaliada e produz o resultado correto (3.333333) que armazenado na varivel c.

33

CAPTULO 5 - Entrada de dados


At agora, em todos os nossos exemplos, as variveis foram inicializadas no prprio programa. Neste captulo, iremos estudar algumas formas de entrada de dados a partir do teclado.

A funo scanf( )
Para entrada de dados a partir do teclado voc, na maior parte das vezes, utilizar a funo scanf( ). A funo scanf( ) equivalente funo printf( ). Seu formato :
scanf("expresso de controle", endereo1, endereo2, ...);

A funo scanf( ) l uma seqncia de campos de entrada (um caracter por vez), formata cada campo de acordo com um padro de formatao passado na string expresso de controle e armazena a entrada formatada em um endereo passado como argumento, seguindo a string expresso de controle. scanf( ) usa a maioria dos cdigos de formatao utilizados por printf( ). Dessa forma, usa-se %d quando se quer ler um inteiro, %f para nmeros em ponto flutuante, %c para caracteres, etc. Espaos em branco na expresso de controle so ignorados. Existe uma diferena fundamental, no entanto, entre scanf( ) e printf( ): os itens seguindo a expresso de controle so os endereos das variveis que vo receber os valores lidos e no, como em printf( ), as prprias variveis. A explicao para esta distino ficar clara posteriormente, quando estudarmos ponteiros, funes e passagem de parmetros por referncia. Por ora, suficiente ter em mente esta distino. A falta do operador de endereo (&) antes do nome das variveis a serem lidas no acarreta em erro de compilao. A execuo de tal programa, no entanto, ter resultados imprevisveis. A funo scanf( ) retorna o nmero de parmetros lidos, convertidos e armazenados com sucesso.

Exemplo:
1. 2. 3. 4. main() { int a; int b; int soma; scanf("%d %d", &a, &b); soma = a + b; printf("Soma = %d\n", soma);

5. 6. 7. 8. }

34

Anlise: A instruo na linha 5 faz com que o programa pare a execuo e espere voc digitar dois nmeros inteiros. Os nmeros podem ser separados por um ou mais espaos em branco, tabs, ou enters . O primeiro nmero lido atribudo varivel a, e o segundo varivel b. Mas, e se quisermos que os valores de a e b sejam digitados separados por v rgulas? Nesse caso teramos de modificar a chamada scanf( ) que ficaria:
scanf("%d,%d", &a, &b);

Deve existir uma exata correspondncia entre os caracteres diferentes de branco existentes na expresso de controle e a seqncia digitada via teclado. Por exemplo, se quisermos entrar com os valores a = 3 e b = 5:
3,5 3.5 3, 5 3 ,5 correto errado correto errado

Outro erro freqente consiste na tentativa de se limitar o tamanho dos campos de entrada quando da leitura das variveis:
scanf(%10.5f, &f); // errado

A limitao do tamanho dos campos dos dados s tem sentido quando do uso de funes de sada de dados. Assim, a forma correta da instruo acima seria, por exemplo:
scanf(%f, &f); printf(%10.5f, f);

A funo scanf( ) interrompe sua execuo quando todos os valores so lidos, ou quando a entrada no combina com a expresso de controle.

Lendo Strings
Vamos nos antecipar um pouco ao estudo de vetores (como foi visto no Captulo 3, uma string em C um vetor de caracteres) e ver como podemos ler uma string do teclado. Ler uma string consiste de dois passos: reservar espao de memria para armazen-la e usar alguma funo que permita a sua leitura. O primeiro passo declarar a string especificando o seu tamanho. Por exemplo:

35

char nome[30];

Esta declarao aloca 30 bytes consecutivos de memria nos quais as letras do nome sero armazenadas, uma por byte. Uma vez reservado o espao necessrio voc pode usar as funes scanf( ) ou gets( ) da biblioteca C para ler a string.

Lendo strings com a funo scanf( )


Vamos estudar a leitura de strings usando scanf( ) atravs de um exemplo:
main() { char nome[30]; printf("Digite o seu nome : "); scanf("%s", nome); printf("Como vai %s?\n", nome); }

Sada:
Digite o seu nome : Carlos da Silva Como vai Carlos?

Anlise: Observe que no foi usado o operador de endereo (&) antes de nome. Isto ocorreu porque em C o nome de um vetor corresponde ao endereo do primeiro byte do vetor. Se voc no quiser usar essa elegante caracterstica da linguagem C, poder escrever (o que absolutamente no recomendamos):
scanf("%s", &nome[0]);

Observe que o programa s usou o primeiro nome na sua saudao. Isto ocorre por que a funo scanf( ) interpreta o branco aps o Carlos, como um sinalizador de final de string. Mas, e se quisssemos ler uma string com brancos entre os nomes? Nesse caso, a soluo mais recomendada usar a funo gets( ) que ser vista a seguir.

A funo gets( )
Vamos reescrever o exemplo anterior usando gets( ):

36

main() { char nome[30]; printf("Digite o seu nome : "); gets(nome); printf("Como vai %s?\n", nome); }

Sada:
Digite o seu nome : Carlos da Silva Como vai Carlos da Silva?

Anlise: A funo gets( ) l tudo o que voc digita at que voc pressione Enter. O caracter Enter no acrescentado string.

A funo getchar( )
A biblioteca C dispe de funes dedicadas leitura de caracteres. Estas funes, menores e mais rpidas do que scanf( ) so tambm de mais fcil utilizao. Uma das funes pertencentes a esta classe a funo getchar( )2. A seqncia de caracteres digitados pelo usurio bufferizada (a entrada de dados termina quando o caracter ENTER pressionado) e, a cada chamada de getchar( ), o prximo caracter no buffer lido (incluindo o ENTER ou \n). Em caso de sucesso getchar( ) retorna o caracter lido. Em caso de erro getchar( ) retorna EOF (caracter End Of File). A biblioteca C dispe de outras funes (getch( ) e getche( ), por exemplo) que lem um caracter no instante em que ele digitado, sem a necessidade do ENTER para concluir a leitura 3. Estas funes por no estarem disponveis em todas as verses dos compiladores C, no sero vistas aqui. Exemplo de utilizao de getchar( ):
main() { char ch; printf("Digite algum caracter : "); ch = getchar();
2

Em verdade, getchar( ) uma macro declarada em <stdio.h>. A distino entre macros e funes ser vista mais adiante. 3 Em verdade, estas funes esto disponveis apenas em compiladores C para mquinas do tipo PC tais como o compilador da Borland e o Microsoft C. Em mquinas do tipo RISC, para que os caracteres digitados pelo usurio sejam imediatamente disponveis para o programa, necessrio combinar a utilizao de getchar( ) com funes da biblioteca curses (libcurses.a).

37

printf("O caracter que voce digitou foi o %c.", ch); }

Sada:
Digite algum caracter : x O caracter que voce digitou foi o x.

Anlise: Observe que a funo getchar( ) no aceita argumentos. Ela devolve o caracter lido para a funo que a chamou. No nosso exemplo, esse caracter foi atribudo varivel ch.

38

CAPTULO 6 - Desvio condicional


As instrues de desvio condicional permitem que o programa execute diferentes procedimentos baseado em decises do tipo: se uma dada condio verdadeira, fao isso; seno fao aquilo. Este tipo de deciso est presente no nosso dia a dia (Se chover, vou de carro; seno, vou de nibus, etc.) e, por isso, todas as linguagens de programao incorporam instrues de desvio condicional.

Operadores relacionais e operadores lgicos


Existem duas classes de operadores sobre as quais ainda no falamos: os operadores relacionais e os operadores lgicos. Operadores relacionais Os operadores relacionais em C so:
> >= < <= == != maior do que maior ou igual a menor do que menor ou igual a igual a diferente de

Operadores relacionais permitem que se compare dois valores e obtenha-se um resultado que dependente da comparao ser falsa ou verdadeira. Se a comparao falsa, o resultado obtido 0. Se a comparao verdadeira, o resultado obtido 1. O programa a seguir mostra expresses booleanas como argumentos da funo printf( ):
1. main() { 2. int capitulo=6; 3. 4. 5. } printf("Este e' o capitulo 5? %d\n", capitulo==5); printf("Este e' o capitulo 6? %d\n", capitulo==6);

Sada:
Este e' o capitulo 5? 0 Este e' o capitulo 6? 1

Anlise:
39

Na linha 3, a expresso booleana (capitulo==5) avaliada e o resultado da comparao (Falso ou 0) impresso por printf( ). De modo anlogo, na linha 4, o resultado da comparao (capitulo==6) impresso por printf( ).

Um dos erros mais freqentes entre iniciantes em programao C o de confundir o operador de atribuio (=) com o operador relacional (==). Observe que, se tivssemos escrito:
printf("Este e' o capitulo 5? %d\n", capitulo=5);

o compilador no retornaria nenhuma mensagem de erro, uma vez que no existe nenhum erro de sintaxe e sim um erro de lgica. A sada gerada por essa instruo seria:
Este e' o capitulo 5? 5

Operadores lgicos Os operadores lgicos em C so:


&& || ! operador lgico E operador lgico OU operador lgico NOT

Esses operadores no devem ser confundidos com os operadores em bits (&, |, ~) descritos anteriormente. Os operadores lgicos trabalham com valores lgicos (verdadeiro ou falso), permitindo que voc combine expresses relacionais. Se exp1 e exp2 so duas expresses simples, ento:
exp1 && exp2

verdadeira se ambas as expresses so verdadeiras. verdadeira se pelo menos uma das expresses for verdadeira. verdadeira se exp1 for falsa.

exp1 || exp2 !exp1

Exemplos: Seja a == 3,
(a>0) && (a<=4) (a>0) || (a==-3) Verdadeiro Verdadeiro

40

!(a==5)

Verdadeiro

Em C, uma expresso verdadeira se, quando avaliada, produzir um resultado diferente de zero. Uma expresso falsa somente quando produzir um resultado igual a zero. Exemplo: As seguintes construes so corretas e produzem os resultados assinalados.
1||2 !5 verdadeiro (1) falso (0)

O COMANDO if
Um desvio condicional usado quando se deseja escolher um dentre dois comandos ou blocos de comandos para ser executado, dependendo de uma condio do programa.

V CONDIO

COMANDO 1 (BLOCO 1)

COMANDO 2 (BLOCO 2)

Figura 6.1: Fluxograma de Desvio Condicional

Em C existem 2 formas para o desvio condicional, que so: 1)


if (condio) comando;

41

A condio avaliada. Se o seu valor verdadeiro, comando executado. O processamento continua no comando seguinte ao if. 2)
if (condio) comando1; else comando2;

A condio avaliada. Se o seu valor verdadeiro, comando1 executado, seno comando2 executado. O processamento continua no comando seguinte ao if.

Nas duas formas acima, comando pode ser tanto um comando simples quanto um bloco de comandos. Vejamos um exemplo: Pseudocdigo:
LEIA A SE A > 0 ENTO IMPRIMA "A maior que zero"

em C:
main() { int A; printf("Entre com o valor de A : "); scanf("%d", &A); if (A>0) printf("A e' maior que zero\n"); }

Caso mais de uma ao tenha que ser executada, deve-se utilizar o bloco de comandos como mostra o seguinte exemplo:
if (salario > 10000) { inps = salario * 0.10; irenda = salario * 0.15; }

O exemplo a seguir ilustra a forma do "if-else". Pseudocdigo:


LEIA A

42

Se A > 0 IMPRIMA "A E MAIOR QUE ZERO" Seno IMPRIMA "A E MENOR OU IGUAL A ZERO" Fim Se

Em C:
main() { int a; printf("Entre com o valor de A : "); scanf("%d", &a); if (a>0) printf("A e' maior que zero\n"); else printf("A e' menor ou igual a zero\n"); }

Como vimos anteriormente, em C uma expresso verdadeira quando o seu valor diferente de zero. Usando essa caracterstica, podemos excluir em certos casos a comparao lgica dentro do if. Por exemplo, as duas formas seguintes so equivalentes:
if (a!=0)

e
if (a)

Deve-se tomar muito cuidado tambm aqui, para no confundir o operador de atribuio (=) com o operador relacional (==). Por exemplo, sejam as seguintes instrues.
if (a==8) comando;

e
if (a=8) comando;

Na primeira forma o comando s executado se a for igual a 8. Na segunda forma o valor 8 atribudo varivel a e o comando sempre executado.

if aninhados
43

Um comando if pode estar dentro de um outro comando if. Dizemos ento que o if interno est aninhado. Exemplo:
1. 2. 3. 4. 5. if ((ch=getchar()) >= 'a') if (ch <= 'z') puts("Voc digitou uma letra"); else puts("Voc no digitou uma letra");

Anlise: Observe a construo na linha 1. No h nela nenhuma novidade. Como vimos antes, em C, qualquer atribuio entre parnteses considerada como uma expresso que tem o valor da atribuio sendo feita. Problemas de ambigidade em construes do tipo:
if ((ch=getchar()) >= 'a') if (ch <= 'z') puts("Voce digitou uma letra"); else puts("Voce nao digitou uma letra");

onde poderamos ficar em dvida sobre a qual if corresponde o else, so resolvidos assumindo-se que cada else sempre associado ao mais recente if ainda sem else. Mas, e se quisssemos que o else no exemplo anterior pertencesse ao primeiro if? Nesse caso, voc tem de usar chaves:
if ((ch=getchar()) >= 'a') { if (ch <= 'z') puts("Voce digitou uma letra"); } else puts("Voce nao digitou uma letra");

O comando switch
Todas as selees de comandos dentro de um programa podem ser escritas atravs de comandos if ou if-else.

44

Isto, porm, pode tornar-se muito trabalhoso e de difcil compreenso quando tivermos muitas alternativas, como mostra o exemplo a seguir.
main() { char ch; ch = getchar(); if (ch=='a') { // entre com os dados . . } else if (ch=='b') { // processe dados . . } else if (ch=='c') { // imprima relatrio . . } else puts("Opcao invalida"); }

Para reescrever este mesmo programa de forma mais simples, C fornece o comando switch, que tem o seguinte formato:
switch (expresso) { case Constante1: lista de comandos 1; [break;] case Constante2: lista de comandos 2; [break;] . . . case ConstanteN: lista de comandos N; [break;] default: /* opcional */ lista de comandos default; }

A execuo deste comando segue os seguintes passos:

45

1. A expresso avaliada; 2. Se o resultado da expresso for igual a uma das constantes, ento a execuo comea a partir do comando associado essa constante e prossegue com a execuo de todos os comandos at o fim do switch, ou at que se encontre uma instruo break; 3. Se o resultado da expresso no for igual a nenhuma das constantes e voc tiver includo no comando switch a opo default, o comando associado ao default executado. Caso contrrio, isto , se a opo default no estiver presente, o processamento continua a partir do comando seguinte ao switch.

Vamos refazer o programa anterior usando switch.


main() { char ch; ch = getchar(); switch (ch) { case 'a': /* entre com os dados */ . . break; case 'b': /* processe dados */ . . break; case 'b': /* imprima relatrio */ . . break; default: puts("Opcao invalida"); } }

Observaes: Pode haver uma ou mais instrues seguindo cada case. Estas instrues no precisam estar entre chaves; A expresso em switch (expresso) deve ter um valor compatvel com um inteiro, isto , podem ser usadas expresses do tipo char e int com todas as suas variaes. Voc no pode usar reais ( float e double), ponteiros, strings, ou outras estruturas de dados.
46

O comando break causa uma sada imediata do switch. Se no existir um break seguindo as instrues associadas a um case, o programa segue executando todas as instrues associadas aos case abaixo. Esta caracterstica pode ser interessante em algumas circunstncias. Uma delas quando temos o mesmo procedimento associado a mais de uma constante diferente. Por exemplo, no programa anterior poderamos querer fazer a seleo de procedimentos usando tambm letras maisculas:
switch (ch) { case 'A': case 'a': /* entre com os dados */ . . break; case 'B': case 'b': /* processe dados */ . . break; case 'C': case 'c': /* imprima relatrio */ . . break; default: puts("Opcao invalida"); }

O operador condicional ternrio


Seja o seguinte programa para calcular o mximo de dois nmeros:
main() { int a; int b; int max; printf("Digite dois numeros : "); scanf("%d%d", &a, &b); if (a > b) max = a; else max = b; printf("O maior deles e' o %d\n", max); }

47

Observe que o programa teve de escolher entre duas sentenas (max = a ou max = b baseado em uma condio (a > b ou a <= b). Esta uma situao to comum em programao de computadores, que em C existe uma construo especial para fazer essa seleo: o operador condicional. Forma geral:
expresso1? expresso2 : expresso3

O operador condicional pode ser visto como uma nica expresso. Sua interpretao a seguinte: Se expresso1 verdadeira, a expresso toda toma o valor de expresso 2; seno a expresso toma o valor de expresso3. O programa anterior poderia ser reescrito na forma:
main() { int a; int b; int max; printf("Digite dois numeros : "); scanf("%d%d", &a, &b); max = (a > b) ? a : b; printf("O maior deles e' o %d\n", max); }

ou, ainda melhor:


main() { int a; int b; printf("Digite dois numeros : "); scanf("%d%d", &a, &b); printf("O maior deles e' o %d\n", (a>b)?a:b); }

Expresses com o operador condicional no so necessrias, visto que o comando if-else pode substitu-las. So, entretanto, mais compactas e, em geral, geram um c digo de mquina menor.

48

CAPTULO 7 - Laos
Assim como existem comandos que voc deseja executar condicionalmente, podem existir outros comandos que voc deseja executar repetidamente. Laos so usados, tipicamente, em situaes onde voc deseja repetir um dado procedimento at que acontea alguma coisa (uma tecla em particular seja pressionada ou uma varivel contenha um dado valor). Existem trs tipos de laos em C: o lao while, o lao for e o lao do...while. Ns estudaremos os trs nessa ordem.

O lao while
O lao while o mais genrico dos trs e pode ser usado para substituir os outros dois, ou, em outras palavras, o lao while tudo o que voc precisa. Os outros dois existem somente para a sua convenincia. O formato do comando while :
while (condio) comando;

onde condio tem um valor igual a 0 (falso) ou diferente de 0 (verdadeiro), e comando pode ser um nico comando ou uma seqncia de comandos entre chaves ({...}).

A execuo do while segue os seguintes passos: 1. A condio avaliada: 2. Se condio for verdadeira ento o comando executado. Seno, a execuo vai para o passo 4; 3. Volta para o passo 1; 4. Fim do comando.

Pela ordem de execuo vista acima, percebemos que, se a expresso tiver valor falso na primeira vez em que for avaliada, o comando associado no ser executado nem mesmo uma vez. Vejamos como exemplo um programa que imprime na tela os nmeros de 1 a 5:
49

1. main() { 2. int counter = 0; 3. 4. 5. 6. 7. 8. } while(counter < 5) { counter++; printf(%d\n, counter); } puts(Fim do Programa);

O lao anterior poderia ser escrito de forma um pouco mais compacta:


1. main() { 2. int counter = 0; 3. 4. 5. 6. } while(counter++ < 5) printf(%d\n, counter); puts(Fim do Programa);

Sada:
1 2 3 4 5 Fim do Programa

Anlise: Esteja certo de que voc entendeu as modificaes feitas antes de prosseguir. A varivel de controle counter inicializada com zero na linha 2. A instruo na linha 3 verifica se o valor de counter menor do que 5 e em seguida incrementa a varivel de controle de modo que, na primeira passagem do loop, o primeiro valor a ser impresso o nmero 1. O programa prossegue at que counter atinge o valor 5, quando o lao encerrado. Podemos utilizar ainda construes onde no existe nenhum comando associado ao while. Exemplo:
while ((ch=getchar()) != 'a');

O lao anterior l um caracter do teclado at que ele seja o caracter 'a'. Observe que tivemos de colocar o ponto e vrgula (;) aps o while para indicar a ausncia de comandos.

50

Laos while mais complexos


Uma ou mais expresses lgicas podem ser combinadas para gerar a condio do lao while. Observe o seguinte exemplo:
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. } #define MAXINT 0x7FFF main() { int small; long large; printf("Entre com um nmero pequeno: "); scanf("%d", &small); printf("Entre com um nmero grande: "); scanf("%ld", &large); while (small<large && small<MAXINT) { small++; large-=2; } printf("\nSmall: %d Large: %ld\n", small, large);

Anlise: O programa consiste em uma espcie de jogo. O objetivo determinar em que ponto uma varivel inteira, small, se encontra com a varivel large. O valor inicial das variveis fornecido pelo usurio. Em cada passagem do lao (linhas 9-12) o valor de small incrementado de uma unidade e o valor de large decrementado de 2 unidades. Observe a condio composta de parada do lao while na linha 9. O programa termina quando (small < large) mas, alm disso, o valor atribudo varivel small no pode ser maior do que MAXINT, uma constante definida na linha 1 contendo o maior valor permissvel para uma varivel do tipo int.

O LAO for
O lao for encontrado na maioria das linguagens de programao, incluindo C. No entanto, como iremos ver, a verso C mais flexvel e dispe de muito mais recursos do que a implementao das outras linguagens. A idia bsica do comando for a de que voc execute um conjunto de comandos um nmero fixo de vezes enquanto que uma varivel (chamada de varivel de controle do for) incrementada ou decrementada a cada passagem pelo lao. Vamos, por exemplo, modificar o programa do contador de 1 a 5 de forma a usar o lao for.
1. main() { 2. int i;

51

3. 4. 5. }

for (i = 1; i <= 5; i++) printf("%d\n", i);

Anlise: O corao do lao for encontra-se na linha 3. Ali a varivel de controle inicializada e incrementada e a condio de parada testada. Os detalhes de implementao do lao for sero vistos a seguir.

Forma genrica do lao for:


for (expresso1; expresso2; expresso3) comando;

Observe que dentro dos parnteses existem 3 expresses separadas por ponto e vrgula: expresso1 , normalmente, a inicializao da varivel de controle do lao. expresso2 um teste que, enquanto resultar verdadeiro, causa a continuao do lao. expresso3 , normalmente, o incremento ou decremento da varivel de controle do for.

A forma genrica do lao for equivalente ao seguinte cdigo:


expresso1; while (expresso2) comando; expresso3; }

Qualquer uma das trs expresses pode ser omitida mas os ponto e vrgulas devem permanecer. Exemplo:
main() { int i=1; for (; i <= 5; i++) printf("%d\n", i); }

52

Se voc omitir expresso2, o compilador assume que ela verdadeira, o que resulta em um lao que nunca termina, ou, um lao eterno. Laos eternos so construes freqentemente encontradas em programas de computadores. Eles no terminam devido ao teste de uma condio. Ao invs disso, o programador confia que a ocorrncia de algum evento durante a execuo do lao force o desvio do fluxo para fora do mesmo. Assim, visando a clareza do cdigo escrito, muitos programadores C costumam usar a seguinte construo para a criao de laos eternos:
1. #define EVER ;; 2. for(EVER) { 3. // comandos... 4. }

O define na linha 1 cria uma constante simblica EVER que faz com que as trs expresses no for da linha 2 sejam omitidas, resultando em um lao eterno. A diretiva define ser vista com mais detalhes posteriormente, quando estudarmos o prprocessador C. Por outro lado, qualquer uma das expresses de um lao for podem conter vrias instrues separadas por vrgulas. Vejamos atravs de um exemplo:
main() { int i; int j; for (i = 1, j = 10; i <= 10; i++, j += 10) printf("%d %d\n", i, j); }

Observe que a primeira e a ltima expresses do for so constitudas de 2 instrues cada uma, inicializando e modificando as variveis i e j. Pode-se tornar as expresses do for arbitrariamente complexas incluindo, por exemplo, em expresso1 instrues sem relao com as variveis do lao ou ainda incluindo em expresso3 todas as instrues do corpo do for. Isto citado aqui apenas como curiosidade e, absolutamente, no constitui boa prtica de programao. Exemplo:
main() { int i; for i++); } (puts("comecou"), i=1; i<=10; printf("%d\n", i),

53

Laos for aninhados


Pode-se ter um for dentro de outro for. Seja, por exemplo, o seguinte programa que imprime a tabuada:
main() { int i; int j; for (i=1; i<10; i++) for (j=1; j<10; j++) printf("%d x %d = %d\n", i, j, i*j); }

O lao externo executa 10 vezes, enquanto que o lao interno executa 10 vezes para cada passagem do lao externo, totalizando 10 * 10 = 100 passagens pela funo printf( ).

O LAO do...while
Forma genrica:
do comando while (expresso);

O comando do...while semelhante ao comando while. A diferena est no momento de avaliao da expresso, que sempre ocorre aps a execuo do comando. Isto faz com que o comando do lao do...while sempre execute ao menos uma vez.

A execuo do lao do...while segue os seguintes passos: 1. Executa comando; 2. Avalia expresso; 3. Se expresso for verdadeira ento vai para passo 1; 4. Fim do comando.

Exemplo:

54

1. main() { 2. int i=1; 3. 4. 5. 6. 7. 8. } do { printf("%d\n", i); i++;

} while (i<=5);

que poderia ser reescrito como:


1. main() { 2. int i=1; 3. 4. 5. 6. 7. } do printf("%d\n", i); while (++i<=5); puts(acabou);

Na linha 2 a varivel de controle do lao (i) inicializada. O programa entra no corpo do lao do...while antes do teste da condio, o que garante a execuo dos comandos ao menos uma vez. Na linha 4 o valor da varivel de controle impresso e, na linha 5, a condio testada. Se a condio for verdadeira, a execuo do programa retorna para o incio do lao na linha 4, caso contrrio o programa prossegue para a linha 6.

OS COMANDOS break, continue e goto


O comando break O comando break, quando utilizado dentro do bloco de comandos associado a um for, a um while ou a um do...while, faz com que o lao seja imediatamente interrompido, transferindo o processamento para o primeiro comando seguinte ao lao. Observe o programa a seguir:
1. void main() 2. char ch; 3. int i; {

4. for (i=0; i<10; i++) { 5. ch = getchar(); 6. getchar(); 7. if (ch=='#') break; 8. printf("%c\n", ch); 9. } 10. puts("Acabou"); 11.}

55

Anlise: O programa acima l e imprime um mximo de 10 caracteres digitados pelo teclado. Se um dos caracteres digitados for o caracter #, o lao for terminado e a execuo pula para a instruo seguinte ao for na linha 10. Observe a instruo na linha 6. Como foi visto no Captulo 5, a entrada de dados para a funo getchar( ) bufferizada e, por este motivo, o ENTER digitado pelo usurio depois de cada caracter deve ser retirado do buffer. Esta a funo da instruo na linha 6; o caracter de nova linha retirado do buffer mas no atribudo a nenhuma varivel do programa. Este um artifcio comum quando fazemos leitura de caracteres via getchar( ) ou scanf( ). Voc deve estar lembrado que ns j vimos o comando break quando estudamos a instruo de desvio condicional switch. Ele fazia com que o processamento fosse desviado para a instruo seguinte ao switch. Alm do uso com o switch, o comando break pode ser usado com qualquer uma das formas de lao (while, for e do...while).

O comando continue
O comando continue tem funcionamento semelhante ao comando break. A diferena reside em que, ao invs de interromper a execuo do lao, como no break, o comando continue pula as instrues que estiverem abaixo, e fora a prxima iterao do lao. Nos laos while e do...while, a execuo do programa vai diretamente para o teste condicional. No caso do lao for, o computador primeiro executa expresso3 (normalmente o incremento/decremento da varivel de controle do lao) e ento vai para o teste condicional. Observe o seguinte exemplo:
void main() { char ch; int count=0; while (count<10) { ch=getchar(); getchar(); if(ch=='#') continue; printf("%c\n", ch); count++; } puts("Acabou!"); }

O programa l e imprime 10 caracteres digitados pelo usurio. Se um dos caracteres digitados for o #, ele ignorado e o processamento continua com a prxima iterao do lao.
56

O comando goto
O comando goto causa o desvio da execuo do programa para algum outro ponto dentro do cdigo. Forma genrica:
goto rtulo;

Exemplo:
goto erro;

Para que esta instruo opere, deve haver um rtulo em algum outro ponto do programa. Um rtulo um nome seguido por dois pontos. Exemplo:
erro: puts("Deu pau!");

A execuo desviada para a instruo seguinte ao rtulo. Formalmente, o comando goto no necessrio e, na prtica, sempre possvel escrever programas sem ele. O seu uso fortemente desaconselhado uma vez que, atravs dele, pode-se criar programas sem qualquer estrutura: pode-se, entre outras coisas, desviar o fluxo de execuo para o interior de laos e para fora destes, usar goto como chamada de funo e chamadas de funes como goto, tornando os programas de difcil manuteno e leitura. No entanto, em algumas poucas ocasies o comando goto pode ser til. Observe o seguinte exemplo:

while (condio1) { // comandos. while (condio2) { // comandos while (condio3) // comandos if (desastre) goto ERRO; } } } ERRO: // tratamento de erro

Compare com o mesmo trecho de cdigo escrito sem o uso do goto:


5 57

while (condio1) { // comandos while (condio2) { // comandos while (condio3) { // comandos if (desastre) break; } if (desastre) break; } if (desastre) break; } if (desastre) // tratamento de erro

58

CAPTULO 8 - Funes
Ns j vimos como executar um trecho de cdigo condicionalmente (if, switch, etc.) ou iterativamente (while, for e do...while). Neste captulo, consideraremos como executar o mesmo trecho de cdigo em diversos pontos do seu programa ou, o mesmo procedimento para conjuntos de dados diferentes.

Introduo
Agora que voc j conhece os comandos mais simples da linguagem e j escreveu e testou vrios programas pequenos, deve estar pensando: Mas como eu fao para escrever um programa maior? Talvez voc j tenha tentado e tenha encontrado dificuldades em criar o programa. Normalmente, a grande dificuldade encontra-se em como comear pois, mesmo para a criao de um programa simples, existem muitas tarefas a serem executadas. Uma das respostas para essa questo, est na essncia do mtodo usado na programao estruturada: a reduo da complexidade do problema que est sendo tratado. Isto pode ser conseguido quebrando-se o problema num conjunto de subproblemas menores, at que cada um dos subproblemas seja de soluo imediata. Podemos explicar melhor essa idia por meio de um exemplo: Vamos supor que voc deseje construir um jogo de adivinhao de nmeros. O jogador deve pensar um nmero e dizer ao programa o intervalo onde o nmero se encontra, e o computador deve adivinhar o nmero que o jogador pensou em um nmero razovel de tentativas. Por exemplo, se o intervalo est entre 1 e 1000, o programa deve adivinhar o nmero, no pior caso, em 10 vezes. A procura do nmero a ser adivinhado ser feita pelo mtodo de BUSCA BINRIA. Tenta-se primeiro, o nmero no meio do intervalo. Se for igual ao nmero pensado pelo jogador, a procura termina. Se o nmero do meio do intervalo for maior (menor) do que o nmero do jogador, repete-se o processo para a metade inferior (superior) do intervalo. A soluo deste problema envolve trs tarefas bsicas: 1) iniciar o programa e pedir o intervalo onde se encontra o nmero; 2) calcular o valor do meio do intervalo; 3) conferir com o jogador se este o valor correto, ou se est acima ou abaixo do valor correto.

59

Sabemos ainda, que as tarefas (2) e (3) tm que ser repetidas at que o resultado correto seja encontrado. Este programa pode ser representado pelo seguinte diagrama grfico, tambm conhecido por DIAGRAMA DE ESTRUTURAS:

PROGRAMA ADIVINHA

INICIAR

PROCURAR VALOR AT ACERTAR

CALCULAR VALOR

CONFERIR VALOR

O diagrama de estrutura mostra a diviso de um programa em mdulos. Cada mdulo deve executar uma funo bem definida. No diagrama acima vemos que o programa ADIVINHA composto por 3 mdulos (as folhas da rvore) e cada um deles ir executar uma das tarefas descritas anteriormente. O diagrama de estrutura, por sua caracterstica grfica, ajuda bastante na identificao e visualizao dos mdulos componentes de um programa. Aps a confeco do diagrama de estrutura, podemos voltar ao nosso problema original j com uma boa noo de como ele deve ser equacionado. Vamos atacar o problema por partes. Primeiro, tratemos o programa principal, que pode ser descrito informalmente da seguinte maneira:

60

comeo Iniciar faa CalcularValor ConferirValor enquanto no acertou fim

Esta estrutura pode ser reproduzida fielmente por um trecho de programa C da seguinte maneira:
main() { Iniciar(); do { CalcularValor(); ConferirValor(); } while (!acertou); }

No trecho de programa acima, podemos ver toda a estrutura de funcionamento deste programa. Procedendo desta forma, podemos conferir se as linha gerais do programa se encaixam naquilo que desejamos. Ainda sabemos como fazer Iniciar(), CalcularValor() e ConferirValor(), nem como determinar o valor da varivel acertou, porm esta uma questo para ser resolvida no prximo passo. Tratando cada um deles como um programa menor, a soluo fica muito mais fcil. no

Funes
Ainda nos falta um mecanismo em C para definir Iniciar(), CalcularValor() e ConferirValor(). Sabemos, com certeza, que cada um destes nomes corresponde a um mdulo de programa. Sabemos tambm, quais as tarefas que devem ser feitas em cada um deles. A questo, como definir um mdulo em C? A soluo est no uso de uma construo chamada subrotina, subprograma ou procedimento. O termo subrotina o nome que se d a um mdulo de programa que permite o desenvolvimento do programa por partes.

61

Em C, todas as subrotinas so chamadas de funes. Teoricamente, uma funo um procedimento que retorna um valor para o programa que a chamou. Esta distino bem marcada em PASCAL, onde as subrotinas so divididas em procedures (que no retornam valor) e functions (que retornam valor). A definio de funo em C mais flexvel: alm do uso convencional, funes podem retornar valores que no so usados pelo programa que as chamou, bem como no retornar valor nenhum. A definio de uma funo deve ser associada a um identificador, para que a mesma possa ser ativada por uma chamada do programa. Na sua forma mais simples, uma funo em C tem o seguinte formato:
nome() { variveis internas da funo; corpo do procedimento; }

/* se houver alguma */

Assim, por exemplo, a funo que inicia o programa pode ser definida da seguinte maneira:
Iniciar() { puts(" Programa Adivinha\n"); puts("Pense num numero dentro de um intervalo"); printf("Entre com o valor inferior do intervalo: "); scanf("%d", &inf); printf("Entre com o valor superior do intervalo: "); scanf("%d", &sup); }

Dentro do corpo de Iniciar(), colocamos todas as tarefas que desejamos executar ao iniciar o programa. Da mesma maneira, escrevemos os outros procedimentos e compomos o programa global:
int int int int inf; sup; num; acertou=0;

Iniciar() { puts(" Programa Adivinha\n"); puts("Pense num numero dentro de um intervalo"); printf("Entre com o valor inferior do intervalo: "); scanf("%d", &inf); printf("Entre com o valor superior do intervalo: "); scanf("%d", &sup); getchar(); } CalcularValor() { num = (inf + sup) >> 1;

/* divisao por 2. Lembra? */

62

} ConferirValor() char ch; {

printf("\nMeu chute e': %d\n", num); printf("Este valor e' maior(>), menor(<) ou igual(=) ao seu? "); ch = getchar(); getchar(); switch (ch) { case '>': sup = num - 1; break; case '<': inf = num + 1; break; case '=': acertou = 1; } } main() { Iniciar(); do { CalcularValor(); ConferirValor(); } while (!acertou); }

Anlise: Como voc pode observar, a estrutura de uma funo C bastante semelhante da funo main(). Na verdade, a nica diferena que main() uma funo privilegiada. Todo programa em C tem uma funo chamada main(); quando se inicia a execuo do programa, o fluxo desviado para o incio de main() e o processamento continua a partir da. O programa termina quando todas as instrues de main() tiverem sido executadas. Do mesmo modo que chamamos uma funo da biblioteca C - getchar(), scanf(), printf(), etc. - chamamos nossas prprias funes: Iniciar(), CalcularValor() e ConferirValor(). Ao encontrar a primeira sentena com o nome de uma funo, no nosso caso Iniciar(), passam a ser executados os comandos que se encontram dentro da definio desta funo. Ao chegar ao final da funo, a execuo volta funo main(). A prxima sentena a ser executada o do...while, onde as funes CalcularValor() e ConferirValor() devero ser chamadas at que a varivel acertou tenha um valor diferente de zero.
63

main() { Iniciar(); do { CalcularValor(); ConferirValor(); }

Iniciar( ){ ---; ---; ---; }

CalcularValor( ){ ---; }

ConferirValor( ){ ---; ---; ---; {

Voc deve ter notado, que o simples fato de dividir o programa em mdulos simplificou em muito, no somente a tarefa de escrita, como tambm a sua leitura, pois, durante a execuo, tudo se passa como se o corpo das funes fosse includo dentro da funo main(). Resumindo: Um problema sempre mais difcil de ser resolvido quando consideramos todos os seus aspectos simultaneamente; Um problema complexo = soma de problemas menos complexos; Programa = corpo principal que chama subrotinas; A utilizao de procedimentos facilita a construo do programa e gera programas mais simples de serem compreendidos; A chamada de uma subrotina ocasiona a execuo de um trecho de programa; A utilizao de procedimentos permite dividir o trabalho de elaborao de um programa grande entre vrios programadores ou grupos de programadores, trabalhando independentemente sob a coordenao de um analista.

Variveis locais
64

As variveis declaradas dentro de uma funo so chamadas variveis locais, e so conhecidas somente pela funo onde elas so declaradas. Por exemplo, a varivel ch em ConferirValor(), conhecida somente por ConferirValor() e invisvel s demais funes, incluindo main(). Se inclussemos em main() a instruo:
printf("%c", ch);

teramos um erro de compilao, pois main() no conhece a varivel ch. Uma varivel local conhecida em C como varivel automtica, pois ela automaticamente criada quando a funo ativada e destruda na sada da funo. Veremos isso com mais detalhes adiante. Vamos exemplificar o conceito de variveis locais atravs de mais um exemplo. Seja o seguinte programa que converte temperaturas em graus Fahrenheit para graus Celsius:
float Far; Convert() { float Cel; Cel = ((Far - 32) * 5) / 9; printf(Em Convert. Temperatura em Celsius: %f\n, Cel); } main() { float Cel=0.0; printf("Temperatura em Fahrenheit: "); scanf("%f", &Far); Convert(); printf(Em main. Temperatura em Celsius: %f\n, Cel); }

Sada:
Temperatura em Fahrenheit: 212 Em Convert. Temperatura em Celsius: 100.00000 Em main. Temperatura em Celsius: 0.000000

Anlise: Observe o valor impresso para a temperatura em graus Celsius na funo main(). Ele corresponde ao valor da varivel Cel em main() (deste ponto em diante denotada por Cel:main). Esta varivel, inicializada com 0.0, nada tem a ver com a varivel Cel:Convert. O contedo de Cel:main permanece inalterado durante toda a execuo do programa. Apesar de as variveis terem o mesmo nome, elas ocupam endereos diferentes de memria. A varivel Cel:Convert destruda ao final da execuo da funo Convert() e o seu contedo perdido.
65

Variveis globais
Variveis globais so conhecidas em C como variveis externas. Uma varivel dita global quando for declarada fora de qualquer mdulo do programa fonte. Com isto, o seu valor torna-se acessvel a todas as funes, desde o ponto de declarao da varivel at o fim do programa. Como as variveis globais so acessveis a todos os mdulos do programa fonte, elas constituem uma boa maneira de trocar informaes entre funes que precisam ter acesso aos mesmos dados. Observe o programa da adivinhao de um nmero: as variveis inf e sup, so lidas por Iniciar(), mas so acessadas por CalcularValor() e ConferirValor(). A varivel num tem o seu valor determinado por CalcularValor(), mas utilizada por ConferirValor(). Finalmente, acertou determinada por ConferirValor() e utilizada pela funo main(). Por esse motivo, todas essas variveis foram declaradas no incio do programa, fora de qualquer uma das funes do programa.
int int int int inf; sup; num; acertou=0; { Programa Adivinha\n");

Iniciar() puts(" . . .

Vamos rever o programa para converso de temperaturas dessa vez com a utilizao de variveis globais.
float Far; float Cel; Convert() { Cel = ((Far - 32) * 5) / 9; printf(Em Convert. Temperatura em Celsius: %f\n, Cel); } void main() { printf("Temperatura em Fahrenheit: "); scanf("%f", &Far); Convert(); printf(Em main. Temperatura em Celsius: %f\n, Cel);

66

Sada:
Temperatura em Fahrenheit: 212 Em Convert. Temperatura em Celsius: 100.00000 Em main. Temperatura em Celsius: 100.000000

Anlise: Observe os valores impressos para a temperatura em graus Celsius. Eles correspondem ao valor da varivel Cel a qual, por ser uma varivel global, comum a ambas as funes: main() e Convert(). Voc deve prestar especial ateno ao nome das variveis. Variveis locais com o mesmo nome de variveis globais impossibilitam o acesso a estas. Observe o seguinte exemplo:
float Far; float Cel=0.0; Convert() float Cel; {

Cel = ((Far - 32) * 5) / 9; printf(Em Convert. Temperatura em Celsius: %f\n, Cel); } main() { printf("Temperatura em Fahrenheit: "); scanf("%f", &Far); Convert(); printf(Em main. Temperatura em Celsius: %f\n, Cel); }

Sada:
Temperatura em Fahrenheit: 212 Em Convert. Temperatura em Celsius: 100.00000 Em main. Temperatura em Celsius: 0.000000

Anlise: A declarao de uma varivel local Cel:Convert torna impossvel funo Convert() qualquer referncia varivel global Cel. A varivel global foi mascarada pelo uso do mesmo nome para variveis locais e globais. Variveis globais devem ser usadas com cautela, pois, por elas serem acessveis a todos os mdulos componentes do programa, o seu contedo pode ser inadvertidamente alterado
67

por algum desses mdulos. Existem outros meios de trocar informaes entre funes, os quais sero vistos adiante.

Funes que retornam um valor


Ns j vimos funes da biblioteca C que retornavam um valor. Por exemplo:
ch = getchar();

Vamos ver agora como escrever nossas prprias funes de modo que elas retornem valores. Para isso, usaremos o comando return. O comando return tem duas aes: Se houver alguma expresso aps o return, o valor da expresso atribudo funo. Ou, em outras palavras, a funo passa a ter o valor da expresso. Em seguida a funo interrompida no ponto do return e o fluxo volta ao mdulo que chamou a funo que estava sendo processada. O comando return pode retornar somente um nico valor funo que chama.

Exemplo: No programa de adivinhao, vamos modificar a rotina ConferirValor() de modo que ela retorne o valor de acertou.
ConferirValor() char ch; {

printf("\nMeu chute e': %d\n", num); puts("Este valor e' maior(>), menor(<) ou igual(=) ao seu?"); ch = getchar(); switch (ch) { case '>': sup = num - 1; return 0; case '<': inf = num + 1;; return 0; case '=': return 1; } }

Observe que no so necessrios os comandos break aps cada caso do switch. Por que? Porque o return causa o imediato retorno do processamento funo main().
68

A chamada funo passa a ter a seguinte forma:


acertou = ConferirValor();

observe ainda, que agora a varivel acertou no precisa mais ser global, podendo ser local funo main(). Vamos transcrever novamente o programa adivinha, agora com as modificaes acima:
int int int inf; sup; num;

/* variveis externas */

Iniciar() { puts("Programa Adivinha\n"); puts("Pense num numero dentro de um intervalo"); printf("Entre com o valor inferior do intervalo: "); scanf("%d", &inf); printf("Entre com o valor superior do intervalo: "); scanf("%d", &sup); getchar(); } CalcularValor() { num = (inf + sup) >> 1; } ConferirValor() char ch; {

/* divisao por 2. Lembra? */

printf("\nMeu chute e': %d\n", num); puts("Este valor e' maior(>), menor(<) ou igual(=) ao seu?"); ch = getchar(); getchar(); switch (ch) { case '>': sup = num - 1; return 0; case '<': inf = num + 1;; return 0; case '=': return 1; } } main() { int acertou; Iniciar(); do { CalcularValor(); acertou = ConferirValor(); }

69

while (!acertou); }

Passando dados para a funo chamada


At agora, vimos duas maneiras de funes trocarem informaes: atravs de variveis externas e atravs do comando return. Vamos estudar agora uma outra maneira que atravs do uso de parmetros ou argumentos. Voc j usou argumentos nas funes printf() e scanf(). Vamos estudar a passagem de parmetros, novamente atravs do programa de adivinhao. Vamos supor que a funo CalcularValor(), no mais acesse as variveis externas inf e sup, mas que agora as receba como parmetros. A chamada funo, em main(), ficaria:
CalcularValor(inf, sup);

e a definio da funo passaria a:


CalcularValor(int baixo, int alto) { num = (baixo + alto) >> 1; // divisao por 2. Lembra? }

Observe a declarao dos parmetros no interior dos parnteses que seguem o nome da funo. Os parmetros baixo e alto so, na verdade, novas variveis, funcionando exatamente como variveis automticas da funo CalcularValor(). Embora o nome dos argumentos no seja o mesmo, o compilador entende que a varivel baixo em CalcularValor() receber o valor armazenado em inf. Da mesma forma, alto recebe o valor de sup. As variveis declaradas no cabealho da funo (baixo e alto no nosso exemplo) so chamadas de parmetros formais da funo, enquanto que as expresses usadas na chamada funo (as variveis inf e sup) so chamadas de parmetros reais ou argumentos. Note que o tipo dos parmetros formais e dos argumentos so idnticos, condio fundamental para que o programa funcione corretamente. Podemos agora apresentar a sintaxe da definio de uma funo em uma forma mais genrica:
nome(parmetros) { variveis internas da funo; corpo da funo;

/* se houver alguma */

70

compare com CalcularValor().

Mais modificaes no programa exemplo


Vamos, a ttulo de exerccio, ver algumas outras modificaes que poderiam ser introduzidas no programa exemplo. Em primeiro lugar, a funo CalcularValor() poderia usar o comando return para devolver o valor de num. A funo main() ficaria ento:
main() { int acertou; Iniciar(); do { num = CalcularValor(inf, sup); acertou = ConferirValor(); } while (!acertou); }

ConferirValor() poderia receber como parmetro num, de modo que num no mais precisaria ser uma varivel global, podendo ser local main(). main() { int num; int acertou; Iniciar(); do { num = CalcularValor(inf, sup); acertou = ConferirValor(num); } while (!acertou); }

Observe que, baseado no trecho de cdigo anterior, a seguinte construo seria vlida em C:
main() { Iniciar(); while (!ConferirValor(CalcularValor(inf, sup))); }

71

Perderamos, no entanto, a legibilidade que a forma original nos proporcionava.

Passagem de parmetros por valor


Para entender melhor o conceito de passagem de parmetros por valor, vamos estudar com um pouco mais de detalhes a troca de dados entre funes usando argumentos. Seja, por exemplo, a seguinte funo que calcula a potncia n de x:
potencia(int x, int n) int p; for (p=1; n>0; --n) p *= x; return p; } {

cuja chamada seria, por exemplo:


fator = potencia(base, expoente);

A passagem de parmetros entre funes se d atravs de uma estrutura de dados conhecida por pilha, onde os dados so literalmente empilhados. O topo da pilha marcado por um apontador chamado de stack pointer (SP). Supondo, que no momento da chamada base = 2 e expoente = 5, vamos acompanhar, de forma simplificada, a evoluo da pilha. Antes da chamada da funo potencia():

...

SP

Na chamada da funo potencia():

n:potncia x:potncia

5 2 ...

SP

72

Durante a execuo de potencia():

p:potencia n:potencia x:potencia

32 5 2 ...

SP

Ao trmino do processamento de potencia():

p:potencia n:potencia x:potencia

32 5 2 ...
SP

Observe: a) que na chamada da funo, so empilhados os VALORES de base e expoente, da o nome CHAMADA POR VALOR. A funo potencia() trabalha sobre os dados na pilha, e no sobre as variveis base e expoente que esto armazenadas em algum outro lugar na memria. b) que o espao para a varivel automtica p alocado tambm na pilha. c) que ao trmino do processamento de potncia o stack pointer volta a posio original, liberando com isso o espao ocupado pelos parmetros e pelas variveis automticas para uso posterior na chamada a outras funes.

73

A partir da observao a conclumos que, em C, uma funo chamada no pode alterar o valor de uma varivel da funo que a chamou; ela s pode alterar a sua cpia temporria, que criada na pilha. Uma exceo a essa regra a passagem de vetores como parmetros de funes. Quando passamos como argumento o nome de um vetor, a funo chamada no recebe uma cpia do vetor, mas sim o endereo da primeira posio do mesmo. Isto se d porque C , em tudo, uma linguagem voltada para a eficincia do cdigo gerado e seria muito dispendioso empilhar uma cpia de cada uma das posies de um vetor de, digamos, 1000 posies. A segunda concluso, tirada das observaes b e c, a de que as variveis automticas existem apenas durante a execuo da funo onde elas esto declaradas e so destrudas na sada. Por esse motivo, elas no podem reter seus valores de uma ativao a outra da funo, e devem ser inicializadas a cada ativao da mesma. Vejamos um outro exemplo de passagem de parmetros por valor: A funo swap troca o valor dos seus argumentos inteiros x e y.
swap (int x, int y) { int temp; printf("Swap. Antes do swap. y); temp = x; x = y; y = temp; printf("Swap. Depois do swap. x : %d, y : %d\n", x, y); } main() { int x = 5, y = 10; printf("Main. Antes do swap. y); swap(x,y); printf("Main. Depois do swap. x : %d, y); } y : %d\n", x, x : %d, y : %d\n", x, x : %d, y : %d\n", x,

Sada:
Main. Swap. Swap. Main. Antes do swap. Antes do swap. Depois do swap. Depois do swap. x x x x : : : : 5, 5, 10, 5, y y y y : : : : 10 10 5 10

Anlise:
74

Para entendermos o funcionamento do programa vamos, mais uma vez, observar a evoluo da pilha:
a) int x = 5, y = 10;

y:main x:main

10 5

SP

75

b)

swap(x,y);

temp:swap y:swap x:swap y:main x:main

? 10 5 10 5

SP

c)

temp = x;

temp:swap y:swap x:swap y:main x:main

5 10 5 10 5

SP

d)

x = y;

temp:swap y:swap x:swap y:main x:main

5 10 10 10 5

SP

76

e)

y = temp;

temp:swap y:swap x:swap y:main x:main

5 5 10 10 5

SP

e)

Retorno da funo swap

temp:swap y:swap x:swap y:main x:main

5 5 10 10 5
SP

Observe que a funo swap opera sobre cpias dos valores das variveis da funo main(). As variveis x:main e y:main permanecem inalteradas durante toda a execuo do programa. Ao trmino do processamento da funo swap o valor do stack pointer atualizado e as variveis e os parmetros locais da funo deixam de existir.

Passagem de parmetros por referncia


Mas e se quisermos que uma funo altere o valor das variveis do mdulo que a chamou? A soluo, nesse caso, passarmos como parmetro na chamada da funo o endereo das variveis e no o seu contedo. Isso feito com o operador de endereo (&). Ns j lidamos com esse caso quando usamos a funo scanf():
scanf("%d", &inf);

77

Neste exemplo, queremos que scanf() efetivamente altere o valor de inf, e por esse motivo, usamos o operador de endereo. A funo chamada deve lidar com o parmetro recebido como um endereo, e no como um valor. Veremos a passagem de parmetros por referncia com mais detalhes quando estudarmos ponteiros.

Funes no inteiras
O tipo de uma funo determinado pelo tipo de valor que ela retorna e no pelo tipo de seus argumentos. At agora, temos trabalhado somente com funes inteiras. Se uma funo for do tipo no inteira ela deve ser declarada. Em C, existe uma distino entre declarar uma funo e definir uma funo. A declarao de uma funo semelhante declarao de uma varivel e segue as mesmas regras de escopo, isto , uma declarao pode ser global ou local. Vamos estudar um exemplo. Seja o programa que transforma temperaturas Fahrenheit para Celsius.

float Convert(float Fer); main() { float Far, Cel;

// declarao da funo

printf("Temperature in Fahrenheit: "); scanf("%f", &Far); Cel=Convert(Far); printf("Celsius: %f\n", Cel); } float Convert(float Fer) float TCel; { // definio da funo

TCel = ((Fer - 32) * 5) / 9; return TCel; }

A declarao de uma funo tambm chamada de prottipo da funo. O prottipo da funo tem como objetivo informar ao compilador:
78

a) O tipo de valor retornado pela funo. b) O tipo e o nmero de parmetros passados para a funo. Estas informaes so necessrias para que o compilador aloque o espao necessrio na pilha para a passagem de parmetros e para o retorno do valor calculado pela funo. A declarao de uma funo no necessria se o programador puder garantir que toda funo definida antes de ser chamada. Ou, em outras palavras, o prottipo de uma funo no necessrio se a definio da funo for escrita no arquivo fonte antes de qualquer chamada funo. Criar o prottipo das funes, no entanto, constitui em boa prtica de programao uma vez que torna explicita a interface com as funes alm de livrar o programador da preocupao de ordenar as funes no arquivo. Vejamos mais um exemplo. O programa abaixo chama uma funo que, dada a base e a altura de um tringulo, calcula a sua rea. Obs: rea do tringulo = base * altura/2
/* prottipo da funo */

1. float area(float, float); 2. main() 3. float 4. 5. 6. 7. 8. } { s, b, h;

printf(Entre com a base e a altura: ); scanf(%f%f, &b, &h); s = area(b, h); // chamada da funo printf(Area = %.2f\n, s);

9. float area(float base, float altura) 10. return base*altura/2.0; 11.}

Olhe com ateno o prottipo da funo na linha 1. Observe que o nome dos parmetros no foi especificado mas apenas o seu tipo. Como foi dito anteriormente, o compilador precisa conhecer o nmero e o tipo dos parmetros passados para a funo a fim de alocar espao na pilha. O nome dos parmetros uma informao desnecessria para o compilador. Constitui em boa prtica de programao, no entanto, dar nomes aos parmetros quando da construo do prottipo das funes. Lembre-se que o prottipo de uma funo a especificao da interface com a mesma. Assim, tornar-se-ia muito mais claro para um programador entender o que a funo area faz se o seu prottipo fosse escrito como:
float area(float base, float altura);

79

O nome dado aos parmetros no prottipo de uma funo uma informao til ao programador e no ao compilador.

Podemos agora apresentar a forma genrica da definio de uma funo:


tipo nome(parmetros) { variveis internas da funo; corpo da funo; }

/* se houver alguma */

compare com a funo area().

Em C, pode-se declarar funes que no retornam nada com o tipo void. No exemplo anterior, se a varivel s fosse externa, teramos:
float s; void area(float, float); void main() { float b, h; printf(Entre com a base e a altura: ); scanf(%f%f, &b, &h); area(b, h); // chamada da funo printf(Area = %.2f\n, s); } void area(float base, float altura) s = base*altura/2.0; } {

/* prottipo da funo */

A vantagem de se declarar funes void a de que o compilador acusa um erro se tentarmos retornar um valor em funes desse tipo. C no permite a construo de funes aninhadas, isto , funes dentro de funes.

Classes de armazenamento
As variveis em C dividem-se, quanto classe de armazenamento em quatro grupos: variveis automticas;

80

variveis externas ou globais; variveis estticas; variveis registrador;

Estudaremos cada um dos grupos com mais detalhes.

Variveis automticas
J vimos variveis automticas em nossos exemplos anteriores. Suas caractersticas so: declaradas no interior de uma funo(bloco); criadas na pilha; existem somente durante a execuo da(o) funo(bloco) onde elas foram declaradas; so visveis, ou acessveis, somente no interior da(o) funo(bloco) onde foram declaradas;

As caractersticas acima usando bloco no lugar de funo seriam mais precisas, uma vez que C permite construes do tipo:
void main() float a, b; {

printf("Entre com dois numeros: "); scanf("%f%f", &a, &b); if (a>b) { float aux; aux=a; a=b; b=aux; } printf("Valores ordenados: %g, %g\n", a, b); }

A varivel aux existe somente durante a execuo do if, e conhecida apenas no bloco onde ela foi declarada.

Variveis externas

81

As variveis externas ou globais foram tambm vistas em nossos exemplos anteriores. Suas principais caractersticas so: declaradas fora de qualquer mdulo do programa fonte; mantm sua posio de memria alocada durante toda a execuo do programa. o seu valor acessvel a todas as funes existentes desde o ponto de declarao da varivel, at o fim do programa. Vimos que variveis externas so uma alternativa para a troca de informaes entre funes num mesmo arquivo. As funes e variveis que compem um programa C no precisam ser compiladas ao mesmo tempo. O programa fonte pode estar dividido em vrios arquivos e, assim, variveis externas servem tambm, para trocar informaes entre funes definidas em arquivos diferentes. Vejamos um exemplo:

Arquivo 1 (arq1.c)
float base, altura; extern float area(void); // definida no Arquivo 2

void main() { base=10.0; altura=2.0; printf("Area = %g\n", area()); }

Arquivo 2 (arq2.c)
float area(void) { extern float base, altura; return base*altura/2.0; } // declaradas no Arquivo 1

Observe que as variveis compartilhadas devem ser globais no arquivo onde elas forem declaradas. Nos demais arquivos elas devem ser declararadas com o prefixo extern. O mesmo se aplica s funes.

Compilao: a) Ambiente UNIX rodando o compilador gcc:


gcc -o area arq1.c arq2.c

82

b) Ambiente DOS usando o compilador Borland C++: necessrio criar um arquivo project contendo os arquivos componentes do programa.

Variveis estticas
Variveis estticas so declaradas com o prefixo static o qual modifica algumas das caractersticas de variveis locais e globais. Variveis locais estticas: Declaradas no interior de um bloco; No mais criadas na pilha. Mantm a sua posio de memria alocada durante toda a execuo do programa. Retm seus valores durante toda a execuo do programa; So visveis somente no interior do bloco onde foram declaradas;

Exemplo:
void soma(void); void main() soma(); soma(); soma(); } {

void soma(void) { static int i=0; printf("%d\n", ++i); }

Sada 1 2 3 Observe que a varivel i retm o seu valor entre as chamadas de soma(). Variveis externas declaradas com o prefixo static tm as mesmas caractersticas das variveis externas comuns, com a exceo de que elas agora so visveis apenas pelas
83

funes definidas no mesmo arquivo da declarao. Em outras palavras, variveis externas declaradas com o prefixo static no podem ser acessadas por funes definidas em outros arquivos. Esta caracterstica constitui-se em um mecanismo de privacidade importante programao modular.

Variveis registrador
O prefixo register, quando usado, indica que a varivel deve ser armazenada em uma memria de acesso muito rpido chamada de registrador. Os registradores localizam-se fisicamente dentro da CPU do computador e so em nmero limitado. O prefixo register deve ser usado para variveis que so muito acessadas no programa. (tais como as variveis de controle de laos, por exemplo) Nada garante que variveis declaradas com o prefixo register sejam realmente alocadas em registradores. O compilador faz um esforo nesse sentido, mas, se os registradores estiverem ocupados, o prefixo register ignorado e a varivel alocada em memria. Somente podem ser declaradas com o prefixo register variveis automticas e parmetros formais do tipo int ou char. Exemplo: Rode o programa abaixo com e sem o prefixo register e tente observar a diferena no tempo de processamento.
main() { register unsigned int

i, j;

for (i=1; i<2000; i++) for (j=1; j<2000; j++); }

Observao: No compilador C da Borland o uso de variveis do tipo registrador controlado atravs de uma opo de compilao: Options, Compiler, Optimization, Register Variables: [None, Register keyword, Automatic]. O valor default Automatic, significando que as variveis inteiras so alocadas nos registradores sempre que possvel, independente do uso ou no do prefixo register

Inicializao de variveis
Veremos agora como as variveis nas diversas classes de armazenamento podem ser inicializadas.

84

Na falta de inicializao explcita, variveis externas e variveis estticas so inicializadas com zero. Essas variveis so inicializadas uma nica vez, em tempo de compilao, e, portanto, devem ser inicializadas com constantes. Exemplo:
int i=1;

main() { static int . . . }

j=3;

Vetores podem ser inicializados com uma lista de valores entre chaves e separados por vrgulas:
int vetor[5]={0, 1, 2, 3, 4};

Variveis automticas e registradores, quando no inicializadas explicitamente, tm o seu valor indefinido. Lembre-se que o espao para essas variveis alocado somente quando da ativao do bloco onde elas foram declaradas, e que esse espao (a pilha no caso de variveis automticas e os registradores no outro caso) provavelmente j tinha sido usado por funes chamadas anteriormente, que deixaram l o contedo das suas prprias variveis. A inicializao dessas variveis, quando explicitamente feita no programa, se d a cada vez que o bloco onde elas foram declaradas ativado. Por esse motivo, a inicializao pode ser feita com constantes, variveis ou expresses. Exemplo:
main int { n=3;

// comandos; if (n>0) { int i=n; // varivel automtica inicializada com expresso for (; i>0; i--) { // bloco de comandos } } // comandos; }

85

Funes recursivas
Uma funo dita recursiva quando existe dentro da funo uma chamada a ela mesma. Como exemplo, vamos escrever o fatorial de um nmero de forma recursiva:
fatorial(n) = n * fatorial(n-1);

A funo que implementa o fatorial seria:


long fatorial(int n) long res; {

if (n==0) res=1L; else res = n*fatorial(n-1); return res; }

O que torna possvel a recursividade em C o fato de as variveis automticas e os parmetros formais serem criados na pilha.

Vamos estudar de forma simplificada a evoluo da pilha para o clculo de fatorial(2) usando a funo acima:

a) Primeira chamada da funo: fatorial(2)

res n

2*fat(1)

SP

86

b) Segunda chamada da funo: fatorial(1)

res n res n

1*fat(0)

SP

1
2*fat(1)

c) Terceira chamada da funo: fatorial(0)


res n res n res n fat(0)==1
SP

0
1*fat(0)

1
2*fat(1)

d) Primeiro retorno da funo.


res n

1 0
SP

res fat(1)=1*1 n res n

1
2*fat(1)

e) Segundo retorno da funo.


87

res n res n

1 0 1 1
SP

res fat(2)=2*1 n

Observe que a cada chamada da funo fatorial() so criadas novas instncias das variveis n e res as quais, ainda que tenham o mesmo nome, no confundem seus valores. claro que variveis locais a funes recursivas no podem ser estticas. Por qu? Ao usarmos funes recursivas devemos ter em mente que a dimenso da pilha finita e, portanto, se o nmero de chamadas exceder um limite mximo, pode esgotar-se a quantidade de memria disponvel para a pilha.

O Pr-Processador C
O pr-processador um programa que examina o programa fonte em C e executa neste fonte certas modificaes baseado em instrues chamadas de diretivas. As modificaes so feitas no texto do cdigo fonte, antes do incio do processo de compilao. Todas as diretivas ao pr-processador comeam com o smbolo #

A diretiva #define A diretiva define pode ser usada para definir constantes simblicas com nomes apropriados. Exemplo: Vamos escrever uma funo que calcule o volume de uma esfera.
#define PI 3.14

88

float CalculaVolume(float raio) { return(4.0/3.0*PI*raio*raio*raio); }

Antes do incio da compilao, o pr-processador troca todas as ocorrncias de PI no arquivo fonte por 3.14. Constantes simblicas so escritas usualmente em letras maisculas para diferenci-las de variveis. Por qu deve-se usar constantes simblicas? Em primeiro lugar para tornar o seu programa mais legvel. Em segundo lugar, suponha que no exemplo anterior a constante PI aparecesse em diversos pontos do seu programa e que, um dia, voc resolvesse aumentar a preciso e usar 3.1415926 no lugar do 3.14. Com o uso de constantes simblicas a alterao feita em apenas um ponto do programa: na diretiva define. Se, ao contrrio, voc tiver digitado 3.14 ao longo do programa, voc ter de procurar cada ocorrncia da constante e alterar seu valor. A diretiva define pode ser usada para definir no apenas constantes numricas mas tambm constantes simblicas. Exemplo: Se a diretiva define fosse usada para definir as constantes simblicas:
#define then #define begin { #define end }

um programa em C poderia ser escrito como:


if (i>0) then begin a = 1; b = 2; end

Observe como o programa ficou parecido com um programa Pascal.

Macros A diretiva define pode ser usada com argumentos, quando ento chamada de macro. O uso de macros semelhante ao uso de funes. Vamos, por exemplo, escrever uma macro que calcula a rea de um tringulo.
#define area(base, altura) ((base)*(altura)/2.0)

89

cuja chamada seria:


s = area(b, h);

o pr-processador substituir a instruo acima por:


s = ((b)*(h)/2.0)

Por qu so usados os parnteses? Imagine que no tivssemos usado parnteses e feito a seguinte chamada macro:
s = area(b+1, h+1);

o pr-processador faria ento a seguinte alterao:


s = b+1*h+1/2.0;

o que, obviamente, no seria o que espervamos. Por precauo, coloque parnteses envolvendo o texto todo de uma macro, bem como cada um dos argumentos. Vejamos outro exemplo. A macro abaixo calcula o valor absoluto de um nmero.
#define abs(x) ((x) > 0 ? (x) : -(x))

Macros versus funes Uma macro uma soluo eficiente quando argumentos de tipos diferentes devem ser usados em um mesmo programa. Seja, por exemplo, a macro abs() acima. Podemos cham-la com argumentos inteiros ou reais, ao passo que se fossemos escrever uma funo para isso, teramos de fazer:
float abs(float x) { return (x>0 ? x : -x); }

Se quisssemos calcular o valor absoluto de argumentos inteiros, teramos de escrever uma outra funo para esse fim. O uso de macros torna ainda a execuo do programa mais rpida, uma vez que ele elimina o desvio de fluxo (o jump para o incio da funo chamada e o return) e a passagem de parmetros entre funes.
90

Por outro lado, o pr-processador substitui cada chamada de uma macro pelo seu cdigo, o que aumenta o tamanho do programa fonte.

A diretiva #include A diretiva #include j foi vista no Captulo 1, quando falamos do arquivo stdio.h. Observe que o uso do #include diferente de mantermos funes em arquivos distintos que so compilados separadamente e que so linkados se necessrio. O pr-processador ao encontrar o #include fisicamente copia um arquivo para dentro do outro antes de iniciar a compilao. O cdigo resultante todo compilado junto.

91

CAPTULO 9 - Vetores
Chama-se vetor a um conjunto de posies contguas de memria onde cada uma destas posies armazena elementos de um mesmo tipo. Cada posio de memria chamada de um elemento do vetor. Vetores estendem o conceito de variveis contendo um item de informao, para variveis contendo vrios itens de informao. Eles oferecem a possibilidade de tratar um grupo de dados do mesmo tipo como um conjunto. Cada um dos elementos do conjunto pode ser individualmente acessado atravs de um ndice. Nesse captulo estudaremos vetores unidimensionais, matrizes, strings e dois algoritmos freqentemente aplicados a vetores: Busca e Ordenao.

Vetores unidimensionais
A utilizao de um vetor em C deve ser precedido pela declarao do mesmo. A declarao de um vetor composta pelo tipo do vetor (o tipo de cada um dos elementos), seguida pelo nome do vetor e pela dimenso do mesmo (o nmero de elementos que compem o vetor). A declarao de uma tabela de, por exemplo, 25 inteiros teria a forma:
int IntArray[25];

Quando o compilador encontra a declarao acima, ele reserva espao em memria para armazenar, exatamente, 25 nmeros inteiros. Supondo que os nmeros inteiros sejam representados em 4 bytes, a declarao do vetor IntArray aloca em memria 100 bytes contnuos como mostrado na figura a seguir.

IntArray[24]

4 bytes IntArray[0]

O primeiro elemento do vetor referenciado como IntArray[0], o seguinte como IntArray[1], e assim por diante at IntArray[24]. Observe, que o elemento referenciado por:
92

IntArray[2]

no o segundo elemento do vetor, e sim o terceiro, uma vez que a numerao comea em 0.

Assim, a forma geral da declarao de um vetor :


tipo nome[dimenso];

onde tipo um tipo qualquer de dados, nome o nome do vetor e dimenso o nmero de elementos do tipo tipo contidos no vetor. O primeiro elemento do vetor nome[0] e o ltimo nome[dimenso-1]. O espao total de memria alocado para o vetor nome :
dimenso*sizeof(tipo)

Vamos estudar um exemplo de aplicao de vetores. Seja o seguinte programa que soma as posies correspondentes de dois vetores: vetor1 e vetor2, cujos elementos so fornecidos pelo usurio.

1. #define DIM 3 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.} main() { int i; int vetor1[DIM]; int vetor2[DIM]; for (i=0; i<DIM; i++) { printf("vetor1[%d] = ", i); scanf("%d", &vetor1[i]); } for (i=0; i<DIM; i++) { printf("vetor2[%d] = ", i); scanf("%d", &vetor2[i]); } for (i=0; i<DIM; i++) printf("vetor1[%d] + vetor2[%d] = %d\n", i, i, vetor1[i]+vetor2[i]);

Sada:

93

vetor1[0] vetor1[1] vetor1[2] vetor2[0] vetor2[1] vetor2[2] vetor1[0] vetor1[1] vetor1[2]

= = = = = = + + +

0 1 2 0 1 2 vetor2[0] = 0 vetor2[1] = 2 vetor2[2] = 4

Anlise: Observe o uso da diretiva #define para dimensionar o vetor. Se, posteriormente, quisermos trocar a dimenso do vetor, basta alterar um nico ponto do programa, ao invs de procurarmos cada ocorrncia de DIM. O programa consiste de 3 laos for. No primeiro, na linha 6, o usurio entra com os dados para vetor[1]. Observe que na instruo scanf( ) foi usado o operador de endereo (&) precedendo cada elemento do vetor. Isso possvel porqu vetor1[i] uma varivel como outra qualquer e, portanto, tem endereo univocamente determinado. O programa acima poderia ser facilmente alterado para que os elementos dos vetores fossem, por exemplo, nmeros reais:

#define DIM 3 void main() { int i; float vetor1[DIM]; float vetor2[DIM]; for (i=0; i<DIM; i++) { printf("vetor1[%d] = ", i); scanf("%f", &vetor1[i]); } for (i=0; i<DIM; i++) { printf("vetor2[%d] = ", i); scanf("%f", &vetor2[i]); } for (i=0; i<DIM; i++) printf("vetor1[%d] + vetor2[%d] = %.2f\n", i, i, vetor1[i]+vetor2[i]); }

Alocao de vetores

94

Os elementos de um vetor so alocados em posies contnuas de memria. Deste modo, o elemento de ndice 0 alocado primeiro, o elemento de ndice 1 imediatamente abaixo e assim sucessivamente, at o ltimo elemento do vetor. Seja, por exemplo, a seguinte declarao:
int int sentinela=0; Array[3];

A alocao em memria, numa mquina do tipo PC, seria:

Array[0] Array[1] Array[2] sentinela

32 5 2 0

SP

Escrevendo alm do fim de um vetor


Quando voc atribui um valor a um elemento de um vetor, o compilador calcula onde armazenar o valor b aseado no tamanho de cada elemento e no ndice deste elemento. Suponha que voc queira atribuir um valor a Array[2] que o terceiro elemento do vetor. O compilador multiplica o ndice 2 pelo tamanho de cada elemento do vetor, no caso, 4 bytes. O valor resultante (8) somado ao endereo da primeira posio do vetor para obter o endereo de Array[2]. Em C no existe crtica quanto ao limite dos vetores. Se voc pedir ao compilador para acessar o elemento Array[3], o compilador ignora o fato que no h tal elemento. Ele calcula a que distncia (em bytes) tal elemento deveria estar do primeiro elemento do vetor e escreve por cima do dado que se encontrava naquela localizao de memria. Este pode ser virtualmente qualquer dado e, escrever um novo valor nesta posio, pode levar a resultados imprevisveis. Se voc tiver sorte, seu programa causar algum erro fatal e ser imediatamente interrompido. Caso contrrio, o programa comear a gerar resultados estranhos muito depois do acesso invlido e voc ter dificuldades para entender o que saiu errado. Observe o seguinte exemplo:

95

1. void main() { 2. int i; 3. int sentinela=0; 4. int Array[25]; 5. 6. 7. 8. 9. } printf("Sentinela = %d\n", sentinela); for (i = 0; i<=25; i++) Array[i] = 20; printf("Sentinela = %d\n", sentinela);

Sada:
Sentinela = 0; Sentinela = 20;

Anlise: Na linha 3 a varivel sentinela alocada logo abaixo do vetor Array[] e inicializada com 0. O for da linha 6 preenche os elementos do vetor com o inteiro 20. Observe que a varivel de controle do for assume os valores 0..25. O elemento de ndice 25 no foi originalmente alocado para o vetor. O seu endereo corresponde ao endereo da varivel Sentinela. Desta forma, a atribuio Array[25] = 20, em verdade, atribui o inteiro 20 varivel Sentinela, o que pode verificar-se pelo valor impresso na linha 8.

Inicializao de vetores
Vetores podem ser inicializados com uma lista de valores entre chaves e separados por vrgulas. Exemplo:
int vetor[5]={0, 1, 2, 3, 4};

Os valores so atribudos em seqncia, isto ,


vetor[0] = 0; vetor[1] = 1;

e assim por diante.

A declarao acima poderia ser substituda por:

96

int

vetor[]={0, 1, 2, 3, 4};

Observe os colchetes vazios. Se nenhum nmero for fornecido para dimensionar o vetor, o compilador conta o nmero de itens na lista de inicializao e atribui esse nmero dimenso do vetor.

Vetores com mais de uma dimenso


Os elementos de um vetor podem ser de qualquer tipo, simples ou estruturado. Na prtica, muito comum a ocorrncia em que o tipo do elemento tambm um vetor. A estrutura assim definida chamada de MATRIZ. Uma matriz simplesmente uma tabela com 2 ou mais dimenses (ou ndices).

Forma geral:
tipo nome [tamanho1][tamanho2] ... [tamanhoN]

Exemplo 1: Vamos analisar uma situao, onde desejamos construir uma estrutura capaz de armazenar as 5 notas obtidas pelos alunos de um determinado curso. Cada aluno identificado pelo seu nmero, que corresponde sua posio na lista de chamada da turma.

ALUNO 1 2 3 4 5 6

PROVA 1 9.5 5.0 7.0 6.5 4.5 3.0

PROVA 2 9.0 4.0 6.5 7.0 5.0 3.5

PROVA 3 6.5 5.5 8.0 5.5 6.0 6.0

PROVA 4 5.0 6.5 10.0 4.5 5.5 5.5

PROVA 5 7.0 3.5 8.5 6.5 5.0 5.0

Como acessar cada nota? Utilizamos dois ndices para obter o valor de uma nota: o nmero do aluno e o nmero da prova. A figura acima mostra claramente que cada uma das entradas da tabela na verdade uma segunda tabela contendo as notas das provas de um aluno. Alm disso, cada um dos elementos da tabela de notas pode ser um nmero fracionrio e portanto do tipo float.
97

As dimenses da tabela so o nmero mximo de alunos (n de linhas) e o nmero de provas naquele curso (n de colunas). Como fica ento a declarao desta tabela?
#define #define NumeroDeProvas 5 MaximoDeAlunos 50

float boletim[MaximoDeAlunos][NumeroDeProvas];

Como podemos referenciar a 3 nota do 15 aluno?


boletim [14][2]

Em C o ndice de um vetor tem de ser do tipo inteiro. Em certas aplicaes pode ser interessante trabalhar com ndices no numricos tais como dias da semana, cores, meses do ano, etc. Em tais ocasies usa-se as constantes enumeradas vistas no Captulo 3. Vejamos alguns exemplos de declarao de tabelas em C usando constantes enumeradas.

Exemplo 2: Suponha que se deseje armazenar o nmero de horas trabalhadas, em cada dia da semana, pelos consultores de uma empresa. CONSULTOR 1 2 . . 30 SEG 6 8 . . 6 TER 8 8 . . 10 QUA 7 9 . . 4 QUI 4 9 . . 3 SEX 10 7 . . 2

A declarao dessa tabela em C ficaria:


#define maxconsult 30 int tabela_horas[maxconsult][5];

Quantas horas o consultor nmero 2 trabalhou na quinta-feira? Vamos criar um tipo enumerado para os dias da semana:

98

enum dia_util {SEG, TER, QUA, QUI, SEX};

Assim, o nmero de horas que o consultor 2 trabalhou na quinta-feira seria acessada como:
tabela_horas[1][QUI]

Para sabermos quantas horas o consultor 5 trabalhou naquela semana, teramos o seguinte trecho do programa:
int num_horas; dia_util dia; for (num_horas=0, dia=SEG; dia<=SEX; dia++) num_horas += tabela_horas[4][dia]; printf("O consultor 5 trabalhou %d horas\n", num_horas);

Exemplo 3: Uma loja de departamentos est oferecendo uma promoo fornecendo descontos nas suas vrias linhas de mercadorias. Esse desconto depender do tipo da mercadoria adquirida e do tipo do cliente (novo ou antigo). Como o preo final obtido diretamente nos terminais do computador central, o gerente da loja pediu ao programador que alterasse o programa de clculo de preo de mercadorias, com o objetivo de i plantar a atual poltica de m descontos. Para tal, o programador organizou a seguinte tabela: CLIENTE MERCADORIA alimento limpeza papelaria ferragem eletrodomstico novo 0 0 5 10 15 antigo 5 5 10 15 20

que em C, seria definida como:


enum cliente {novo, antigo}; enum mercadoria {alimento, limpeza, papelaria, ferragem, eletrodomestico}; float tab_desconto[5][2];

Observe que a tabela de descontos deve ser preenchida com os comandos de atribuio:
99

tab_desconto[alimento][novo]=0; tab_desconto[limpeza][novo]=0; . . tab_desconto[eletrodomestico][antigo]=20;

Exemplo 4: J vimos como construir tabelas de 1 dimenso (vetores) e duas dimenses (matrizes). A Linguagem C permite a construo de tabelas com qualquer nmero de dimenses, o que propicia a soluo de inmeros problemas em vrios campos de aplicao. Imagine o caso de uma firma construtora que deseja controlar a quantidade de materiais comprados para suas diversas obras. Os materiais podem ser comprados de vrios fornecedores cadastrados na firma e usados em qualquer uma das obras. A estrutura abaixo, define uma tabela de 3 dimenses (fornecedor, obra e material) que representa a quantidade de todos os materiais, comprados de todos os fornecedores cadastrados, para todas as obras da firma.
#define MaxFornecedor 100 #define MaxObra 20 #define MaxMaterial 500 int compras[MaxFornecedor][MaxObra][MaxMaterial];

Para sabermos a quantidade do material nmero 150 (cimento, por exemplo) comprado do fornecedor cujo cdigo 11 para a obra em Madureira, cujo cdigo 13, devemos usar:
compras [11] [13] [150]

fornecedor obra

material

Inicializao de matrizes
Lembre-se que uma matriz consiste de um vetor cujos elementos so vetores. Sendo assim, a inicializao de matrizes semelhante inicializao de vetores: uma lista de elementos (vetores) entre chaves e separados por vrgulas. Exemplo: Considere o seguinte programa que inicializa duas matrizes e ento as multiplica.

100

main() { short int a[3][4] = { {-14, -36, -62, 78}, {-77, 14, -92, 17}, { 67, -51, 18, -60} }, b[4][2] = { { 60, -65}, { 7, 34}, {-23, 69}, { 32, -1} }; short int i, j, k, c[3][2]; for (i=0; i<3; i++) for (j=0; j<2; j++) { for(c[i][j]=0, k=0; k<4; k++) c[i][j] += a[i][k]*b[k][j]; printf("c[%d][%d] = %d\n", i, j, c[i][j]); } }

Sada:
c[0][0] c[0][1] c[1][0] c[1][1] c[2][0] c[2][1] = = = = = = 2830 -4670 -1862 -884 1329 -4787

Matrizes so armazenadas na memria por linha, isto , o ndice que varia mais rpido o ltimo (o das colunas). Essa mesma regra se aplica no caso de vetores de mais de duas dimenses. A matriz C no exemplo anterior seria armazenada na seguinte ordem:

C[0][0] C[0][1] C[1][0] C[1][1] C[2][0] C[2][1]

101

Cada posio de memria representa 2 bytes.

Inicializando vetores de 3 dimenses


Vetores de 3 dimenses podem ser vistos como vetores em que os elementos so matrizes. Exemplo:
int vet3d[3][4][2]={ { {6, {9, {3, {4, { {5, {6, {7, {9, { {4, {8, {2, {4, 3}, 7}, 9}, 2} }, 1}, 2}, 8}, 1} }, 5}, 1}, 3}, 5} } };

Como faramos para acessar o primeiro 8?


vet3d[1][2][1]

Vetores como argumento de funes


Vimos no captulo anterior que vetores constituam em uma exceo passagem de parmetros por valor. Quando passamos como argumento o nome de um vetor, a funo chamada no recebe uma cpia do vetor, mas sim o endereo da primeira posio do mesmo. Observe o programa abaixo que retorna o ndice do maior elemento em um vetor de inteiros.

102

#define DIM 5 int maior(int vet[]); void main() { int vetor[DIM]={1, 10, 7, 35, 4}; printf("Maior = %d\n", maior(vetor)); } int maior(int vet[]) int i, M; {

for(M=vet[0], i=1; i<DIM; i++) if(vet[i]>M) M=vet[i]; return M; }

Observe a declarao do vetor vet na funo maior():


int maior(int vet[]);

Voc notou os colchetes vazios? Reside a uma das grandes foras da linguagem C: no necessrio conhecer a dimenso do vetor vet em tempo de compilao. Por qu? Porque o compilador se satisfaz em saber que vet[] o endereo da primeira posio do vetor, uma vez que responsabilidade do programador no ultrapassar a dimenso do mesmo. E se quisermos passar uma matriz como argumento? Seja o seguinte programa que preenche uma matriz quadrada com 0's.

#define DIMX 5 #define DIMY 5 void preenche(int mat[][DIMY]); void main() { int mat[DIMX][DIMY]; preenche(mat); } void preenche(int mat[][DIMY]) int i, j; for(i=0; i<DIMX; i++) for(j=0; j<DIMY; j++) mat[i][j]=0; {

103

Observe o prottipo da funo preenche():


void preenche(int mat[][DIMY]);

Ela parece um pouco misteriosa, no ? Para entend-la vamos ver novamente a alocao em memria de uma matriz C de inteiros (short) de dimenso DIML x DIMC, onde DIML=3 e DIMC=2. Vamos supor que a matriz alocada a partir do endereo 1000.

1000 1002 1004 1006 1008 1010

C[0][0] C[0][1] C[1][0] C[1][1] C[2][0] C[2][1]

Vamos ver como o compilador calcula o endereo de um elemento genrico c[i][j]:


&c[i][j] == &c[0][0] + (i*DIMC + j) * sizeof(int);

Por exemplo, o endereo do elemento c[2][1] seria:


&c[2][1] == 1000 + (2*2 + 1) * 2 == 1010

Pela expresso acima fica claro que o compilador precisa conhecer o nmero de colunas da matriz. O nmero de linhas, assim como em vetores unidimensionais, no necessrio.

STRINGS
Em C no existe um tipo de dados string como no PASCAL. Ao contrrio, strings so implementadas como vetores de caracteres, terminados pelo caracter null ('\0'). Como em qualquer vetor, os caracteres da string podem ser individualmente acessados.
104

O caracter null serve como uma marca de fim de string para as funes que lidam com strings. Por exemplo:
main() { char nome[10]; nome[0] = 'N'; nome[1] = 'C'; nome[2] = 'E'; nome[3] = '\0'; puts(nome); }

A funo printf() imprime os caracteres armazenados a partir do endereo nome (Lembre-se: nome == &nome[0]) at que seja encontrado o caracter null. O caracter null ou '\0' tem valor 0 decimal. No confundir com o caracter '0' que tem valor 48 decimal.

Strings constantes Strings constantes so uma seqncia de caracteres entre aspas. J vimos vrios exemplos de strings constantes ao longo dessa apostila. Por exemplo:
printf("%s", "NCE");

Observe que voc no deve incluir o caracter null ao fim de uma string constante. O compilador faz isso por voc. Observe ainda a diferena entre 'c' e "c". No primeiro caso armazenado na memria apenas 1 byte correspondente ao caracter c. No segundo caso so armazenados dois bytes: o caracter c seguido do caracter null.

Inicializando strings Strings, assim como qualquer outro tipo de vetor, podem ser inicializadas em tempo de compilao com uma lista de valores entre chaves e separados por vrgulas.
char nome[]={'N','C','E','\0');

Vetores de caracteres podem ainda ser inicializados com strings constantes:


105

char

nome[]="NCE";

que equivalente inicializao anterior.

Vetores de strings Como strings em C so vetores de caracteres, vetores de strings so, na verdade, vetores de vetores de caracteres ou, matrizes de caracteres. Exemplo:
char nomes[][10]={ "Eduardo", "Andre", "Alexandre", "Debora", "Cecilia" };

Vamos reforar o conceito de que, em C, o nome de um vetor o endereo da primeira posio do mesmo.

char

pessoa[]="Eduardo";

Lembre-se: pessoa == &pessoa[0]. De modo equivalente, em matrizes como a do exemplo acima, temos:
nomes[0] == &nomes[0][0].

Pode-se entender nomes[0] como o nome do vetor representando a primeira linha da matriz. Assim, a instruo:
printf("O mais bonito e': %s.\n", nomes[0]);

imprimiria:
O mais bonito e' Eduardo.

Referncias a uma matriz usando apenas um ndice so equivalentes ao endereo das linhas da matriz.
106

Funes que manipulam strings


As bibliotecas de funes C incorporam uma srie de funes para lidar com strings. Veremos a seguir algumas das mais usuais:

strcpy()

<STRING.H>

Copia a string no endereo de origem para o endereo destino Declarao char *strcpy(char *dest, const char *src); Comentrios Copia a string src para a string dest caracter a caracter. A cpia termina quando o caracter nulo em src tiver sido copiado. Valor de Retorno O endereo apontado por dest. Exemplo
void main() { char String1[] = "No man is an island"; char String2[80]; strcpy(String2,String1); printf("String1: %s\n", String1); printf("String2: %s\n", String2); }

Sada
String1: No man is an island String2: No man is an island

strlen()

<STRING.H>

Calcula o comprimento de uma string. Declarao


107

size_t strlen(const char *s); Comentrios strlen() calcula o comprimento (nmero de caracteres) da string s. Valor de Retorno Retorna o nmero de caracteres em s. O caracter nulo ao final de s no includo na contagem. Exemplo
#include <stdio.h> #include <string.h> void main() { char *string = "Borland International"; printf("%d\n", strlen(string)); }

strcat() Concatena duas strings Declarao

<STRING.H>

char *strcat(char *dest, const char *src); Comentrios strcat() escreve uma cpia da string src no final da string dest. O tamanho da string resultante strlen(dest) + strlen(src). Valor de Retorno strcat() retorna um ponteiro para as strings concatenadas. Exemplo
void main() { char destination[25]; char *blank = " ", *c = "C++", *turbo = "Turbo"; strcpy(destination, turbo); strcat(destination, blank);

108

strcat(destination, c); printf("%s\n", destination); }

strcmp() Compara duas strings Declarao

<STRING.H>

int strcmp(const char *s1, const char*s2); Comentrios strcmp() realiza uma comparao entre as strings s1 e s2. A comparao comea com o primeiro caracter em cada string e continua com os caracteres subseqentes at que os caracteres correspondentes difiram ou o final de uma das strings seja alcanado.

Valor de Retorno strcmp() retorna um valor inteiro que :


< 0 == 0 > 0 se s1 < s2 se s1 == s2 se s1 > s2

Exemplo
void main() { char *buf1 = "aaa", *buf2 = "bbb"; int ptr; if ((ptr=strcmp(buf2, buf1))>0) printf("buf2 maior do que buf 1\n"); else printf("buf2 menor do que buf1\n"); }

strchr()

<STRING.H>

Procura a primeira ocorrncia de um dado caracter numa string. Declarao char *strchr(const char *s, int c);
109

Comentrios strchr() percorre uma string procurando pela primeira ocorrncia de um dado caracter. O caracter nulo considerado como parte da string; por exemplo, strchr(strs, 0) retorna um ponteiro para o caracter nulo que marca o fim da string strs. Valor de Retorno Em caso de sucesso retorna um ponteiro para a primeira ocorrncia do caracter c na string s. Em caso de erro (a string s no contm o caracter c) retorna null. Exemplo
void main() { char string[15]= "This is a string"; char *ptr, c = 'r'; ptr = strchr(string, c); if (ptr) printf("The character %c is at position: %d\n", c, ptr-string); else printf("The character was not found\n"); }

strstr()

<STRING.H>

Encontra a primeira ocorrncia de uma substring em outra string Declarao char *strstr(const char *s1, const char *s2); Comentrios strstr() percorre s1 procurando pela primeira ocorrncia da substring s2.

Valor de Retorno Em caso de sucesso, strstr() retorna um ponteiro para o caracter em s1 onde comea s2 (um ponteiro para s2 em s1). Em caso de erro (s2 no ocorre em s1), strstr() retorna null.
110

Exemplo
void main() { char *str1 = "Borland International"; char *str2 = "nation", *ptr; ptr = strstr(str1, str2); printf("The substring is: %s\n", ptr); }

Algoritmos de Busca
A necessidade de procurar uma informao numa tabela ou num catlogo muito comum. Por exemplo: procurar o telefone de uma pessoa no catlogo, ou o valor do IPVA de um automvel em uma tabela que d o valor do imposto em funo do modelo e do ano de fabricao do carro. Em processamento de dados, a tarefa de procurar , como se pode imaginar, uma das funes mais utilizadas. Por exemplo, consultar um terminal automtico para saber o valor de seu saldo, ou um sistema de registro acadmico para saber o seu histrico escolar. Como esta funo muito utilizada, importante desenvolver rotinas que a executem de forma eficiente. Por eficiente deve-se entender uma rotina que faa a busca no menor tempo possvel. Como veremos adiante, possvel detalhar com um pouco mais de preciso o conceito de eficincia porm, por enquanto, ficaremos com esta definio intuitiva. Sabemos que o tempo gasto procurando dados em tabelas depende, em primeiro lugar, do tamanho da tabela. Veja por exemplo o tempo gasto para se descobrir o telefone de um assinante na lista de So Paulo e faa a comparao com uma cidade do interior com 1.000 telefones. Alm disso, a eficincia do processo de busca depende fortemente do algoritmo empregado. O objetivo primordial desta seo apresentar um conjunto de rotinas para busca de informaes em tabelas. Alm disto, queremos mostrar uma maneira de fazer avaliaes sobre a eficincia destas rotinas. Deste modo, ao defrontar-se com um problema prtico, voc poder julgar se os algoritmos apresentados prestam-se ou no a esta dada situao. Para visualizar melhor os algoritmos envolvidos, vamos considerar apenas o caso de tabelas de nmeros inteiros. Nos casos prticos, em geral, cada elemento da tabela um registro (record), e procuramos um elemento desta tabela cujo campo chave tenha correspondncia com uma dada chave de busca. Imagine, por exemplo, uma tabela que contm o nome e as notas dos alunos de uma turma. O campo pesquisado pode ser o nome do aluno e a informao que procuramos sua nota.

111

A generalizao do processo para este tipo de estrutura pode ser feita facilmente depois que os princpios aqui expostos forem conhecidos.

Busca Seqencial
Neste mtodo, o processo de busca pesquisa a tabela seqencialmente, desde o seu incio. Cada elemento da tabela comparado com a chave. Se eles forem iguais, o ndice do elemento retornado e a busca termina. Se o algoritmo atingir o fim da tabela e a chave ainda no tiver sido encontrada, isto sinaliza que a busca falhou e que nenhum elemento da tabela tinha o valor da chave.

#define N 10 void main() int ind; int chave; int tab[N]; { /* retorna a posicao do elemento */ /* valor a ser procurado */ /* tabela a ser pesquisada */

puts("Entre com os valores da tabela"); for (ind=0; ind<N; ind++) scanf("%d", &tab[ind]); printf("Entre com o valor a ser procurado..."); scanf("%d", &chave); for (ind=0; (ind < N) && (tab[ind] != chave); ind++); if (ind < N) printf("Chave encontrada na posicao %d\n", ind); else printf("Chave nao encontrada\n"); }

Anlise: Quantas comparaes sero executadas para encontrar a chave? Depende de como os elementos so distribudos na tabela. Se esta distribuio for aleatria, podemos esperar desde encontrar a chave na primeira posio, at ter de percorrer toda a tabela. Dessa forma, em mdia, o algoritmo executa N/2 repeties do for. Diz-se ento, que o tempo de execuo do algoritmo da ordem de N/2 e escreve-se O(N/2). Este algoritmo pode ainda ser ligeiramente melhorado. Observe que para cada valor de ind, a rotina tem de fazer duas comparaes: a primeira para saber se (ind < N) e a segunda para testar se (tab[ind] != chave). Na verso seguinte do algoritmo, eliminaremos uma das comparaes.
112

Busca com Sentinela


A otimizao do algoritmo anterior d-se pela insero da chave procurada ao final da tabela (que , portanto, acrescida de um elemento). A busca termina quando a chave for encontrada, o que certamente ocorrer. Se isto ocorrer na ltima posio da tabela, a chave procurada no pertence tabela.

#define N 10 void main() { int ind; int chave; int tab[N+1]; puts("Entre com os valores da tabela"); for (ind=0; ind<N; ind++) scanf("%d", &tab[ind]); printf("Entre com o valor a ser procurado..."); scanf("%d", &chave); for (ind=0, tab[N]=chave; (tab[ind] != chave); ind++); if (ind < N) printf("Chave encontrada na posicao %d\n", ind); else printf("Chave nao encontrada\n"); }

Uma outra forma de otimizar o algoritmo de busca seqencial, ordenar os elementos do vetor em ordem crescente. Nesse caso, ao atingir-se um elemento da tabela maior do que a chave, a busca termina, indicando que a chave procurada no encontra-se na tabela. (Uma vez que todos os elementos at o fim da tabela so maiores do que a chave)

Tambm possvel, se for conhecida a freqncia com que cada um dos elementos da tabela ser procurado, armazenar os elementos em ordem decrescente da freqncia de busca. Garante-se assim que os elementos mais freqentemente acessados encontram-se nas primeiras posies do vetor e que, portanto, sero rapidamente localizados.

Busca Binria em Tabela Ordenada


Obviamente, ningum pensaria em fazer uma busca seqencial em uma lista de assinantes em cidades como o Rio de Janeiro ou So Paulo. No entanto, consegue-se localizar um
113

nome em alguns poucos segundos. A chave para a eficincia do algoritmo empregado vem do fato de que os nomes so listados em ordem alfabtica. O mtodo que, normalmente, as pessoas usam para procurar um nome no catlogo abrir o mesmo mais para o incio ou mais para o fim dependendo da inicial do assinante procurado. Se a pgina aberta contiver nomes que, alfabeticamente, vm depois do nome procurado, a "metade" direita descartada e a busca se limita "metade" esquerda. Esse processo de eliminao a base da busca binria. O nome binria vem do fato de que, a cada comparao, metade da tabela descartada. Imagine, por exemplo, que voc est procurando a palavra tarol (uma espcie de tambor) num dicionrio de 1500 pginas. Voc poderia aplicar a seguinte rotina: Abre-se o dicionrio aproximadamente ao meio e notamos que a entrada est na letra J - pg. 798. Como a letra T vem depois da letra J, podemos abandonar a primeira metade e procurar somente na parte final; Tomamos a metade final, dividimos ao meio novamente e encontramos a letra P (pg. 1106); Tomamos novamente a parte final e dividimos ao meio, caindo na letra R (pg. 1204); Dividimos ao meio e chegamos letra S (pg. 1318); Ao dividirmos novamente, chegamos entrada Tomo (pg. 1368). Portanto a palavra deve estar entre as pginas 1318 e 1386; Se continuarmos teremos sucessivamente as pginas 1353, 1343, 1347 e 1345.

Desta forma, dividimos a rea de pesquisa ao meio em cada passo. Caso o dicionrio tenha 1500 pginas e a palavra procurada nos leve ao pior caso, onde a rea de pesquisa reduzida a cada comparao at conter uma nica pgina, o nmero de comparaes necessrias seria:

114

Comparao 1 2 3 4 5 6 7 8 9 10 11

Pginas Restantes 1500/2 750 750/2 375 376/2 188 188/2 94 94/2 47 47/2 23 24/2 12 12/2 6 6/2 3 3/2 1 2/2 1

Vemos que, no pior caso, 11 pesquisas sero necessrias. Na verdade este nmero pode ser expresso pela equao log2 N (logaritmo na base 2 de N). O logaritmo de um nmero tem a propriedade de crescer muito menos rapidamente do que o nmero. Por exemplo, uma pesquisa em tabela com 32.000 itens, precisa de somente 15 comparaes. A cada vez que dobramos o tamanho da tabela, necessitamos de apenas mais uma pesquisa. No programa abaixo, incio, meio e fim so os marcadores da rea de pesquisa. Caso o elemento pesquisado seja menor do que o elemento procurado, fazemos com que o marcador incio seja igual a meio + 1, caso contrrio, fazemos com que fim seja igual a meio - 1.

#define N 10 void main() { int ind; int chave; int tab[N]; int inicio=0; int fim=N-1; int meio=(inicio+fim)/2;

/* /* /* /* /*

valor a ser procurado */ tabela a ser pesquisada */ inicio da area de pesquisa */ fim da area de pesquisa */ indice de pesquisa */

puts("Entre com os valores da tabela em ordem crescente"); for (ind=0; ind<N; ind++) scanf("%d", &tab[ind]); printf("Entre com o valor a ser procurado..."); scanf("%d", &chave); while ((inicio <= fim) && (tab[meio] != chave)) if (tab[meio] < chave) inicio = meio + 1; else fim = meio - 1; meio = (inicio+fim)>>1; /* divisao por 2 */ {

115

} if (tab[meio] == chave) printf("Chave encontrada na posicao %d\n", meio); else printf("Chave nao encontrada\n"); }

O algoritmo de busca binria da ordem de O(log N).

Algoritmos de Ordenao
A ordenao, da mesma forma que a busca, uma das tarefas bsicas em processamento de dados. Ordenar uma tabela consiste em fazer com que os elementos desta tabela sejam armazenados de acordo com um critrio de ordenao. Este critrio pode ser muito variado dependendo do tipo dos elementos que estejam sendo ordenados. Nos casos mais simples, como os que sero examinados aqui, os elementos da tabela so nmeros inteiros, e os critrios de ordenao se resumem ordem crescente ou decrescente dos valores da tabela. Ordem crescente:
tab[i] >= tab[j] se i > j

Ordem decrescente:
tab[i] < = tab[j] se i > j

Os algoritmos de ordenao podem ser classificados de acordo com sua eficincia em elementares ou avanados. Isto se deve ao fato de que existe uma grande disparidade entre os diversos mtodos de ordenao. Alguns algoritmos apresentam um desempenho excepcional quando comparados com os mtodos mais simples. Vamos comear examinando a Ordenao pelo Mtodo da Seleo.

Mtodo da Seleo
Talvez, o mtodo mais intuitivo de ordenar uma tabela de nmeros inteiros em ordem crescente, seja procurar o menor nmero e coloc-lo na primeira posio. Em seguida
116

procuramos novamente o menor entre os nmeros restantes e o armazenamos na segunda posio e assim por diante. Nisto consiste a essncia do mtodo da seleo. Para melhor acompanhar o algoritmo, vamos examinar um exemplo com um vetor de 8 elementos. tab = [46 15 91 59 62 76 10 93] Desejamos colocar este vetor em ordem crescente. Para saber quem fica na posio 1 podemos varrer o vetor da posio 2 at a 8, descobrir o menor elemento (no caso, 10 na posio 7) e comparar com o elemento que encontra-se na primeira posio. Se aquele for menor do que este trocamos as posies dos dois elementos. Desta forma, ao final do primeiro passo, o primeiro elemento estar posicionado.
tab = [10 15 91 59 62 76 46 93]

Para posicionar o segundo elemento procuramos pelo menor elemento entre a terceira posio e o final do vetor. Encontramos o nmero 46 e comparamos com o elemento da segunda posio (15), no efetuando-se a troca. Repete-se o processo at a penltima posio do vetor, pois a ltima ficar automaticamente posicionada. O processo, na sua totalidade, apresentado na seqncia abaixo.
Incio passo 1 passo 2 passo 3 passo 4 passo 5 passo 6 passo 7 passo 8 [46 [10 [10 [10 [10 [10 [10 [10 [10 15 15 15 15 15 15 15 15 15 91 91 46 46 46 46 46 46 46 59 59 59 59 59 59 59 59 59 62 62 62 62 62 62 62 62 62 76 76 76 76 76 76 76 76 76 10 46 91 91 91 91 91 91 91 93] 93] 93] 93] 93] 93] 93] 93] 93]

O algoritmo, numa primeira verso, pode ser escrito na forma:


1. #define N 10 2. void main() { 3. int 4. ind1, ind2, 5. aux, 6. tab[N]; 7. 8. 9. 10.

/* marcadores */ /* varivel auxiliar */ /* tabela a ser pesquisada */

puts("Entre com os valores da tabela"); for (ind1=0; ind1<N; ind1++) scanf("%d", &tab[ind1]); for (ind1=0; ind1<N-1; ind1++)

117

11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.}

for (ind2=ind1+1; ind2<N; ind2++) if (tab[ind1] > tab[ind2]) { aux = tab[ind2]; tab[ind2] = tab[ind1]; tab[ind1] = aux; } puts("\nO vetor ordenado e':"); for (ind1=0; ind1<N; ind1++) printf("%d ", tab[ind1]); printf("\n");

Sada:
Entre com os valores da tabela 9 2 3 8 4 7 5 10 6 1 O valor ordenado : 1 2 3 4 5 6 7 8 9 10

Anlise: O corao do algoritmo encontra-se nas linhas 10-15. Em cada passo, o elemento na posio ind1 preenchido com o valor correto. Cada vez que o elemento em ind2 for menor do que o elemento na posio ind1, estes dois elementos trocam de posio.

O algoritmo acima pode ser refinado para chegarmos ao programa final apresentado a seguir.
1. #define N 10 2. main() { 3. int 4. ind1, ind2, 5. tab[N]; 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

/* marcadores */ /* tabela a ser pesquisada */

puts("Entre com os valores da tabela"); for (ind1=0; ind1<N; ind1++) scanf("%d", &tab[ind1]); for (ind1=0; ind1<N-1; ind1++) { int aux, /* variavel auxiliar para a troca */ indmin; /* posicao do menor elemento */ for (indmin=ind1, ind2=ind1+1; ind2<N; ind2++) if (tab[indmin] > tab[ind2]) indmin = ind2; aux = tab[indmin];

118

17. 18. 19. 20. 21. 22. 23.}

tab[indmin] = tab[ind1]; tab[ind1] = aux; } puts("\nO vetor ordenado e':"); for (ind1=0; ind1<N; ind1++) printf("%d\n", tab[ind1]);

Anlise: A otimizao introduzida neste algoritmo consiste em no efetuar a troca de posies toda vez que o elemento em ind2 for menor do que o elemento em ind1. Ao invs disso, armazena-se a posio do menor elemento encontrado em cada passo e procede-se a troca de posies apenas uma vez, ao final do passo.

Podemos analisar a eficincia deste algoritmo da seguinte maneira: o esforo computacional gasto num processo de ordenao est nas suas operaes bsicas que so as comparaes e as trocas de posies de elementos. A cada passagem do for mais externo feita uma troca, num total de (N-1) trocas. O nmero de comparaes depende do passo que estamos executando. No primeiro passo temos 9 comparaes, no segundo 8, no terceiro 7 at que, no ltimo passo, temos somente 1 comparao. O nmero de comparaes ento a soma dos nmeros inteiros de 1 at 9. Para o caso geral, num vetor com N elementos, o nmero de comparaes igual a soma dos N-1 primeiros inteiros. Isto pode ser expresso pela frmula: Nc = ( N 1) * N 2 Da frmula acima podemos notar que o nmero de comparaes domina o nmero de trocas, e proporcional ao quadrado de N. Dizemos ento que a eficincia do algoritmo quadrtica (O(n2)). Isto significa que se N for dobrado ou triplicado, o tempo de processamento multiplicado por um fator de 4 ou 9 respectivamente. Obviamente, necessitamos de algoritmos mais rpidos para valores grandes de N (talvez at para valores pequenos). O algoritmo da prxima seo apresenta uma evoluo nesse sentido.

Mtodo da bolha
O mtodo da bolha deriva o seu nome da maneira com que os maiores valores afundam em direo ao fim do vetor enquanto que os menores valores borbulham em direo superfcie ou topo da tabela.
119

Este mtodo consiste em comparar-se cada elemento com o seguinte, comeando com os dois primeiros e terminando nos dois ltimos. Se o elemento tab[ind1+1] for menor do que o elemento tab[ind1], os dois elementos so trocados de posio. Se, ao final de uma varredura, alguma troca houver sido efetuada, o processo se repete. No programa a seguir usa-se a varivel troquei para indicar se durante um passo foi executada alguma troca.

#define N 10 enum BOOL {FALSE, TRUE}; void int int int BOOL main() { ind1, ind2; /* marcadores */ tab[N]; /* tabela a ser pesquisada */ aux; /* variavel auxiliar para a troca */ troquei; /* flag de parada */

puts("Entre com os valores da tabela"); for (ind1=0; ind1<N; ind1++) scanf("%d", &tab[ind1]); ind2=N-1; do { troquei=FALSE; for (ind1=0; ind1<ind2; ind1++) if (tab[ind1] > tab[ind1+1]) aux=tab[ind1]; tab[ind1]=tab[ind1+1]; tab[ind1+1]=aux; troquei=TRUE; } ind2--; } while (troquei); puts("\nO vetor ordenado e':"); for (ind1=0; ind1<N; ind1++) printf("%d ", tab[ind1]); printf("\n"); }

Se o algoritmo for aplicado a uma tabela ordenada s necessrias N comparaes e o nenhuma troca para chegar ao fim do procedimento. Este o melhor caso e o tempo de processamento proporcional a N. Se, no pior caso, o algoritmo for aplicado a uma tabela com os elementos em ordem decrescente, seriam necessrias (N-1) comparaes e (N-1) trocas no primeiro passo, (N2) comparaes e trocas no segundo passo, e assim por diante. O nmero total de comparaes e trocas seria:
120

( N 1) + ( N 2 )+K+1 = N * ( N 1)

Logo, no pior caso, o mtodo da bolha da ordem de O(N2) da mesma forma que o mtodo da seleo. No entanto, o mtodo da seleo sempre da ordem de O(N 2) enquanto que o mtodo da bolha varia de O(N) para tabelas j ordenadas at O(N 2) para tabelas em ordem decrescente.

121

CAPTULO 10 - Ponteiros
A maioria das variveis vistas at agora armazenavam dados, isto , a informao manipulada pelo programa na sua forma final. Algumas vezes, no entanto, necessitamos saber onde uma varivel foi armazenada ao invs do seu contedo. Para isso, precisaremos de ponteiros.

Introduo
O computador armazena o cdigo e as variveis dos programas escritos por voc na memria. A memria constituda, no seu nvel mais baixo, por bits , circuitos eletrnicos capazes de armazenar dois valores normalmente associados aos nveis lgicos 0 e 1. O computador enxerga a memria como uma seqncia de bytes (grupos de 8 bits), cada um dos quais com um endereo nico. Os dados so dispostos seqencialmente na memria de modo que, se, por exemplo, o primeiro byte de uma varivel inteira for armazenado no endereo N, o byte seguinte ser armazenado no endereo N+1 e assim por diante. Um ponteiro uma varivel que, no seu espao de memria, armazena o endereo de uma segunda varivel, essa sim, normalmente, contendo o dado a ser manipulado pelo programa. Seja, por exemplo, um programa que contenha duas variveis inicializadas de alguma forma e dispostas seqencialmente na memria: a, uma varivel inteira contendo o valor 5 e ptr, um ponteiro para a varivel a. Suponha que as variveis sejam criadas a partir do endereo 1000. O contedo da memria ser:

1000

a==5

1004 ptr==1000 1008 1012

Observe que o contedo da varivel ptr o endereo da varivel a.


122

Por que usar ponteiros? Em primeiro lugar porque um nico ponteiro permite que sejam acessados diferentes dados em diferentes posies de memria bastando, para isso, trocar o endereo armazenado na varivel do tipo ponteiro. Alm disso, o uso de ponteiros permite que sejam criadas variveis enquanto o programa est executando. Existem em C funes que retornam o endereo de uma rea de memria ainda no utilizada pelo computador4. Esse endereo pode ento ser atribudo a um ponteiro que, dessa forma, passa a apontar para uma varivel criada dinamicamente ou em tempo de execuo. O tamanho do bloco de memria requisitado especificado na chamada funo. Assim, um programa pode, por exemplo, criar um vetor com a dimenso exata da necessidade da aplicao, evitando-se assim o risco de super ou subdimensionar o problema. Ponteiros fornecem ainda um mecanismo pelo qual as funes podem retornar mais de um valor, permitem um acesso mais rpido aos elementos de vetores e matrizes e possibilitam a criao de estruturas de dados complexas tais como listas encadeadas e rvores binrias, onde cada elemento da estrutura deve apontar para outros elementos da mesma estrutura. elemento 1 elemento 2 elemento 3

Usando Ponteiros
Agora voc j deve estar convencido da utilidade de ponteiros. Como us-los em C? Antes de mais nada, assim como qualquer outra varivel, eles precisam ser declarados. A forma geral da declarao de um ponteiro : tipo *ptipo;

O operador (*) chamado em C de operador indireto e indica ao compilador que a varivel que o segue um ponteiro. A declarao acima deve ser lida da seguinte forma: ptipo um ponteiro para uma varivel do tipo tipo. Exemplo: int *pint; pint um ponteiro para uma varivel do tipo inteiro.

Esta rea de memria recebe o nome de heap

123

Considere o seguinte programa: 1. main() { 2. int i; 3. int *ptr; 4. 5. 6. ptr = &i; *ptr = 3; }

Para entender o funcionamento do programa, vamos acompanhar a evoluo da pilha instruo a instruo. Suponha que as variveis do programa so carregadas a partir do endereo 1000. Declarao: int i; int *ptr;

?
ptr:main i:main

? ? ...

996 1000

Observe que a simples declarao do ponteiro no inicializa ptr com nenhum valor em particular. O ponteiro precisa ser explicitamente inicializado antes de ser utilizado.

1 instruo: ptr = &i;

?
ptr:main i:main

1000 ? ...

996 1000

124

O operador (&) usado para atribuir o endereo de i a ptr. Ou, em outras palavras, ptr passa a apontar para i. 2 instruo: *ptr = 3;

?
ptr:main i:main

1000 3 ...

996 1000

Observe que o operador de indireo tem duas leituras diferentes: quando usado na declarao de uma varivel do tipo ponteiro e, no corpo do programa, quando usado para referenciar o contedo de uma posio de memria. Assim, a declarao na linha 3 deve ser lida como: ptr um ponteiro para o tipo inteiro. J a instruo na linha 5 deve ser lida como: A posio de memria apontada por ptr recebe o inteiro 3. A instruo na linha 5 seria equivalente a: i = 3; Por qu? Porque i e *ptr referem-se ao mesmo endereo de memria de modo que ambas as instrues atribuem o valor 3 ao endereo 1000. importante entender que ponteiros so variveis como quaisquer outras variveis. Algumas variveis so apropriadas para armazenar no seu espao de memria nmeros inteiros (variveis inteiras). Outras so prprias para armazenar nmeros em ponto flutuante (variveis float) ou caracteres (variveis char). De forma semelhante, existem variveis prprias para armazenar endereos: as variveis do tipo ponteiro. Assim como qualquer outra varivel, as variveis do tipo ponteiro tem tambm um contedo e um endereo. Observe o programa abaixo. Esteja certo de no prosseguir antes de compreender as diferenas entre os valores impressos pelos 3 printf(). 1. 2. 3. main() { int i; int *ptr;
125

4. 5. 6. 7. 8. 9. Sada:

ptr = &i; *ptr = 3; printf( ptr = %u\n, ptr); printf(&ptr = %u\n, &ptr); printf(*ptr = %d\n, *ptr); }

ptr = 1000 &ptr = 996 *ptr = 3

Alocao Dinmica
Pode-se alocar memria para os dados do programa dinamicamente, em tempo de execuo. Esta memria alocada pelo sistema operacional em uma regio de memria conhecida como heap. Assim, atravs de funes especficas, o programa pede ao sistema operacional a quantidade de memria desejada no heap. O sistema operacional reserva esta quantidade de memria para a aplicao (se houver tal quantidade de memria disponvel) e marca os bytes alocados a fim de que, em chamadas futuras, o seu endereo no seja atribudo a outros ponteiros. Vamos estudar as funes para alocao dinmica de memria. calloc() Aloca memria no heap Declarao void *calloc(unsigned nitems, unsigned size); Comentrios calloc() prov acesso memria heap. O heap usado para a alocao dinmica de blocos de memria de tamanho varivel. Diversas estruturas de dados tais como rvores e listas encadeadas fazem uso de reas de memrias alocadas dinamicamente no heap.
calloc() aloca um bloco de memria de tamanho (nitems*size) bytes e

<STDLIB.H>

preenche o seu contedo com zeros. Valor de Retorno Em caso de sucesso, calloc() retorna um ponteiro para a rea recm alocada.
126

Em caso de falha (no existe espao suficiente para o bloco de memria requisitado ou nitems ou size igual a 0), retorna null. Exemplo
#include <stdio.h> #include <stdlib.h> void main() int *ptr; {

ptr=(int *)calloc(1, sizeof(int)); *ptr = 3; printf("%d\n", *ptr); }

Neste exemplo a funo calloc() chamada para criar dinamicamente uma rea de memria no heap a qual acessada atravs do ponteiro ptr. Vamos estudar em detalhes cada uma das partes componentes da chamada funo calloc(). O operador sizeof() Uso: sizeof(tipo) ou sizeof(expresso) Devolve o tamanho em bytes do tipo ou da expresso entre parnteses. Exemplos:
sizeof(int) == 2 sizeof(float) == 4

A converso forada de tipos Uso: (tipo)nome Converte a varivel nome para o tipo entre parnteses. Exemplos:
(float)i //converte a varivel i para o tipo float (int *)calloc(2) //converte o ponteiro retornado pela funo calloc() //em um ponteiro para inteiro.

127

A converso forada de tipo necessria porque o ponteiro retornado por calloc() de tipo indefinido ou void. A inicializao de um ponteiro realmente necessria? Sim. Lembre-se que a declarao de um ponteiro no inicializa o contedo do mesmo. Desta forma, o efeito de atribuir um valor a *ptr sem antes inicializar o ponteiro imprevisvel uma vez que podemos alterar o contedo de alguma outra varivel do programa ou mesmo sujar o cdigo do programa. A regra para usar ponteiros simples: nunca acesse o contedo de uma posio de memria referenciada por um ponteiro sem antes inicializar o mesmo, ou, em outras palavras, atribua sempre um endereo a um ponteiro antes de us-lo.

malloc() Aloca memria no heap Declarao

<STDLIB.H>

void *malloc(unsigned size); Comentrios malloc() aloca um bloco de tamanho size bytes no heap. Ela permite que uma aplicao aloque memria em tempo de execuo, na medida exata da necessidade da aplicao. O heap usado para a alocao dinmica de blocos de memria de tamanho varivel. Diversas estruturas de dados tais como rvores e listas encadeadas fazem uso de reas de memrias alocadas dinamicamente no heap. Valor de Retorno Em caso de sucesso, malloc() retorna um ponteiro para a rea recm alocada. Em caso de erro (no existe espao suficiente para o bloco de memria requisitado) malloc() retorna null. Exemplo
void main() int *pAge; {

pAge=(int *)malloc(sizeof(int)); *pAge = 5; }

128

free()

<STDLIB.H>

free() libera os blocos de memria alocados no heap. Declarao void free(void *block); Comentrios free() libera os blocos de memria alocados no heap atravs de chamadas prvias s funes calloc() e malloc(). Valor de Retorno Nenhum

Quando uma rea de memria alocada dinamicamente deixa de ser necessria ela deve ser liberada atravs de uma chamada funo free( )

O exemplo a seguir mostra a utilizao das funes malloc(), calloc() e free().


1. 2. 3. 4. void main() { int Local = 5; int *pLocal= &Local; int *pHeap=(int *)calloc(1, sizeof(int)); if (pHeap == NULL) { puts("No memory for pHeap!!"); return; } *pHeap = 7; printf("Local: %d\n", Local); printf("*pLocal: %d\n", *pLocal); printf("*pHeap: %d\n", *pHeap); free(pHeap); pHeap = (int *)malloc(sizeof(int)); if (pHeap == NULL) { puts("No memory for pHeap!!"); return; } *pHeap = 9;

5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.

129

20. 21. 22.}

printf("*pHeap: %d\n", *pHeap); free(pHeap);

Sada
Local: 5 *pLocal: 5 *pHeap: 7 *pHeap: 9

Anlise Na linha 2 a varivel Local declarada e inicializada com o valor 5. Na linha 3 declarado o ponteiro pLocal e o seu contedo inicializado com o endereo da varivel Local. Na linha 4 declarado o ponteiro pHeap e o seu contedo inicializado com o endereo de um bloco de memria no heap obtido atravs de uma chamada funo calloc(). Este bloco de memria tem tamanho suficiente para armazenar um nico nmero inteiro5. Na linha 5 realizado um teste para verificar se o bloco de memria no heap foi alocado com sucesso. Uma vez que existe a possibilidade de erro nas chamadas s funes calloc() e malloc() (por exemplo, se o bloco de memria requisitado for muito grande), qualquer aplicao sria deveria verificar o valor retornado por tais funes. Na linha 9 o bloco de memria no heap inicializado com o valor 7. Observe que a varivel Local e o ponteiro pLocal referem-se mesma posio de memria, o que pode ser constatado pela seqncia de printf() nas linhas 10-12. Na linha 13 o bloco de memria no heap liberado. Na linha 14 o ponteiro pHeap usado para alocar um novo bloco no heap. O endereo atribudo a pHeap atravs da chamada funo malloc() no necessariamente o endereo do bloco liberado anteriormente pela chamada funo free(). Observe a diferena entre o acesso memria usando variveis e ponteiros. Como vimos anteriormente, o nome de uma varivel corresponde a um a pelido de uma posio de memria. No entanto, esta associao entre o nome da varivel e o endereo de memria permanece fixa durante toda a execuo do programa 6. O endereo de memria referenciado por um ponteiro, ao contrrio, pode variar durante a execuo do programa podendo apontar para diferentes regies de memria.

Ponteiros e Funes

Veremos posteriormente que o programador poderia referir-se ao bloco recm alocado como pHeap[0]. Nada impediria que ele referenciasse, por exemplo, a posio pHeap[1]. O resultado deste acesso, no entanto, traria conseqncias imprevisveis uma vez que o bloco de memria alocado tem tamanho suficiente para armazenar um nico nmero inteiro. 6 Pode-se pensar, portanto, em uma varivel como um ponteiro constante para um dado endereo de memria.

130

Quando estudamos funes, vimos que o mecanismo pelo qual uma funo retorna um valor atravs do uso do comando return. Mas, e se quisermos que a funo chamada retorne mais de um valor? Essa uma situao que ocorre com muita freqncia na prtica. Suponha, por exemplo, que a troca de posio entre dois elementos no mtodo de ordenao da bolha fosse efetuada por uma funo troca(). Seja a seguinte implementao (ERRADA) onde os parmetros so passados por valor.
do { troquei = FALSE; for (ind1=0; ind1<ind2; ind1++) if (tab[ind1] > tab[ind1+1]) { troca(tab[ind1], tab[ind1+1]); troquei = TRUE; } ind2--;

} while (troquei); . . void troca(int a, int b) int aux = a; a = b; b = aux; } { /* ERRADO */

Para entender porque o programa acima no funciona vamos estudar a evoluo da pilha durante a execuo do mesmo. Suponha por exemplo que (tab[ind1]==2) e (tab[ind1+1]==3). antes da chamada de troca()

994 998 1002

... ...
tab[ind1] tab[ind1+1]

1006

SP

2 3

1026 1030

131

Na chamada funo troca()

aux:troca b:troca a:troca

994

SP

3 2 ... ...

998 1002 1006

tab[ind1] tab[ind1+1]

2 3

1026 1030

No retorno ao programa principal:

aux:troca b:troca a:troca

2 2 3 ... ...

994 998 1002 1006


SP

tab[ind1] tab[ind1+1]

2 3

1026 1030

Observe que a funo troca() no altera os valores de tab[ind1] e tab[ind1+1]. Isto se d porque a funo trabalha sobre o contedo das suas prprias variveis, criadas na pilha. A soluo para esse problema consiste em o programa principal passar os endereos das variveis cujo contedo deve ser permutado.
troca(&tab[ind1], &tab[ind1+1]);

132

Lembre-se que se a funo troca() vai receber os endereos dos argumentos a serem trocados, ela deve armazenar estes endereos em variveis prprias para armazenar endereos, ou seja, ponteiros. As variveis a serem trocadas so ento acessadas atravs dos ponteiros para as mesmas.
void troca(int *a, int *b) int aux = *a; *a = *b; *b = aux; } {

Vamos estudar novamente a evoluo da pilha: antes da chamada de troca():

994 998 1002

... ...
tab[ind1] tab[ind1+1]

1006

SP

2 3

1026 1030

Na chamada funo troca()

aux:troca b:troca a:troca

994

SP

1030 1026 ... ...

998 1002 1006

tab[ind1] tab[ind1+1]

2 3

1026 1030

133

aux = *a;
aux:troca b:troca a:troca

2 1030 1026 ... ...

994 998 1002 1006

SP

tab[ind1] tab[ind1+1]

2 3

1026 1030

*a = *b;

aux:troca b:troca a:troca

2 1030 1026 ... ...

994 998 1002 1006

SP

tab[ind1] tab[ind1+1]

3 3

1026 1030

*b = aux;

aux:troca b:troca a:troca

2 1030 1026 ... ...

994 998 1002 1006

SP

tab[ind1] tab[ind1+1]

3 2

1026 1030

134

No retorno ao programa principal:

aux:troca b:troca a:troca

2 1030 1026 ... ...

994 998 1002 1006


SP

tab[ind1] tab[ind1+1]

3 2

1026 1030

Observe que a funo troca() efetivamente alterou o valor das variveis tab[ind1] e tab[ind1+1]. Isto foi possvel devido passagem dos endereos dos argumentos para a funo. A esse mecanismo de passagem de parmetros, usando ponteiros, d-se o nome de passagem de parmetros por referncia. J tnhamos visto antes passagem de parmetros por referncia quando estudamos a funo scanf(). Os argumentos para a funo scanf() so os endereos das variveis a serem lidas. Desta forma, os valores so lidos de standard input e armazenados nos endereos das variveis passadas como argumentos.

Aritmtica com Ponteiros


De modo a possibilitar um acesso rpido aos dados do programa, ponteiros podem ser incrementados e decrementados. Seja o seguinte exemplo:
1. 2. 3. 4. 5. 6. 7. void main() { int vetor[3]; int *p1, *p2; int i; p1 = vetor; p2 = &vetor[2]; *p1 = 0;

135

8. 9. 10. 11. 12. 13. 14.}

*(p1+1) = 1; *(p1+2) = 2; for(i=0; i<3; i++) printf("vetor[%d] = %d\n", i , vetor[i]); if (p2 > p1) printf("Posicoes: %d\n", p2-p1);

Sada:
vetor[0] = 0 vetor[1] = 1 vetor[2] = 2 Posicoes: 2

Anlise: Este programa contm as operaes bsicas que podem ser efetuadas em ponteiros: Atribuio Observe as duas primeiras instrues:
p1 = vetor; p2 = &vetor[2];

Elas j so nossas conhecidas. Consistem na atribuio de endereos, ou inicializao, dos ponteiros p1 e p2. O endereo atribudo , normalmente, o endereo de alguma varivel do programa ou obtido dinamicamente atravs de chamadas s funes malloc() e calloc().

Acessando os endereos apontados por ponteiros


*p1 = 0;

Esta instruo tambm familiar. Ela simplesmente diz: armazene o inteiro 0 na posio de memria apontada por p1.

Incrementando ponteiros importante entender a instruo seguinte:


*(p1+1) = 1;

136

Vamos observar o contedo da memria at este instante. Suponha que as variveis foram carregadas a partir do endereo 1000.

137

i p2 p1 vetor[0] vetor[1] vetor[2]

? 1020 1012 0 ? ?

1000 1004 1008 1012 1016 1020

SP

primeira vista voc poderia imaginar que a instruo


*(p1+1) = 1

armazena o inteiro 1 no byte seguinte ao endereo apontado por p1 (1012). Isso causaria uma grande confuso uma vez que vetor[0] um inteiro (o qual ocupa 4 bytes) e o seu contedo seria assim destrudo. Felizmente, o compilador C esperto o suficiente para perceber que p1 um ponteiro. Assim, quando incrementamos um ponteiro, o valor efetivamente acrescentado ao contedo do ponteiro no a unidade, mas sim, o tamanho do tipo apontado por este ponteiro. Desta forma, a expresso (p1+1) refere-se ao endereo:
p1 + 1 * sizeof(int) == 1012 + 1*4 == 1016 // endereo de vetor[1]

De maneira semelhante, (p1+2) refere-se ao endereo


p1 + 2 * sizeof(int) == 1012 + 2*4 == 1020 // endereo de vetor[2]

Sendo assim, a instruo:


*(p1+2) = 2;

armazena o inteiro 2 na posio ocupada por vetor[2]. De maneira geral, se ptr um ponteiro para tipo, ento a expresso (ptr + i) referese ao endereo:
ptr + i * sizeof(tipo)

138

Comparaes entre ponteiros O programa anterior mostra ainda a possibilidade de comparar ponteiros numa expresso:
if (p2 > p1)

Testes relacionais so aceitos somente quando os operandos so ponteiros do mesmo tipo.

Subtrao de ponteiros No caso da subtrao de dois ponteiros, o resultado ser dado pelo nmero de elementos do tipo apontado existentes entre os ponteiros. Assim,
printf("Posicoes: %d\n", p2 - p1);

imprime o decimal 2, uma vez que entre o endereo apontado por p2 (1020) e o endereo apontado por p1 (1012) existem 2 inteiros (vetor[0] e vetor[1]).

Ponteiros e Vetores
Existe uma forte relao em C entre ponteiros e vetores. Como j vimos, o nome de um vetor o endereo da primeira posio desse vetor ou, em outras palavras, o nome de um vetor um ponteiro para esse vetor. De fato, voc pode usar o nome de um vetor como se ele fosse um ponteiro, da mesma forma que voc pode usar um ponteiro como se ele fosse um vetor. Sejam, por exemplo, as seguintes declaraes:
int list[10];

ou,
int *list=(int *)malloc(10*sizeof(int));

Em ambos os casos possvel escrever: a) Referncia a endereo:


(list+i) &list[i] // equivalente a &list[i] // equivalente a (list+i)

b) Referncia a contedo:
139

*(list+i) list[i]

// equivalente a list[i] // equivalente a *(list+i)

Os grupos de expresses acima so equivalentes, isto , voc pode usar uma no lugar da outra, independente de list ter sido declarado como um ponteiro ou como um vetor. Para ilustrar a relao entre ponteiros e vetores, vamos examinar duas verses de um programa para imprimir o contedo de um vetor. A primeira delas usa notao de vetores para percorrer os elementos do mesmo, enquanto que a segunda usa o nome do vetor como um ponteiro. Usando notao de vetor:
main() { int list[]={1, 2, 3, 4, 5}; int ind; for (ind=0; ind<5; ind++) printf("%d\n", list[ind]); }

Usando ponteiros:
main() { int list[]={1, 2, 3, 4, 5}; int ind; for (ind=0; ind<5; ind++) printf("%d\n", *(list+ind)); }

Existem, no entanto, diferenas significativas entre declarar list como um ponteiro ou como um vetor. A primeira delas consiste em que, se voc declarar list como um vetor, o compilador automaticamente reserva o espao em memria necessrio para armazen-lo. Ao contrrio, se voc declarar list como um ponteiro, ser necessrio obter o espao em memria (atravs, por exemplo, de uma chamada funo malloc()) ou ento, atribuir a list o endereo de alguma varivel ou estrutura do programa. No exemplo anterior, se list fosse declarado como um ponteiro teramos:
main() { int *list=(int *)calloc(5, sizeof(int)); int ind; list[0] = 1; list[1] = 2; list[2] = 3; list[3] = 4; list[4] = 5; for (ind=0; ind<5; ind++)

140

printf("%d\n", list[ind]); }

Esta rotina usa a funo calloc() para reservar espao no heap. Assim, depois da atribuio, list aponta para um espao de memria de 20 bytes (5*sizeof(int)), suficiente para armazenar 5 nmeros inteiros. Outra importante diferena entre vetores e ponteiros que o nome de um vetor um ponteiro constante e, portanto, no pode ter o seu valor alterado. Baseado nisso, as formas abaixo no so vlidas:
main() { int list[5]; int i; list = &i; // ERRADO list++; // ERRADO }

Ponteiros variveis fornecem ainda uma maneira eficiente de percorrer seqencialmente os elementos de um vetor. Seja o seguinte exemplo:
main() { int list[]={1, 2, 3, 4, 5}; int *pint; int ind; for (pint=list, ind=0; ind<5; ind++, pint++) printf("%d\n", *pint); }

Observe que para acessar o elemento list[ind] usando a notao de vetores, o compilador teria de gerar cdigo para calcular o endereo:
&list[ind] == list + ind * sizeof(int)

No exemplo anterior tira-se proveito do fato de que o vetor percorrido seqencialmente, de modo que:
&list[ind] == pint == pint + sizeof(int))

Observe que deixamos de fazer uma multiplicao a cada acesso7.

Ainda que, em geral, estas multiplicaes sejam implementadas com deslocamentos da palavra esquerda (shift left), uma operao executada de forma bastante rpida em qualquer CPU.

141

Vetores passados como argumentos


Vimos anteriormente que, quando um vetor passado como um argumento para uma funo, somente o endereo do mesmo efetivamente passado. Isso se deve a questes de eficincia, uma vez que a passagem de um vetor por valor implicaria na cpia dos elementos do mesmo na pilha, a cada chamada funo. Vamos reescrever o exemplo anterior, dessa vez usando uma funo para imprimir os elementos do vetor.
main() { int list[]={1, 2, 3, 4, 5}; imprime(list, 5); }

Uma vez que o argumento passado para a funo imprime() o endereo do vetor list, os dois prottipos abaixo so equivalentes:
void imprime(int vetor[], int dim);

ou:
void imprime(int *vetor, int dim);

A nica diferena consiste em que, no primeiro caso, vetor um ponteiro constante e, portanto, no pode ter o seu valor alterado, enquanto que, no segundo caso, poderamos escrever:
void imprime(int *vetor, int dim) int i; for (i=0; i<dim; i++, vetor++) printf("%d\n", *vetor); } {

Vetores de ponteiros versus matrizes


Observe as declaraes:
1. int vetor1[500]

2. int *vetor2[500]; 3. int *vetor3=(int *)malloc(500*sizeof(int));

142

vetor3 uma variao de vetor1 mas muito diferente de vetor2. vetor1 um vetor esttico de 500 nmeros inteiros. O espao de memria reservado para o vetor alocado em tempo de compilao. De modo semelhante, vetor3 tambm

um vetor de 500 nmeros inteiros. No entanto, o espao de memria ocupado pelo vetor alocado de forma dinmica, em tempo de execuo do programa. vetor2 tambm um vetor de 500 posies alocadas em tempo de compilao. No entanto, cada um dos elementos do vetor um ponteiro para um nmero inteiro. Ou, em outras palavras, cada uma das posies do vetor vai armazenar o endereo de um nmero inteiro. Observe que a simples declarao do vetor de ponteiros no inicializa o contedo das posies do vetor (os endereos dos nmeros inteiros). Desta forma, durante a execuo do programa, cada uma das posies do vetor precisa ser explicitamente inicializada8. Vetores de ponteiros oferecem uma alternativa implementao de matrizes, como veremos no exemplo a seguir.
/* soma 10 a cada um dos elementos da matriz */ main() { int matriz[3][3]={{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int *p[3]={matriz[0], matriz[1], matriz[2]}; int i, j; for (i=0; i<3; i++) for (j=0; j<3; j++) p[i][j] += 10; }

Supondo que matriz armazenada a partir da posio 1000, a representao simblica da alocao em memria dada por:

matriz p[0]==matriz[0] p[1]==matriz[1] p[2]==matriz[2] 1000 1012 1024 1 4 7 2 5 8 3 6 9

Com o endereo de alguma outra varivel do programa ou atravs de chamadas s funes calloc() ou malloc().

143

Observe que cada um dos elementos de p inicializado com o endereo de uma das linhas de matriz. Observe ainda que o uso de matrizes e de vetores de ponteiros similar, no sentido de que tanto matriz[2][2] como p[2][2], por exemplo, so referncias ao mesmo elemento da matriz. Existem, no entanto, importantes diferenas entre as duas formas. A declarao,
int matriz[3][3];

aloca 9 posies de memria para armazenar nmeros inteiros. J a declarao:


int *p[3];

aloca somente 3 posies de memria onde sero armazenados os ponteiros para inteiros. Os elementos do vetor de ponteiros precisam ser inicializados com o endereo de regies de memria grandes o suficiente para armazenarem cada uma das linhas da matriz. Essas regies de memria podem ser obtidas atravs do uso das funes calloc() ou malloc(), ou, como no exemplo, aproveitando-se o espao de memria alocado pelo compilador para outras estruturas do programa. Em contrapartida, as linhas da matriz podem ser de tamanhos diferentes. Ponteiros fornecem ainda uma maneira eficiente de lidar com os elementos de uma matriz. Suponha que, no exemplo anterior, queremos permutar as duas primeiras linhas da matriz. Vamos comparar as solues usando matrizes e ponteiros. Usando matrizes:
/* permuta as duas primeiras linhas da matriz */ void main() { int matriz[3][3]={{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int i, aux; for (i=0; i<3; i++) { aux = matriz[1][i]; matriz[1][i] = matriz[2][i]; matriz[2][i] = aux; } }

Usando ponteiros:
/* permuta as duas primeiras linhas da matriz */ void main() { int matriz[3][3]={{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

144

int *p[3]={matriz[0], matriz[1], matriz[2]}; int *aux; aux = p[0]; p[0] = p[1]; p[1] = aux; }

Observe que no trocamos de posio as linhas da matriz, apenas os ponteiros para elas. Note a otimizao do cdigo que foi possvel com o uso de ponteiros.

145

matriz p[1]==matriz[0] p[0]==matriz[1] p[2]==matriz[2] 1000 1012 1024 1 4 7 2 5 8 3 6 9

Alocao dinmica de matrizes


Nos exemplos anteriores o vetor de ponteiros foi inicializado com os endereos das linhas de uma varivel automtica matriz dimensionada em tempo de compilao. Veremos a seguir como determinar as dimenses da matriz dinamicamente, em tempo de execuo. a) Dimenso das linhas fixa; dimenso das colunas determinada em tempo de execuo.
1. #define DIML 10 2. #define DIMC 10 3. void main() { 4. int *pMatriz[DIML]; 5. int i, j; 6. 7. 8. 9. 10. 11. 12.} for(i=0; i<DIML; i++) pMatriz[i]=(int *)calloc(DIMC,sizeof(int)); for(i=0; i<DIML; i++) for(j=0; j<DIMC; j++) printf("Matriz[%d][%d] = %d\n", i, j, pMatriz[i][j]);

Anlise: Na linha 4 o vetor pMatriz[] declarado. Observe que a dimenso do vetor, correspondente ao nmero de linhas da matriz (DIML), fixa e determinada em tempo de compilao. Na linha 7, cada uma das posies de pMatriz[] inicializada com o endereo de um buffer no heap suficiente para armazenar um vetor de DIMC posies. Dessa forma, cada chamada funo calloc() aloca espao no heap para uma das linhas da matriz. No caso do exemplo, todas as linhas tm o mesmo nmero de elementos ou colunas ( DIMC). A figura a seguir mostra o esquema de a locao da matriz com a dimenso das linhas fixa

146

PMatriz[0] PMatriz[1] PMatriz[2] PMatriz[3] PMatriz[4] PMatriz[5] PMatriz[6] PMatriz[7] PMatriz[8] PMatriz[9] Alocados no heap

Alocado na pilha

b) Dimenso das linhas e das colunas determinada em tempo de execuo.


1. #define DIML 10 2. #define DIMC 10 3. void main() { 4. int **pMatriz; 5. int i, j; 6. 7. 8. 9. 10. 11. 12. 13.} pMatriz=(int **)calloc(DIML,sizeof(int *)); for(i=0; i<DIML; i++) pMatriz[i]=(int *)calloc(DIMC,sizeof(int)); for(i=0; i<DIML; i++) for(j=0; j<DIMC; j++) printf("Matriz[%d][%d] = %d\n", i, j, pMatriz[i][j]);

Anlise: Observe a declarao na linha 4. pMatriz um ponteiro para um ponteiro, ou, se for usada uma dimenso conveniente na linha 6, pMatriz um ponteiro para um vetor de ponteiros, alocado dinamicamente. Na linha 6 criado no heap o vetor de ponteiros apontado por pMatriz. Na linha 8 cada uma das posies de pMatriz[] inicializada
147

com o endereo de um buffer no heap de tamanho suficiente para armazenar as linhas da matriz. Observe que a chamada funo calloc() na linha 6 determina, em tempo de execuo, a dimenso das linhas da m atriz. De forma semelhante, as chamadas funo calloc() na linha8 determinam, em tempo de execuo, as dimenses das linhas da matriz. A figura abaixo ilustra o esquema de alocao dinmica da matriz.
pMatriz

Alocados no heap

Ponteiros e strings A Linguagem C oferece duas maneiras de lidar com strings . A primeira, vista no Captulo anterior, atravs de um vetor de caracteres. A segunda maneira atravs de um ponteiro para caracteres.

Usando vetores de caracteres Vamos rever o uso de vetores de caracteres atravs de um exemplo:
1. void main() { 2. char nome[10]="antigo"; 3. 4. 5. printf("nome = %s\n", nome); strcpy(nome, "novo"); printf("nome = %s\n", nome);

148

6. }

Sada:
nome = antigo nome = novo

Anlise: Na linha 2 o vetor de caracteres nome[] inicializado com a string antigo. Vimos no Captulo anterior que esta forma de inicializao equivalente a:
char nome[10]={a,n,t,i,g,o,\0);

Na linha 4 a string contida no vetor nome[] alterada para novo. O compilador, ao encontrar a declarao strcpy(nome, novo), cria em algum lugar da rea de cdigo a string "novo" seguida pelo caracter null . Em seguida, ele chama a funo strcpy() passando como argumentos o endereo do vetor nome[] e o endereo da string constante. Por que no fizemos simplesmente (nome = "novo")? Porque o nome de um vetor um ponteiro constante e, nesta instruo, estaramos tentando atribuir o endereo da string "novo" a nome .

Usando ponteiros para caracteres Seja o seguinte exemplo:


1. void main() { 2. char *nome="antigo"; 3. 4. 5. 6. } printf("nome = %s\n", nome); nome = "novo"; printf("nome = %s\n", nome);

Sada:
nome = antigo nome = novo

Anlise: Na inicializao, o compilador cria uma string constante ("antigo") em algum lugar da rea de cdigo e atribui o endereo dessa string ao ponteiro nome . Na linha 4 o compilador atribui o endereo da string "novo" ao ponteiro nome. Observe que agora no foi necessrio usar a funo strcpy(). Isso se d porque nome um
149

ponteiro varivel ao qual pode-se, portanto, atribuir qualquer endereo durante a execuo do programa.

Ponteiros e Vetores de Strings


Como vimos anteriormente, vetores de ponteiros oferecem uma alternativa implementao de matrizes. Assim, vetores de ponteiros para o tipo char so equivalentes a vetores de strings . Vamos rever o exemplo do Captulo anterior de um vetor de strings:
char nomes[][10]={Eduardo, Ricardo, Andre, Alexandre, Debora, Cecilia Marina};

Supondo que nomes armazenada a partir da posio 1000, a representao simblica da alocao em memria dada por:
nomes 1000 E 1010 R 1020 A 1030 A 1040 D 1050 C 1060 M d i n l e e a u c d e b c r a a r x o i i r r e a r l n d d \0 n a i a d \0 a \0 \0 r e \0 o o \0 \0

Observe que as strings constantes so armazenadas na pilha, nas posies de memria alocadas para o vetor nomes[]. Observe ainda que para cada linha so alocadas 10 posies e que, algumas delas, permanecem sem uso. Vamos ver agora a verso usando ponteiros:
char *nomes[]={"Eduardo", Ricardo,

150

"Andre", "Alexandre", "Debora", "Cecilia", Marina};

A representao simblica da alocao em memria dada por:

151

E R nomes[0] nomes[1] nomes[2] nomes[3] nomes[4] nomes[5] nomes[6] A A D C M

d i n l e e a

u c d e b c r

a a r x o i i

r r e a r l n

d d \0 n a i a

o o

\0 \0

d \0 a \0

\0

\0

As strings constantes so criadas na rea de cdigo pelo compilador e os seus endereos so atribudos aos elementos de nomes[]. Observe que agora, no existem bytes sem uso ao final de cada string. A verso com ponteiros possibilita implementar de forma eficiente trocas de posies entre as strings. Suponha, por exemplo, que desejamos ordenar os nomes acima alfabeticamente. Isto pode ser feito permutando-se os endereos do vetor de ponteiros, ao invs de, fisicamente, trocar as strings de posio.
E R nomes[0] nomes[1] nomes[2] nomes[3] nomes[4] nomes[5] nomes[6] A A D C M d i n l e e a u c d e b c r a a r x o i i r r e a r l n d d \0 n a i a d \0 a \0 \0 r e \0 o o \0 \0

Desta forma, nomes[0] aponta para o primeiro nome, nomes[1] para o nome seguinte, e assim por diante.

Argumentos em linha de comandos


152

Voc certamente j utilizou algum programa cujos argumentos eram passados na chamada ao mesmo, a partir do sistema operacional. Exemplo:
$ gcc -o teste teste.c

Esta instruo chama o compilador gcc passando como argumentos uma diretiva de compilao (-o), o nome do programa executvel (teste) e o nome do programa fonte (teste.c). Como fazer isso em C? Quando a funo main() ativada, a ela so passados dois argumentos (que podem ou no ser utilizados): um contador do nmero de argumentos passados na linha de comando e um vetor de ponteiros para os argumentos propriamente ditos. O prottipo da funo main() poderia ento ser escrito como:
void main(int argc, char *argv[]);

Como exemplo, vejamos o programa echo que ecoa os seus argumentos na sada padro. Assim, a linha de comando:
echo NCE Nucleo de Computacao Eletronica

produziria na sada:
NCE Nucleo de Computacao Eletronica

Por conveno, argv[0] contm o nome pelo qual o programa foi ativado, de modo que argc sempre igual ou maior do que 1. No exemplo acima teramos:
argc == 6

e,
argv[0] argv[1] argv[2] argv[3] argv[4] argv[5] "ECHO" "NCE" "Nucleo" "de" "Computacao" "Eletronica"

Finalmente, o programa seria escrito na forma:


void main(int argc, char *argv[]) int i; for (i=1; i<argc; i++) {

153

printf("%s ", argv[i]); }

Ponteiros para funes


Funes tambm possuem endereos que podem ser referenciados indiretamente atravs de ponteiros. A forma geral da declarao de um ponteiro para funo a seguinte:
tipo (*ptr)();

Na declarao acima, ptr um ponteiro para uma funo que retorna um tipo. A utilizao dos parnteses em (*ptr) necessria, uma vez que,
tipo *ptr();

a declarao de uma funo ptr() que devolve um ponteiro para tipo. Vejamos um exemplo prtico. Vamos estudar duas alternativas de implementao de um programa que solicita um nmero ao usurio e, em seguida, apresenta um menu de alternativas de operaes que podem ser feitas com o nmero. A primeira verso usa o comando switch e a segunda, ponteiros para funes. Verso com switch
void main() { int op, result, num; int dobro(int); int triplo(int); int quadruplo(int); printf("Entre com um numero : "); scanf("%d", &num); getchar(); puts("2 - Multiplicar por 2"); puts("3 - Multiplicar por 3"); puts("4 - Multiplicar por 4"); printf("\nEntre com a operacao desejada : "); op = getchar(); switch (op) case '2': result break; case '3': result break; case '4': result { /* Multiplicar por 2 */ = dobro(num); /* Multiplicar por 3 */ = triplo(num); /* Multiplicar por 4 */ = quadruplo(num);

154

} printf("\n\nResultado = %d\n", result); } int dobro(int num) return 2*num; } int triplo(int num) return(3*num); } {

int quadruplo(int num) { return(4*num); }

Verso com ponteiros


typedef int (*VectPontFunc[3])(int); void main() { int op, num; int dobro(int); int triplo(int); int quadruplo(int); VectPontFunc ptr={dobro, triplo, quadruplo}; printf("Entre com um numero : "); scanf("%d", &num); getchar(); puts("2 - Multiplicar por 2"); puts("3 - Multiplicar por 3"); puts("4 - Multiplicar por 4"); printf("\nEntre com a operacao desejada : "); op = getchar(); printf("\n\nResultado = %d\n", (*ptr[op-'2'])(num)); } int dobro(int num) return(2*num); } int triplo(int num) return(3*num); } {

int quadruplo(int num) { return(4*num); }

155

Observe a declarao,
typedef int (*VectPontFunc[3])(int);

Trata-se da declarao de um tipo, VectPontFunc, um vetor de 3 posies onde cada um dos elementos um ponteiro para uma funo que recebe um nmero inteiro como argumento e retorna um valor inteiro. O prefixo typedef cria tipos de dados definidos pelo usurio os quais tm propriedades idnticas as dos tipos pr existentes na linguagem (int, char, float, etc.). Assim, a partir da declarao de um tipo possvel us-lo para a declarao de variveis, prottipos de funes, argumento do operador sizeof(), etc. Voc pode pensar na sintaxe da declarao de um tipo como a declarao de uma varivel precedida do prefixo typedef. O identificador correspondente ao nome da varivel passa a designar o tipo recm criado. Exemplo
typedef unsigned int uint; typedef char str40[41];

Vamos retornar ao exemplo anterior. A declarao:


VectPontFunc ptr={dobro, triplo, quadruplo};

cria uma varivel ptr do tipo VectPontFunc. Isto , ptr um vetor de 3 posies onde cada um dos elementos um ponteiro para uma funo que recebe um argumento inteiro e retorna um valor inteiro. Os elementos do vetor so inicializados com os endereos das funes dobro(), triplo() e quadruplo(). O nome de uma funo desacompanhado de parnteses corresponde ao endereo da funo.

Observe agora a instruo,


(*ptr[i])(num);

ela chama a funo apontada pelo i-simo elemento do vetor ptr, passando como parmetro o inteiro num. Por exemplo, a instruo:
(*ptr[0])(num);

equivalente a:
dobro(num)

156

Comparando as duas verses do programa acima, percebe-se que vetores de ponteiros para funes possibilitam a construo de um cdigo mais eficiente, ainda que de mais difcil compreenso.

157

CAPTULO 11 - Estruturas e Unies


A forma mais geral de estruturao de dados consiste na juno de tipos em um tipo composto. Os tipos estruturados struct e union, possibilitam a criao de estruturas de dados complexas e, por conseguinte, so bastante utilizados na elaborao de programas aplicativos.

Estruturas
Podemos fazer a analogia entre uma estrutura de dados do tipo vetor, onde todos os elementos devem ser do mesmo tipo, e um gaveteiro, onde todas as gavetas so do mesmo tamanho. Por exemplo:
int x[7];

corresponderia a:
x[0] x[1] x[2] x[3] x[4] x[5] x[6]

O que aconteceria se desejssemos criar uma nova estrutura em que cada um dos seus elementos fosse de um tipo diferente? Este um caso que ocorre com muita freqncia em aplicaes prticas como por exemplo, quando temos que armazenar as informaes relativas aos empregados de uma
158

firma. Estas informaes podem constar, por exemplo, de: nome (cadeia de caracteres), cdigo (inteiro) e salrio (real).
NOME CDIGO : PAULO DA SILVA : 1234

SALRIO : 1000,00

Esta forma de organizar uma estrutura, com tipos diferentes, muito comum em processamento de dados e conhecida como REGISTRO. Registros em C so designados pela palavra reservada struct.

NOME CDIGO SALRIO

Cada um dos componentes de uma estrutura chamado de CAMPO. A declarao em C de um registro do cadastro de funcionrios ficaria:
struct { // declarao do tipo char nome[20]; int cdigo; float salrio; } funcionrio; // declarao da varivel

A definio do registro comea com a palavra reservada struct e termina no fecha chaves (}). Entre as chaves encontramos a definio de cada um dos campos componentes do registro. A definio de cada campo exatamente igual definio de qualquer varivel em C. Alm disso, os campos podem ser de qualquer tipo vlido em C, incluindo vetores, ou at mesmo outros registros. A varivel funcionrio segue a declarao do tipo, indicando ao compilador que funcionrio uma varivel do tipo struct. O tamanho ocupado em memria pela varivel funcionrio igual ao somatrio dos tamanhos dos campos, isto :
159

sizeof(funcionrio)==sizeof(funcionrio.nome)+ sizeof(funcionrio.cdigo)+ sizeof(funcionrio.salrio9) sizeof(funcionrio)==20*sizeof(char)+sizeof(int)+sizeof(float )

Quando a mesma estrutura for utilizada em diversos pontos do programa, pode-se dar um nome mesma de modo que ela no tenha de ser redefinida cada vez que declararmos uma varivel do tipo da estrutura. Vejamos um exemplo:
struct RegFunc { char nome[20]; int cdigo; float salrio; } funcionrio; // declarao do tipo

// declarao da varivel

em algum outro ponto do programa poderamos declarar a varivel gerente na forma:


struct RegFunc gerente;

Observe que RegFunc no o nome de uma varivel e sim, o nome da estrutura. A declarao de outras variveis do tipo RegFunc dentro do programa, podem ser feitas utilizando-se apenas o nome da estrutura, no sendo necessria a definio dos campos. A definio de uma estrutura pode ser feita tambm, sem a lista de variveis ao seu final. Suponha, por exemplo, que voc desejasse que a definio de RegFunc fosse global a todo o programa, mas que nenhuma varivel fosse global. A soluo seria fazer:
struct RegFunc { char nome[20]; int cdigo; float salrio; }; // declarao do tipo

main() { struct RegFunc funcionario; struct RegFunc gerente; // declarao das variveis . . }

Observe a forma de referenciar os campos da estrutura. Isto ser visto com mais detalhes adiante neste Captulo.

160

Repare o ponto e vrgula ao final da definio da estrutura. Ele agora necessrio. Temos agora que descobrir como ter acesso s informaes de uma estrutura. Existe alguma semelhana entre a maneira utilizada para se ter acesso aos elementos de um vetor e aquela utilizada para os campos de um registro. Ambas so compostas pelo nome da varivel seguida por um seletor. Para obter-se o contedo do sexto elemento do vetor x[] mencionado anteriormente, devemos escrever:
x [5]

varivel

seletor

De forma anloga, para acessarmos o campo salrio da estrutura funcionrio, temos:


funcionrio.salrio

varivel

seletor

A diferena fundamental no acesso a elementos de registros e de vetores que o seletor de um vetor pode ser uma expresso, enquanto o de um registro tem que ser obrigatoriamente um campo, no podendo portanto, ser calculado em tempo de execuo do programa. Para preenchermos os campos da estrutura funcionrio a partir do teclado, teramos:
scanf("%s", funcionrio.nome); scanf("%d", &funcionrio.cdigo); scanf("%f", &funcionrio.salrio);

Em resumo, um registro uma estrutura com as seguintes caractersticas: contm um nmero fixo de elementos chamados campos;
161

os campos podem ser de qualquer tipo; os campos podem ser de tipos diferentes; cada campo referenciado por um nome chamado de identificador do campo.

Inicializando estruturas
Da mesma forma que vetores, estruturas so inicializadas com uma lista de valores (cada um correspondente a um campo da estrutura) entre chaves e separados por vrgulas. No exemplo anterior, as variveis funcionrio e gerente do tipo RegFunc poderiam ser inicializadas como:
struct RegFunc { char nome[20]; int cdigo; float salrio; }; // declarao do tipo

void main() { struct RegFunc funcionrio={"Mrcio Nascimento", 1234, 1000.00}, gerente={"Roberto Diniz", 24, 2000.00}; }

Atribuies entre estruturas


Em C clssico, atribuies entre estruturas tinham de ser feitas campo a campo, como no exemplo:
strcpy(gerente.nome, funcionrio.nome); gerente.cdigo = funcionrio.cdigo; gerente.salrio = funcionrio.salrio;

Verses mais modernas do compilador C permitem que se faa uma atribuio direta entre estruturas do mesmo tipo:
gerente = funcionrio;

Na instruo acima, todos os campos do registro funcionrio so atribudos aos campos correspondentes do registro gerente.
162

Estruturas aninhadas
Como visto anteriormente, os campos de uma estrutura podem ser de qualquer tipo, inclusive outras estruturas. Suponha por exemplo, que desejamos incluir no registro de funcionrios a data de aniversrio composta de dia, ms e ano. A soluo seria:
typedef struct int dia; char ms[10]; int ano; } data; { // declarao da estrutura mais interna

typedef struct { char nome[20]; int cdigo; float salrio; data nascimento; } RegFunc;

// declarao da estrutura externa

void main() { RegFunc // declarao das variveis funcionrio = {"Mrcio Nascimento", 1234, 1000.00, {10, "Janeiro", 1962}}, gerente = {"Roberto Diniz", 24, 2000.00, {9, "Marco", 1959}}; . . }

Observe a inicializao das variveis. A estrutura aninhada inicializada tambm com uma lista de valores entre chaves e separados por vrgulas. Observe ainda a declarao das estruturas. Foi usado o prefixo typedef de modo a tornar data e RegFunc os identificadores dos tipos criados. O acesso a um campo de uma estrutura aninhada feito na forma:
funcionrio.nascimento.dia = 11; strcpy(gerente.nascimento.ms, "Abril");

Estruturas e funes
163

Em verses mais antigas de compiladores C, estruturas no podiam ser usadas em passagem de parmetros por valor para funes. Isto se devia a razes de eficincia uma vez que, uma estrutura pode ser muito grande e a cpia de todos os campos da estrutura para a pilha poderia consumir um tempo exagerado. Dessa forma, estruturas eram obrigatoriamente passadas por referncia, usando-se o operador de endereo (&). Em compiladores mais recentes, a responsabilidade da deciso deixada para o programador. Assim, uma funo pode passar ou retornar uma estrutura para outra funo. Voltemos ao exemplo: suponhamos que o programa principal chama uma funo NovoFuncionario() que devolve os dados de um novo funcionrio. Em seguida chama-se a funo list() que imprime os dados do novo registro.
1. typedef struct { 2. char nome[20]; 3. int cdigo; 4. float salrio; 5. } RegFunc; 6. RegFunc NovoFuncionario(void); 7. void list(RegFunc); 8. void main() { 9. RegFunc funcionrio; 10. 11. 12. 13. 14. } funcionrio = NovoFuncionario(); list(funcionrio); . .

15. RegFunc NovoFuncionario(void) 16. RegFunc func;

17. printf("Nome : "); gets(func.nome); 18. printf("Cdigo : "); scanf("%d", &func.codigo); 19. printf("Salrio: "); scanf("%f", &func.salario); 20. return func; 21. } 22. 23. void list(RegFunc func) { 24. printf("\nNome : %s\n", func.nome); 25. printf("Cdigo : %d\n", func.codigo); 26. printf("Salrio: %g\n", func.salario); 27. }

Comentrios: (linha 6) Prottipo da funo NovoFuncionario(). NovoFuncionario() uma funo que retorna uma estrutura do tipo RegFunc.
164

(linha 7)

Prottipo da funo list(). list() uma funo que recebe como argumento uma estrutura do tipo RegFunc. A estrutura passada por valor.

(linha 10) Atribuio entre estruturas. A estrutura funcionrio recebe os valores dos campos da estrutura retornada pela funo NovoFuncionario(). (linha 16) func uma varivel local funo NovoFuncionario(). Deve-se ter em mente que se a estrutura a ser passada ou retornada for muito grande, e se o tempo de execuo do programa for um dado crtico, a soluo usando ponteiros (passagem por referncia) pode ser mais apropriada.

Ponteiros para estruturas


Pode-se declarar ponteiros para estruturas da mesma forma que declaramos ponteiros para outros tipos de dados. Ponteiros para estruturas so essenciais criao de estruturas de dados dinmicas tais como listas encadeadas e rvores binrias. De fato, ponteiros para estruturas so usados to freqentemente em C que existe um smbolo especial para acessar um campo de uma estrutura apontada por um ponteiro. Vamos reescrever o exemplo de passagem de estruturas como parmetro entre funes, dessa vez usando ponteiros:
1. typedef struct { 2. char nome[20]; 3. int codigo; 4. float salario; 5. } RegFunc; 6. void NovoFuncionario(RegFunc *pFunc); 7. void list(RegFunc *pFunc); 8. void main() { 9. RegFunc funcionario; 10. NovoFuncionario(&funcionario); 11. list(&funcionario); 12. // . 13. // . 14. } 15. void NovoFuncionario(RegFunc *pFunc) { 16. printf("Nome : "); gets((*pFunc).nome); 17. printf("Codigo : "); scanf("%d", &(*pFunc).codigo); 18. printf("Salario: "); scanf("%f", &(*pFunc).salario); 19. } 20. void list(RegFunc *pFunc) {

165

21. 22. 23. 24. }

printf("\nNome : %s\n", pFunc->nome); printf("Codigo : %d\n", pFunc->codigo); printf("Salario: %g\n", pFunc->salario);

Comentrios: (linha 6) - Observe prottipo da funo NovoFuncionario(). NovoFuncionario() recebe como argumento um ponteiro para uma estrutura do tipo RegFunc. A sintaxe da declarao de um ponteiro para estruturas a mesma de qualquer outra declarao de ponteiros que j tenhamos visto. o

(linha 10) De maneira idntica passagem por referncia de qualquer outro tipo de varivel, o endereo da estrutura obtido com o operador de endereo (&). (linha 16) Acesso aos campos de uma estrutura apontada por um ponteiro. Observe que os parnteses so obrigatrios uma vez que o operador (.) tem precedncia sobre o operador de indireo (*). No caso do exemplo, uma vez que (pFunc==&funcionario), verdadeira a relao:
(*pFunc).nome==funcionario.nome

Por que no podemos fazer gets(pFunc.nome)? Porque pFunc no uma estrutura e sim um ponteiro. (linha 21) De fato, a referncia a campos de estruturas apontadas por ponteiros uma construo to freqente em C, que um operador foi criado especialmente para lidar com esta situao. Este operador formado pelo sinal de menos ( -) seguido pelo smbolo de maior (>).
pFunc->nome

Aqui tambm, como (pFunc == &funcionario), verdadeira a relao:


pFunc->nome == funcionario.nome

Comparando as duas notaes anteriores, vemos que:


pFunc->nome funcionario.nome == (*pFunc).nome ==

Vetor de estruturas
166

Quando definimos a estrutura funcionrio, criamos uma varivel capaz de armazenar apenas um empregado da empresa. Como fazer para armazenar os dados de 1000 funcionrios de uma empresa? A soluo est em usar um vetor de funcionrios:
#define MAXFUNC 1000 typedef struct { char nome[20]; int codigo; float salario; } RegFunc; // declarao do tipo

void main() { RegFunc funcionario[MAXFUNC]; . . }

Observe que foi criada uma tabela de funcionrios (um vetor) onde cada elemento da tabela uma estrutura contendo os dados de um funcionrio. Para referenciar o salrio do funcionrio 25, deve-se escrever:
funcionrio[25].salario

Vetores de estruturas podem ser inicializados como se segue:


#define MAXFUNC 1000 RegFunc funcionario[MAXFUNC]= {{"Alice Falcao", 4650, 1231.00}, {"Ronaldo Jesus", 7587, 670.00}, {"Almir Cardoso", 3640, 247.00}, {"Jose Lima", 6194, 1118.00}, {"Joao Prado", 2435, 889.00}, . . . };

Vejamos um programa simples para listar o cadastro de funcionrios de uma empresa:


1. typedef struct { 2. char nome[20]; 3. int codigo; 4. float salario; 5. } RegFunc; // declarao do tipo

167

6. void main() { 7. int i; 8. RegFunc funcionario[]={{"Alice Falcao", 4650, 1231.00}, 9. {"Ronaldo Jesus", 7587, 670.00}, 10. {"Almir Cardoso", 3640, 247.00}, 11. {"Jose Lima", 6194, 1118.00}, 12. {"Joao Prado", 2435, 889.00}, 13. . 14. . 15. }; 16. 17. 18. 19. 20. 21. 22. } printf("%s %22s %20s\n", "nome", "codigo", "salario"); for (i=0; i<sizeof(funcionario)/sizeof(RegFunc); i++) printf("%-21s %4d %21.2f\n", funcionario[i].nome, funcionario[i].codigo, funcionario[i].salario);

Sada:
nome Alice Falcao Ronaldo Jesus Almir Cardoso Jose Lima Joao Prado codigo 4650 7587 3640 6194 2435 salario 1231.00 670.00 247.00 1118.00 889.00

Comentrios: Observe a construo na linha 17,


sizeof(funcionario)/sizeof(RegFunc)

Ela usada para determinar o nmero de elementos no vetor funcionario[] uma vez que a dimenso do vetor no foi explicitamente definida na declarao do mesmo.

Listas encadeadas
Suponha que o programa anterior fosse usado em uma empresa com 5 funcionrios. Haveria um desperdcio muito grande de memria uma vez que foram alocadas posies para o registro de 1000 funcionrios. Suponha agora, que o nmero de empregados na sua empresa fosse crescendo at o dia em que excedesse o valor de MAXFUNC. Nesse dia, seria necessrio alterar o valor de MAXFUNC e recompilar o programa, o qual seria utilizvel at o dia em que se fizesse necessria nova alterao.
168

Uma soluo para esse problema a utilizao de uma estrutura chamada lista encadeada onde os elementos da lista (no nosso exemplo, os registros de funcionrios) so criados dinamicamente, medida em que se faam necessrios. Uma lista encadeada uma estrutura em que cada elemento contm um ponteiro para o prximo elemento na lista.

elemento 1 cabea

elemento 2

elemento 3

Pode-se fazer uma analogia entre uma lista encadeada e a sala de espera de um consultrio. Muito embora os pacientes no entrem em fila, cada um deles conhece o paciente que chegou imediatamente aps a ele, ou seja, cada paciente capaz de apontar o paciente seguinte, o que garante que eles sejam atendidos na ordem de chegada. Uma lista encadeada acessada atravs de um ponteiro (cabea) para o primeiro elemento da lista. A partir da, os elementos so acessados em seqncia: o prximo elemento aquele apontado pelo elemento atual. Vamos modificar os registros de funcionrios, de modo a que eles possam ser estruturados na forma de uma lista encadeada.
struct RegFunc { // declarao do tipo char nome[20]; int codigo; float salario; struct RegFunc *pprox; }

Observe com ateno a declarao acima: a estrutura RegFunc contm agora um campo (pprox) que um ponteiro para uma estrutura do tipo RegFunc. Observe a correspondncia com a figura da lista encadeada.

Percorrendo listas encadeadas.


Percorre-se a lista a partir da cabea, acessando-se os elementos seqencialmente at que o ponteiro para o prximo tenha o valor NULL. NULL uma constante definida no arquivo stdio.h como 0. Em C, nenhum objeto vlido alocado na posio 0, de modo que este endereo pode ser usado para sinalizar o fim da lista.
169

O algoritmo para percorrer listas encadeadas pode ser visto no exemplo abaixo. A fim de simplificar a anlise, vamos construir uma lista encadeada usando os elementos de um vetor. Esta no a construo mais usual uma vez que, normalmente, os elementos de uma lista so obtidos em tempo de execuo com as funes calloc() ou malloc(). Mais adiante veremos exemplos de construo de listas encadeadas usando alocao dinmica de memria.
1. typedef struct Func { 2. char nome[20]; 3. unsigned codigo; 4. float salario; 5. struct Func *pProx; 6. } RegFunc; 7. void main() { 8. RegFunc func[3]={{"Jorge", 3554, 750.0, &func[1]}, 9. {"Paulo", 1234, 500.0, &func[2]}, 10. {"Carlos", 755, 1200.0, NULL}}; 11. RegFunc *Head=&func[0]; 12. RegFunc *pFunc; 13. 14. 15. 16. 17. 18. } pFunc=Head; while(pFunc!=NULL) { printf("Nome : %s\n", pFunc->nome); pFunc=pFunc->pProx; }

Anlise: Observe a construo da lista encadeada nas linhas 8-10: o primeiro elemento do vetor (func[0]) aponta para o segundo elemento (func[1]). Este, por sua vez aponta para o elemento seguinte (func[2]). O primeiro elemento da lista apontado pela varivel Head. O ltimo elemento da lista aponta para NULL. A lista percorrida usando-se um ponteiro auxiliar pFunc. O procedimento para percorrer a lista encadeada pode ser visto nas linhas 13-17. Observe com ateno a linha 16. Nela o ponteiro pFunc atualizado e passa a apontar o elemento seguinte na lista. O procedimento repetido at que pFunc==NULL.

O lao das linhas 13-17 no exemplo anterior poderia ser reescrito como:
pFunc=Head; while(pFunc) { printf("Nome : %s\n", pFunc->nome); pFunc=pFunc->pProx; }

ou ainda:
170

for(pFunc=Head; pFunc; pFunc=pFunc->pProx) printf("Nome : %s\n", pFunc->nome);

Inserindo novos elementos em listas encadeadas


Vamos ver agora como adicionar novos elementos lista. Em primeiro lugar preciso alocar espao em memria para o novo elemento. Isto pode ser feito com as funes calloc() ou malloc(). Vejamos como seria a insero de um novo funcionrio na lista do exemplo anterior:
pNew = (RegFunc *)malloc(sizeof(RegFunc));

pNew aponta para um espao de memria, alocado no heap, de tamanho sizeof(RegFunc).

Head

pNew

Em seguida preciso preencher os campos da estrutura recm criada com os dados do novo funcionrio:
strcpy(pNew->nome, "Mauro"); pNew->codigo=345; pNew->salario=780.0;

O prximo passo inserir o elemento recm criado na lista. Suponha que os registros de funcionrios no so armazenados em ordem. Nesse caso, a insero pode ser feita perto da cabea, o que nos livra de percorrer toda a lista procurando o lugar certo para o novo registro. A insero se d atravs de duas instrues:
1. pNew->pProx=Head;

Head

171
pNew

2. Head=pNew;

Head

pNew

Desenvolvimento de uma aplicao


Vamos imaginar o caso de uma empresa que tenha um certo nmero de funcionrios e deseja implementar um cadastro em computador com informaes de todo o seu pessoal. Este cadastro deve ser constantemente atualizado com as alteraes no quadro de funcionrios da empresa e, portanto, requer um programa para manuteno e visualizao do mesmo. O programa dever executar as seguintes funes: 1. Incluso de funcionrios; 2. Excluso de funcionrios; 3. Alterao dos dados de funcionrios; 4. Consulta aos dados de funcionrios; 5. Listagem dos dados de todos os funcionrios;

Podemos visualizar o sistema na forma de um diagrama de estruturas que mostra como o programa principal est ligado s diversas tarefas que devem ser executadas. Cada mdulo corresponde a um procedimento do programa, exceto o mdulo SISTEMA CADASTRO que corresponde ao programa principal.
SISTEMA CADASTRO

LER ARQUIVO

PROCESSAR CADASTRO

GRAVAR CADASTRO

172

Os mdulos LER ARQUIVO e GRAVAR ARQUIVO j podem ser codificados, enquanto PROCESSAR CADASTRO precisa ainda ser refinado atravs de um outro diagrama.

PROCESSAR CADASTRO OPO == FIM DO PROGRAMA MOSTRAR MENU LER OPO EXECUTAR OPO

Os mdulos MOSTRAR MENU e LER OPO j podem ser codificados, mas o que significa EXECUTAR OPO?

EXECUTAR OPO

0 INCLUIR

1 ALTERAR

2 CONSULTAR

3 EXCLUIR

4 LISTAR

5 FIM

Para manipular o cadastro na memria do computador vamos usar uma lista encadeada contendo os registros dos funcionrios. Em geral, devido s suas dimenses, o cadastro deve ficar armazenado em disco. Para isto precisamos de rotinas para buscar e guardar em disco as informaes do cadastro. Estas rotinas sero objeto de estudo do prximo captulo, quando trataremos da manipulao de arquivos. Apresentamos a seguir um esboo do programa de processamento do cadastro de funcionrios que acabamos de desenvolver por meio dos diagramas.
enum BOOL {FALSE, TRUE}; struct RegFunc char int { nome[20]; codigo;

173

float salario; struct RegFunc *proximo; };

174

struct RegFunc *cabeca=NULL; struct RegFunc *ptr; BOOL fim=FALSE; int LerOpcao(void); void ExecutarOpcao(int opcao); void LerArquivo(void); void MostrarMenu(void); void ProcessarCadastro(void); void GravarArquivo(void); void main() { LerArquivo(); ProcessarCadastro(); GravarArquivo(); } void LerArquivo() { // le os registros de funcionarios de um // arquivo de entrada e os coloca em uma // lista encadeada. } void ProcessarCadastro() int opcao; do { MostrarMenu(); opcao=LerOpcao(); ExecutarOpcao(opcao); {

} while (!fim); } void MostrarMenu() // por fazer } int LerOpcao() // por fazer } void void void void void void void void { {

ExecutarOpcao(int opcao) { Incluir(void); Alterar(void); Consultar(void); Excluir(void); Listar(void); Fim(void); (*matriz[6])()={Incluir, Alterar, Consultar, Excluir, Listar, Fim};

(*matriz[opcao])(); }

175

void Incluir() // por fazer } void Alterar() // por fazer } void Consultar() // por fazer } void Excluir() // por fazer } void Listar() // por fazer } void Fim() fim=TRUE; }

void GravarArquivo() { // por fazer }

Unies
A sintaxe da definio e uso de uma unio a mesma de uma estrutura. Estruturas e Unies so ambas usadas para armazenar elementos de tipos diferentes em uma mesma varivel. A diferena reside no fato de que, quando declaramos uma varivel do tipo estrutura, o compilador aloca espao suficiente para armazenar todos os elementos ao mesmo tempo enquanto que, para variveis do tipo unio, apenas um dos elementos armazenado por vez. Exemplo:
typedef struct { long i1; long i2; float f1; float f2; } RegStruct; void main() { RegStruct estrutura; estrutura.i1 = 2;

176

estrutura.i2 = 3; printf ("i1 = %-3d i2 = %-3d\n", estrutura.i1, estrutura.i2); estrutura.f1 = 2.5; estrutura.f2 = 3.5; printf ("f1 = %.1f f2 = %.1f\n", estrutura.f1, estrutura.f2); }

Sada:
i1 = 2 f1 = 2.5 i2 = 3 f2 = 3.5

Observe que os dados no se confundem, uma vez que o compilador aloca memria suficiente para armazenar estrutura.i1, estrutura.i2, estrutura.f1, e estrutura.f2 ao mesmo tempo.

estrutura.i1 estrutura.i2 estrutura.f1 estrutura.f2

sizeof(estrutura)==sizeof(estrutura.i1)+sizeof(estrutura.i2)+ sizeof(estrutura.f1)+sizeof(estrutura.f2)

Vamos estudar o mesmo programa usando unies:


typedef union { long i1; long i2; float f1; float f2; } RegUnion; void main() { RegUnion uniao; uniao.i1 = 2; uniao.i2 = 3; printf ("i1 = %-3ld uniao.f1 = 2.5; uniao.f2 = 3.5;

i2 = %-3ld\n", uniao.i1, uniao.i2);

177

printf ("f1 = %.1f }

f2 = %.1f\n", uniao.f1, uniao.f2);

Sada:
i1 = 3 f1 = 3.5 i2 = 3 f2 = 3.5

Observe a sintaxe de declarao de uma union. Ela idntica declarao de uma struct. Por que foi impresso o valor de i1 == 3 se, explicitamente, atribumos (uniao.i1 = 2)? Porque todos os elementos de uma unio compartilham o mesmo espao de memria, o qual pode ser referenciado pelo nome de qualquer um dos elementos. Dessa forma:
&uniao.i1 == &uniao.i2 == &uniao.f1 == &uniao.f2

uniao.i1==uniao.i2==uniao.f1==uniao.f2

Desta forma, quando fizemos (uniao.i2 = 3), o inteiro 3 foi escrito na mesma posio de memria onde anteriormente havamos feito: (uniao.i1 = 2). Esse mesmo espao de memria foi usado nas instrues seguintes para fazer as atribuies dos reais. O tamanho do bloco de memria alocado para uma unio o tamanho do maior dos seus elementos. No exemplo anterior, as variveis tm todas o mesmo tamanho, logo:
sizeof(uniao)==sizeof(uniao.i1)==sizeof(uniao.i2) ==sizeof(uniao.f1)==sizeof(uniao.f1)

Definindo e declarando unies


A sintaxe da definio e declarao de unies idntica de estruturas, exceto pelo uso da palavra reservada union. Por exemplo:
178

union { long long float float } uniao;

i1; i2; f1; f2;

Da mesma forma que em estruturas, aqui tambm podemos dar um nome ao tipo da estrutura de forma que ele no precise ser redefinido sempre que referenciado.
union RegUnion { long i1; long i2; float f1; float f2; }; void main() { RegUnion uniao; . . }

Pode-se ainda criar um novo tipo usando o prefixo typedef de modo a simplificar a declarao de unies.
typedef union { long i1; long i2; float f1; float f2; } RegUnion; void main() { RegUnion uniao; . . }

Acessando membros da unio


Da mesma forma que em estruturas, o operador ponto (.) usado para acessar os membros de uma unio. Deve-se ter em mente que, como todos os elementos compartilham a mesma posio de memria, o tipo da varivel armazenada o mesmo da ltima atribuio de valor feita unio.
179

Por exemplo, no programa anterior, se tentarmos referenciar uniao.i1 aps atribuir um valor a uniao.f1 o resultado ser imprevisvel, uma vez que o compilador interpretar o contedo da varivel real uniao.f1 como um nmero inteiro.

Na utilizao de unies, responsabilidade do programador saber qual foi o tipo mais recentemente referenciado.

180

Estruturas como membros de unies


Da mesma forma que uma estrutura pode ser membro de uma outra estrutura, unies podem ser membros de outras unies, unies podem ser membros de estruturas e estruturas podem ser membros de unies. Vejamos um exemplo desse ltimo caso:
typedef struct long i1; long i2; } DoisInt; typedef struct float f1; float f2; } DoisFloat; union { DoisInt i; DoisFloat f; } teste; void main() teste.i.i1 teste.i.i2 printf("i1 teste.i.i2); teste.f.f1 teste.f.f2 printf("f1 } { = 2; = 3; = {

%-3ld

i2

%-3ld\n",

teste.i.i1,

= 2.5; = 3.5; = %.1f

f2 = %.1f\n", teste.f.f1, teste.f.f2);

Sada:
i1 = 2 f1 = 2.5 i2 = 3 f2 = 3.5

Observe que os campos das estruturas internas unio so acessados usando-se o operador ponto duas vezes:
teste.i.i1

A organizao da unio em memria seria:

teste.i &teste.i.i1==&teste.f.f1 &teste.i.i2==&teste.f.f2

teste.f

181

Por que usar unies?


A unio fornece uma forma de ver o mesmo dado de vrias maneiras diferentes. Seja o seguinte exemplo:
typedef unsigned short int uint; typedef struct byte char baixo ; char alto; } RegByte; union { uint word; RegByte byte; } numero; void main() { numero.word=0xABCD; printf("numero : %X\n", numero.word); printf("byte baixo : %X\n", numero.byte.baixo); printf("byte alto : %X\n", numero.byte.alto); } {

Sada:
numero : ABCD byte baixo : FFCD byte alto : FFAB

Atravs da construo acima, podemos referenciar o contedo da varivel numero como uma palavra de 16 bits,
numero.word

ou podemos referenciar os bytes alto e baixo individualmente:


numero.byte.alto numero.byte.baixo

182

CAPTULO 12 - Operaes com arquivos


Chamamos de arquivo a uma coleo de bytes armazenados em memria secundria (disquete, disco rgido, CD-ROM, etc.), e referenciados por um nome comum. A linguagem C oferece um pacote de funes da biblioteca que nos permite acessar arquivos de quatro modos diferentes: Entrada e sada de caracteres Os dados so lidos e escritos, um caracter por vez. As funes so anlogas s funes putchar() e getchar()

Entrada e sada de linhas Os dados so lidos e escritos como linhas de texto. Anlogo ao uso das funes gets() e puts().

Entrada e sada formatada Os dados so lidos e escritos formatados. Anlogo ao uso das funes printf() e scanf().

Entrada e sada por blocos Os dados so lidos ou escritos em blocos de bytes cujo tamanho especificado nas chamadas s funes. Usadas para ler ou armazenar estruturas, vetores e matrizes.

Estudaremos a seguir cada uma dessas funes.

A estrutura FILE
Todo arquivo aberto tem a ele associado uma estrutura do tipo FILE. Toda e qualquer referncia ao arquivo se d atravs de um ponteiro para tal estrutura. O tipo FILE uma estrutura declarada em stdio.h cujos campos so informaes sobre o arquivo sendo acessado. E informaes so teis ao sistema operacional a fim de stas gerenciar o acesso ao arquivo aberto. Algumas dessas informaes so: status do arquivo, endereo do buffer para transferncia de dados, posio corrente do ponteiro, etc.

183

A estrutura FILE

<STDIO.H>

typedef struct{ short level; unsigned flags; char fd; unsigned char hold; short bsize; unsigned char *buffer, *curp; unsigned istemp; short token; } FILE;

As informaes contidas na estrutura FILE no so de interesse imediato do programador. A este cabe apenas assegurar a associao (atravs de um ponteiro) entre qualquer arquivo aberto e uma estrutura do tipo FILE.
FILE *pFILE;

Abrindo arquivos
Uma vez declarado um ponteiro para um arquivo, necessrio abrir o arquivo. A expresso abrir o arquivo significa prepar-lo para operaes subseqentes de leitura e/ou escrita. Em C, abrimos um arquivo com a funo fopen() a qual devolve um ponteiro para a estrutura FILE associada ao arquivo recm aberto.
FILE *fopen(char *filename, char *mode);

onde, filename uma string contendo o nome do arquivo e mode o modo de abertura do arquivo.

Exemplo:
FILE *pFILE = fopen("teste.c", "w");

A tabela a seguir apresenta os possveis modos de abertura de um arquivo.

184

r w a r+ w+ a+

Abre um arquivo apenas para leitura. O arquivo deve existir no disco, Abre um arquivo para escrita. Se j existir um arquivo com o mesmo nome, este arquivo ser sobrescrito. Abre um arquivo para gravao. Se o arquivo existir os dados so adicionados ao seu final. Se ele no existir ser criado. Abre um arquivo para atualizao (leitura e escrita). O arquivo deve estar presente no disco. Abre um arquivo para atualizao (leitura e escrita). Se o arquivo j existir no disco ele ser destrudo e reinicializado. Se ele no existir ser criado. Abre um arquivo para atualizao (leitura e escrita). Se o arquivo existir os dados so adicionados ao seu final. Se ele no existir ser criado.

Erros ao abrir arquivos


Podem ocorrer erros ao abrir-se arquivos. Pode-se, por exemplo, abrir um arquivo no existente para leitura ou abrir um arquivo para escrita em um meio magntico protegido contra escrita ou sem nenhum espao livre. Quando da ocorrncia de um erro a funo fopen() retorna o endereo NULL. Exemplo:
if((pFILE=fopen("saida.dat", "r"))==NULL) puts("Nao pude abrir o arquivo"); return; } {

A fim de simplificar a codificao, no testaremos a condio de erro nos programas futuros neste Captulo. No entanto, em qualquer aplicao sria, esse teste deveria ser includo.

Fechando Arquivos
Ainda que todos os arquivos abertos sejam fechados pelo sistema operacional ao trmino da aplicao, consiste em boa regra de programao fechar, explicitamente, qualquer arquivo que tenha sido aberto durante a execuo do programa
int fclose(FILE *stream);

Todos os buffers associados com o arquivo so esvaziados antes do fechamento do arquivo.


185

Leitura e gravao de caracteres


A leitura e gravao de caracteres se d atravs do uso de funes semelhantes s usadas para entrada e sada de caracteres de standard input/standard output. Estas funes so apresentadas a seguir: fgetc(), fputc() <STDIO.H>

fgetc() l um caracter de um arquivo fputc() escreve um caracter em um arquivo

Declarao int fgetc(FILE *stream); int fputc(int c, FILE *stream); Comentrios


fgetc() retorna o prximo caracter no arquivo especificado. fputc() escreve o caracter c no arquivo especificado.

Valor de Retorno Em caso de sucesso, fgetc() retorna o caracter lido (convertido para um nmero inteiro sem sinal) fputc() retorna o caracter c. Em caso de erro, ou ao atingir o final do arquivo, fgetc() retorna EOF Em caso de erro, fputc() retorna EOF Exemplos: Escrevendo um arquivo caracter a caracter: O programa abaixo l uma seqncia de caracteres do teclado e os grava em um arquivo. O programa termina quando o usurio pressionar o Return.
void main() { FILE *pFILE; char ch; pFILE=fopen("saida.dat", "w"); while((ch=getchar())!='\n') fputc(ch, pFILE); fclose(pFILE); }

186

Lendo um arquivo caracter a caracter: O programa a seguir escreve na tela do computador caracteres lidos de um arquivo cujo nome passado como argumento na linha de comando.
void main(int argc, char *argv[]) FILE *pFILE; int ch; pFILE=fopen(argv[1], "r"); while ((ch=fgetc(pFILE))!=EOF) putchar(ch); fclose(pFILE); } {

Fim de arquivo (EOF)


No exemplo anterior, os caracteres do arquivo de entrada foram lidos at que (ch = EOF). EOF (End Of File) enviado ao programa pelo sistema operacional quando este detecta o final do arquivo. importante entender que EOF no um caracter e sim um nmero inteiro, definido em stdio.h com o valor -1. Observe que a funo fgetc() retorna um inteiro, a fim de que o caracter de cdigo ASCII 255, se porventura presente ao arquivo, no seja interpretado como o EOF.

Leitura e gravao de linhas


De maneira anloga a leitura e escrita de strings em standard input / standard output existem na biblioteca C funes para a entrada e sada de linhas em arquivos. Chamamos de linha a uma seqncia de caracteres terminada pelo caracter de nova linha (\n). As funes para a entrada e sada de linhas so fgets() e fputs().

fgets(), fputs()

<STDIO.H>

fgets() l uma linha do arquivo fputs() escreve uma linha no arquivo

Declarao char *fgets(char *s, int n, FILE *stream); int fputs(const char *s, FILE *stream); Comentrios

187

fgets() l uma seqncia de caracteres do arquivo e os armazena na string s. A leitura interrompida quando so lidos (n-1) caracteres ou quando for lido um caracter de nova linha (\n), o que ocorrer primeiro. fgets() mantm o caracter de nova linha no final de s e insere o caracter nulo, para marcar o final da string. fputs() copia a string s (terminada por um \0) para o arquivo especificado. Ela

no acrescenta o caracter de nova linha string e o caracter nulo no copiado. Valor de Retorno Em caso de sucesso, fgets() retorna a string apontada por s. fputs() retorna o ltimo caracter escrito. Em caso de erro, ou ao atingir o final do arquivo, fgets() retorna NULL. Em caso de erro, fputs() retorna EOF.

Comparao gets() x fgets()


A funo fgets() semelhante funo gets() mas h alguns pontos onde elas so distintas. A tabela abaixo apresenta a comparao das duas funes: gets() teclado \n no includo includo fgets() arquivo \n ou EOF includo includo

entrada trmino de leitura \n ao final \0 ao final

Exemplos: Lendo um arquivo linha a linha: O programa abaixo l uma seqncia de linhas de um arquivo dado e as ecoa em stdout.
void main(int argc, char *argv[]) FILE *pFILE; char linha[80]; {

if((pFILE=fopen(argv[1],"r"))==NULL) puts("Nao pude abrir o arquivo"); return; } while(fgets(linha, 80, pFILE)!=NULL) printf("%s", linha); fclose(pFILE); }

188

Gravando um arquivo linha a linha: O programa abaixo l uma seqncia de linhas de stdin e as grava no arquivo de sada.
void main(int argc, char *argv[]) FILE *pFILE=fopen(argv[1],"w"); char linha[80]; while(strlen(gets(linha))>0) fputs(linha, pFILE); fputs("\n", pFILE); } fclose(pFILE); } { {

O programa termina quando o usurio entra com uma linha vazia. Observe que a funo fputs() no coloca o caracter de nova linha ('\n') ao final da linha sendo impressa, o que ns tivemos de fazer explicitamente atravs de outra chamada fputs().

Entrada e sada formatada


Sada formatada Sada formatada efetuada com a funo fprintf(). Esta funo similar a printf(), exceto que um ponteiro para FILE tomado como primeiro argumento. fprintf() <STDIO.H>

Envia sada formatada para um arquivo. Declarao int fprintf (FILE *stream, char *format [, argument, ...]); Comentrios A funo fprintf() recebe uma srie de argumentos, aplica a cada um deles um padro de formatao especificado na string *format e escreve o dado formatado em um arquivo. A funo aplica o primeiro padro de formatao ao primeiro argumento, o segundo padro ao segundo argumento, o terceiro ao terceiro, e assim por diante, at o ltimo dos argumentos. Observao: Devem existir suficientes argumentos para serem formatados. Se houver menos argumentos do que padres de formatao os resultados sero imprevisveis e,
189

provavelmente, desastrosos. Argumentos em excesso (em maior nmero do que os requeridos pela string de formatao) so simplesmente ignorados. Valor de Retorno Em caso de sucesso a funo fprintf() retorna o nmero de bytes escritos no arquivo de sada. Em caso de erro a funo retorna EOF

Exemplo:
#define DIM 3 typedef struct { char nome[20]; unsigned codigo; float salario; } RegFunc; void main() { RegFunc func[DIM]={{"Jorge", 3554, 1000.0}, {"Paulo", 1234, 500.0}, {"Carlos", 755, 720.0}}; FILE *pFILE=fopen("saida.dat", "w"); int i; for (i=0; i<DIM; i++) fprintf(pFILE, "%s %u %g\n", func[i].nome, func[i].codigo, func[i].salario); fclose(pFILE); }

Aps a execuo do programa, o contedo do arquivo saida.dat ser:


Jorge 3554 1000 Paulo 1234 500 Carlos 755 720

Entrada formatada
Entrada formatada efetuada com a funo fscanf(). Esta funo similar scanf(), exceto que, assim como em fprintf(), um ponteiro para FILE tomado como primeiro argumento. fscanf() <STDIO.H>

fscanf() l e formata dados lidos de um arquivo de entrada.

190

Declarao
int fscanf (FILE *stream, char *format [, address, ...]);

Comentrios A funo fscanf() l uma srie de campos de entrada (um caracter por vez), formata cada campo de acordo com um especificador de formato (passado na string de formatao *format) e armazena a entrada formatada no endereo passado como argumento aps a string *format. Deve haver um especificador de formato e um endereo para cada campo lido. fscanf(), em geral, leva a resultados inesperados se o especificador de formato diverge do dado lido. A combinao de fgets() seguida por sscanf() mais segura e mais simples e, portanto, recomendada sobre fscanf(). Valor de Retorno Em caso de sucesso, fscanf() retorna o nmero de campos de entrada lidos, formatados e armazenados com sucesso. fscanf() retorna EOF se for feita uma tentativa de leitura alm do final do arquivo. Exemplo: Vamos usar como entrada o programa saida.dat gerado no exemplo anterior.
#define DIM 3 typedef struct { char nome[20]; unsigned codigo; float salario; } RegFunc; void main() { int i; RegFunc func[DIM]; FILE *pFILE=fopen("saida.dat", "r"); for(i=0; i<DIM; i++) fscanf(pFILE, "%s%u%g", func[i].nome, &func[i].codigo, &func[i].salario); fclose(pFILE); }

Aps a execuo do programa, o contedo das variveis ser:


func[0].nome==Jorge func[0].codigo==3554 func[0].salario==1000 func[1].nome==Paulo

191

func[1].codigo==1234 func[1].salario==500 func[2].nome==Carlos func[2].codigo==755 func[2].salario==720

Lendo e gravando registros


As funes fread() e fwrite() possibilitam uma maneira de transferir blocos de dados do disco para a memria do computador e vice-versa. Elas se mostram como uma excelente alternativa para armazenar uma grande quantidade de dados numricos. Isto se deve ao fato de que, em primeiro lugar, os dados so armazenados em modo binrio, ocupando portanto menos espao em disco. Em segundo lugar, possvel com uma nica instruo gravar ou ler todo um vetor, ou uma matriz, um registro ou, at mesmo, um vetor de registros. Vamos estudar as funes fread() e fwrite() com mais detalhes.

Transferindo blocos de dados para o disco. A transferncia de blocos de dados da memria principal do computador para o disco ser feita com a funo fwrite(). fwrite() <STDIO.H>

Escreve um bloco de dados em um arquivo. Declarao


size_t fwrite(void *ptr, size_t size, size_t n, FILE* stream);

Comentrios
fwrite() escreve um nmero especificado de blocos de dados, de mesmo tamanho,

em um arquivo de sada. Argumento


ptr size n stream

Significado Ponteiro para o bloco de dados; os dados a serem escritos comeam em ptr. Tamanho de cada bloco de dados (em bytes) Nmero de blocos a serem escritos Ponteiro para o arquivo de sada.

O nmero total de bytes escritos (n * size)


192

Valor de Retorno
fwrite() retorna o nmero de blocos (de blocos, no de bytes) de fato escritos.

Exemplo: Vamos estudar o mesmo programa usado para exemplificar o uso de sada de dados formatada.
#define DIM 3 typedef struct { char nome[20]; unsigned codigo; float salario; } RegFunc; void main() { RegFunc func[DIM]={{"Jorge", 3554, 1000.0}, {"Paulo", 1234, 500.0}, {"Carlos", 755, 720.0}}; FILE *pFILE=fopen("saida.dat", "w"); fwrite(func, sizeof(RegFunc), DIM, pFILE); fclose(pFILE); }

Aps a execuo do programa, o contedo do arquivo saida.dat (em hexadecimal e em ASCII) ser:
4A 00 00 00 00 6F 00 00 00 00 72 00 00 FA 00 67 00 00 43 00 65 E2 00 43 00 00 0D 00 61 00 00 00 00 72 00 00 00 00 6C 00 00 7A 00 6F F3 00 44 00 73 02 00 50 00 00 00 00 61 00 00 00 00 75 00 00 34 00 6C 00 00 44 00 6F D2 00 00 00 04 00 Jorge zDPaulo CCarlos 4D

Observe que todo o vetor foi gravado com uma nica chamada funo fwrite(). Compare com o cdigo que necessrio para gravar o vetor usando a funo fprintf(). Observe ainda que a funo fwrite() efetua um dump de memria, isto , os dados so gravados em disco na mesma forma em que eles eram armazenados na memria do computador. Por este motivo, o arquivo resultante dito um arquivo binrio (em oposio ao arquivo texto obtido quando da gravao dos dados usando fprintf()). Observe por exemplo o cdigo do funcionrio Jorge. O nmero inteiro sem sinal 3554 armazenado como 0DE2 (sua representao binria). Da mesma forma o salrio de Jorge (1000.0) armazenado como 00 00 7A 44. Um arquivo binrio no pode ser imediatamente lido a partir de um comando type ou cat. Os pontos na representao ASCII no exemplo anterior (coluna direita) correspondem a caracteres no imprimveis no terminal ou impressora.
193

Transferindo blocos do disco para a memria. A transferncia de blocos do disco para a memria feita com a funo fread(), complementar fwrite(). fread() <STDIO.H>

L um bloco de dados de um arquivo. Declarao


size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Comentrios
fread() l um nmero especificado de blocos de dados, de mesmo tamanho, de um

arquivo de entrada. Argumento


ptr size n stream

Significado Incio da rea de memria que receber os dados lidos. Tamanho de cada bloco lido (em bytes) Nmero de blocos a serem lidos Ponteiro para o arquivo de entrada.

O nmero total de bytes lidos (n * size) Valor de Retorno


fread() retorna o nmero de blocos (no de bytes) de fato lidos.

Exemplo Vamos usar o arquivo saida.dat obtido no exemplo anterior.


#define DIM 3 typedef struct { char nome[20]; unsigned codigo; float salario; } RegFunc; void main() { int i; RegFunc func[DIM]; FILE *pFILE=fopen("saida.dat", "rb"); fread(func, sizeof(RegFunc), DIM, pFILE); fclose(pFILE); }

194

Aps a execuo do programa acima, as variveis do programa valem:


func[0].nome==Jorge func[0].codigo==3554 func[0].salario==1000 func[1].nome==Paulo func[1].codigo==1234 func[1].salario==500 func[2].nome==Carlos func[2].codigo==755 func[2].salario==720

Observe o comando de abertura do arquivo:


pFILE=fopen("saida.dat", "rb");

A string "rb" indica ao sistema operacional que o arquivo deve ser aberto no modo de leitura ("r") e que trata-se de um arquivo binrio ("b"). Esta ltima informao necessria para que o caracter 0x0D, se presente no arquivo, no seja interpretado como o caracter de nova linha.

Acesso aleatrio
Todo arquivo aberto tem a ele associado um ponteiro para a posio corrente no arquivo. Esse ponteiro fornece a localizao do prximo byte a ser lido ou escrito. Em todos os exemplos apresentados at agora, os arquivos eram acessados de forma seqencial, isto , em operaes de escrita os dados eram escritos em posies contguas de memria . Da mesma forma para ler um dado, digamos, no meio de um arquivo, era necessrio ler a primeira posio do arquivo e prosseguir at encontrar o dado desejado. O acesso aleatrio ao arquivo, por sua vez, permite que a transferncia de dados seja feita de/para qualquer posio do arquivo sem que as informaes anteriores precisem ser acessadas. Isto conseguido com o uso da funo fseek() que altera o ponteiro de posio corrente para a localizao desejada. Segue-se ento uma operao elementar de leitura ou gravao seqencial dos dados.

fseek()

<STDIO.H>

Reposiciona o ponteiro de posio corrente em um arquivo Declarao


int fseek(FILE *stream, long offset, int origem);

195

Comentrios
fseek() altera o ponteiro associado a um arquivo para uma nova posio.

Argumento
stream offset

Significado Arquivo cujo ponteiro alterado por fseek() Diferena em bytes entre origem (uma posio relativa no arquivo) e a nova posio. Para arquivos texto, offset deve ser 0. origem Trs valores possveis: 0. comeo do arquivo 1. posio corrente do ponteiro 2. fim do arquivo Observe que o offset, contado a partir do fim do arquivo, deve ser negativo. Da mesma forma, o offset contado a partir do incio do arquivo deve ser positivo. Valor de Retorno Em caso de sucesso (o ponteiro movido com sucesso), fseek() retorna 0. Em caso de erro, fseek() retorna um valor no nulo. Exemplo: Vamos rever o programa exemplo anterior. Suponha, que queremos ler apenas o ltimo registro do vetor de funcionrios. A soluo seria:
#define DIM 3 typedef struct { char nome[20]; unsigned codigo; float salario; } RegFunc; void main() { RegFunc func[DIM]; FILE *pFILE=fopen("saida.dat", "rb"); fseek(pFILE, (DIM-1)*sizeof(RegFunc), 0); fread(&func[DIM-1], sizeof(RegFunc), 1, pFILE); printf("nome : %s\n", func[DIM-1].nome); printf("codigo : %u\n", func[DIM-1].codigo); printf("salario : %g\n", func[DIM-1].salario); fclose(pFILE); }

O valor impresso pelo programa ser:


nome : Carlos

196

codigo : 755 salario : 720

o que confirma a correo do programa.

Arquivos padro
Uma caracterstica interessante da linguagem C a de que os perifricos so tratados exatamente como arquivos. Assim, quando um programa inicia a execuo, o sistema operacional abre automaticamente alguns arquivos predefinidos. Estes arquivos, definidos em <STDIO.H>, so:

Nome
stdin stdout stderr stdaux stdprn

Significado Dispositivo padro de entrada Dispositivo padro de sada Dispositivo padro para mensagens de erro Dispositivo auxiliar padro Impressora padro

Cada um desses nomes pode ser usado como um ponteiro para uma estrutura FILE associada ao perifrico correspondente. Seja, por exemplo, a instruo:
fputc(ch, stdout);

Essa instruo imprime um caracter no vdeo e, portanto, equivalente a:


putchar(ch)

De maneira anloga, a instruo:


ch = fgetc(stdin);

l um caracter do teclado e equivalente a:


ch = getchar();

Os arquivos standard so automaticamente fechados quando o programa encerra a execuo.


197