Você está na página 1de 73

Programao em C

1 Introduo A linguagem C uma linguagem de programao popular e amplamente utilizada para criar programas de computador. Programadores no mundo todo utilizam a linguagem C, pois ela oferece o mximo controle e eficcia. Se voc um programador, ou pretende se tornar um, eis alguns benefcios que voc desfrutar ao aprender a linguagem C: voc poder ler e escrever cdigo para diversas plataformas. Desde microcontroladores at sistemas cientficos mais avanados podem ser criados em C e diversos sistemas operacionais modernos so escritos em C; o avano para a linguagem C++ orientada para objeto torna-se muito mais fcil. A linguagem C++ uma extenso de C e praticamente impossvel aprender C++ sem antes aprender C.

Esta animao mostra a execuo de um programa simples em C. Ao final deste artigo, voc entender como ela funciona

2 O que C? C uma linguagem de programao de computadores . Isso significa que voc pode us-la para criar listas de instrues para um computador seguir. A linguagem C uma das milhares de linguagens de programao atualmente em uso. Existe h vrias dcadas e ganhou ampla aceitao por oferecer aos programadores o mximo em controle e eficincia. A linguagem C fcil de aprender: pode ter um estilo um tanto criptogrfico comparada s outras linguagens, mas isso logo superado.

A linguagem C o que se chama de linguagem compilada. Isso significa que, uma vez escrito o programa em C, ele deve ser passado por um compilador para transformar seu programa em executvel para o computador rodar (executar). Um programa em C possui um formato legvel ao homem, enquanto o executvel gerado no compilador possui a forma legvel para a mquina e executada por ela. Isto significa que para escrever e executar um programa em C, voc precisa ter acesso a um compilador de C. Se estiver usando uma mquina UNIX (por exemplo, escrevendo scripts CGI em C no seu computador UNIX, ou se voc um estudante que trabalha em uma mquina UNIX de um laboratrio), o compilador de C est disponvel gratuitamente. Ele chamado cc ou gcc e est disponvel na linha de comando. Se voc um aluno, esto a escola lhe fornecer um compilador (descubra qual a escola usa e aprenda mais sobre ele). Se estiver trabalhando em casa em uma mquina Windows, voc precisar fazer o download de um compilador de C gratuito ou comprar um compilador comercial. Um compilador comercial amplamente utilizado o ambiente Visual C++ da Microsoft (ele compila programas em C e C++). Mas, infelizmente, este programa custa caro. Caso no disponha de algumas centenas de dlares para gastar em um compilador comercial, voc pode usar um dos compiladores gratuitos disponveis na internet. Veja delorie.com/djgpp como ponto de partida em sua pesquisa. Vamos comear com um programa em C bastante simples e progredir a partir dele. Vamos supor que voc usar a linha de comando UNIX e o gcc como seu ambiente

para estes exemplos, mas caso contrrio, todos os cdigos tambm funcionaro (basta compreender e usar o compilador que tiver disponvel). 3 O mais simples programa em C Vamos comear com o programa em C mais simples possvel e us-lo tanto para entender os fundamentos da linguagem C como o processo de compilao em C. Digite o programa seguinte em um editor de textos padro (vi ou emacs no UNIX, Bloco de notas no Windows ou TeachText no Macintosh). Depois salve o programa em um arquivo denominado samp.c. Se deixar de incluir .c, voc provavelmente receber informao de algum tipo de erro ao compil-lo (portanto, no se esquea de inserir o .c). Certifique-se tambm de que o editor no anexe automaticamente caracteres extras (como .txt) ao nome do arquivo. Eis o primeiro programa: #include <stdio.h> int main() { printf("Este o resultado do meu primeiro program!\n"); return 0; } Quando executado, este programa instrui o computador a imprimir a linha: Este o resultado do meu primeiro programa!, e depois encerra o programa. Posio Ao escrever este programa, posicione #include de forma que o sinal # esteja na coluna 1 (mais esquerda). Isso facilita o seu entendimento, mas na verdade o espaamento e recuo podem ser do jeito que voc preferir. Em alguns sistemas UNIX, voc encontrar um programa chamado cb, o C Beautifier (algo como embelezador de C), que formata cdigos. O espaamento e recuo mostrados acima so um bom exemplo a seguir. Para compilar este cdigo, siga estas etapas: em uma mquina UNIX, digite gcc samp.c -o samp (se gcc no funcionar, tente cc). Esta linha executa o compilador em C chamado gcc, pede que compile samp.c e que nomeie o arquivo executvel criado como samp. Para executar o programa, digite samp (ou em algumas mquinas UNIX, ./samp); em uma mquina rodando DOS ou Windows e usando DJGPP (em ingls), digite o seguinte no prompt do MS-DOS: gcc samp.c -o samp.exe. Esta linha executa o compilador em C chamado gcc, Pede que compile samp.c e que nomeie o arquivo executvel criado como samp.exe. Para executar o programa, digite samp; se estiver trabalhando com algum outro compilador ou sistema de desenvolvimento, leia e siga as instrues dele para compilar e executar o programa. Voc ver Este o resultado do meu primeiro programa! ao execut-lo. Veja o que aconteceu ao compilar o programa:

Caso tenha cometido um erro de digitao no programa, ele no compilar ou no poder ser executado. Se o programa no foi compilado ou no executa corretamente, editeo e localize os erros de digitao. Corrija o erro e tente novamente. 4 O mais simples programa em C: o que est acontecendo? Vamos analisar este programa e aprender o que as diferentes linhas de comando fazem: este programa em C comea com #include <stdio.h>. Esta linha inclui uma biblioteca padro de I/O (entrada/sada) em seu programa, que permite ler a entrada a partir do teclado (denominada entrada padro), exibir o resultado em uma tela (denominada sada padro), processar arquivos de texto armazenados em disco e assim por diante. uma biblioteca extremamente til. A linguagem C possui um grande nmero de bibliotecas padro como stdio, incluindo bibliotecas de strings de caracteres, de horrio e de funes matemticas. Uma biblioteca simplesmente um pacote de cdigos que algum escreveu anteriormente para simplificar a sua vida (discutiremos bibliotecas daqui a pouco); a linha int main( ) declara a funo principal. Todo programa em C deve ter uma funo denominada main em algum lugar no cdigo. Aprenderemos mais sobre as funes em breve. O programa comea a ser executado a partir da primeira linha da funo main; em C, os smbolos {e} marcam o comeo e trmino de um bloco de cdigo. Neste caso, o bloco de cdigo que compe a funo principal contm duas linhas;

a instruo printf em C permite enviar o resultado para a sada padro (para ns, a tela). A parte entre aspas denominada string de formato e descreve como os dados sero formatados quando impressos. A string de formato pode conter strings de caracteres como Este o resultado do meu primeiro programa!, smbolos de quebra de linha ( \n), e operadores como expresso de controle para variveis (vide abaixo). Se voc est usando UNIX, pode digitar man 3 printf para obter uma documentao completa sobre a funo printf. Caso contrrio, consulte a documentao includa em seu compilador para mais detalhes sobre a funo printf; a linha return 0; faz com que a funo retorne um cdigo de erro de 0 (sem erros) shell que iniciou a execuo. Esta capacidade ser discutida em detalhes mais tarde.

5 Variveis Como programador, voc com freqncia desejar que seu programa se lembre de um valor. Por exemplo, se seu programa solicita um valor do usurio, ou se ele calcula um valor, voc vai querer armazen-lo em algum lugar para us-lo mais tarde. O modo como seu programa se lembra das coisas por meio do uso de variveis. Por exemplo: int b; Esta linha diz: Quero criar um espao chamado b que possa conter um valor inteiro. Uma varivel tem um nome (neste caso, b) e um tipo (neste caso, int, um inteiro). Voc pode armazenar um valor em b escrevendo algo assim: b = 5; Voc pode usar o valor em b escrevendo algo assim: printf(%d, b); Na linguagem C, h vrios tipos padres de variveis: int valores inteiros (nmero inteiro); float valores de ponto flutuante; char valores de caractere nico (como m ou Z). 6 Printf A instruo printf permite enviar o resultado para a sada padro. Para ns, o termo sada padro se refere tela (embora voc possa redirecionar a sada padro para um arquivo de texto ou outro comando). Eis outro programa que o ajudar a aprender mais sobre printf: #include <stdio.h> int main( ) { int a, b, c; a = 5; b = 7; c = a + b; printf("%d + %d = %d\n", a, b, c); return 0; }

Digite este programa em um arquivo e salve-o como add.c. Compile-o com a linha gcc add.c -o add e depois o execute digitando add (ou ./add). Voc ver a linha 5 + 7 = 12 como resultado. Eis uma explicao das diferentes linhas neste programa: a linha int a, b, c; declara trs variveis de nmero inteiro denominadas a, b e c. a prxima linha inicializa a varivel nomeada a para o valor 5. a prxima linha define 7 para b . a prxima linha soma a e b e "atribui" o resultado a c. O computador adiciona o valor em a (5) ao valor em b (7) para formar o resultado 12 e ento coloca o novo valor (12) na varivel c. Por este motivo, o sinal = nesta linha denominado operador de atribuio. a instruo printf depois imprime a linha 5 + 7 = 12. As expresses de controle %d na instruo printf atuam como expresso de controle de valores. H 3 expresses de controle %d, e no final da linha printf h trs nomes de varivel: a, b e c. A linguagem C liga o primeiro %d ao a e o substitui por 5. Ele liga o segundo %d com b e o substitui por 7, faz a correspondncia do terceiro %d com c e o substitui por 12. Depois, imprime a linha completa na tela: 5 + 7 = 12. O +, o = e o espaamento so parte da linha de formato e so automaticamente integrados entre os operadores %d conforme especificado pelo programador.

7 Printf: lendo os valores de usurio O programa anterior bom, mas seria melhor se ele lesse os valores 5 e 7 inseridos pelo usurio, em vez de usar constantes. Em vez disso, tente este programa: #include <stdio.h> int main() { int a, b, c; printf("Entre o primeiro valor:"); scanf("%d", &a); printf("Entre o segundo valor:"); scanf("%d", &b); c = a + b; printf("%d + %d = %d\n", a, b, c); return 0; }

Eis como este programa funciona ao ser executado:

Faa as alteraes, depois compile e rode o programa para certificar-se de que funciona. Observe que scanf usa as mesmas strings de formato que printf (digite man scanf para mais informao). Observe tambm o & na frente de a e b. Este o operador de endereo em C: ele retorna o endereo da varivel (isto no far sentido at aprendermos sobre os ponteiros). Voc tem de usar o operador & em scanf em qualquer varivel do tipo char, int ou float, bem como tipos de estrutura (que discutiremos em breve). Se excluir o operador &, voc receber um erro ao executar o programa. Tente execut-lo para ver que tipo de erro ocorre. Vamos ver algumas variaes para entender printf completamente. Eis uma instruo simples de printf: printf(Hello); Esta chamada para printf tem uma string de formatos que diz ao printf para enviar a palavra Hello para a sada padro. Compare-a com: printf(Hello\n); A diferena entre as duas que a segunda verso exibe a palavra Hello seguida de uma quebra de linha. As linha seguintes mostram como exibir o valor de uma varivel usando printf. printf("%d", b); O %d uma expresso de controle que ser substituda pelo valor da varivel b quando a instruo printf for executada. Freqentemente, voc vai desejar incluir o valor entre outras palavras. Uma forma de fazer isso : printf("A temperatura "); printf("%d", b); printf(" degrees"); Um modo mais fcil de dizer isso : printf("A temperatura %d graus\n", b); Voc tambm pode usar mltiplas expresses de controle %d em uma instruo printf: printf("%d + %d = %d\n", a, b, c); Na instruo printf, extremamente importante que o nmero de operadores na string de formato corresponda exatamente ao nmero e tipo de variveis que a seguem. Por exemplo, se a string de formatos contm trs operadores %d, ento ela deve ser seguida por

exatamente trs parmetros do mesmo tipo e ordem que aqueles especificados pelos operadores. Voc pode imprimir todos os smbolos normais na linguagem C com printf usando expresses de controle diferentes: int (valores inteiros) usam %d; float (valores de ponto flutuante) usam %f; char (valores de caractere simples) usam %c; strings de caracteres (matrizes de caracteres, discutiremos mais tarde) usam %s. Voc pode aprender mais sobre as nuances do printf em uma mquina UNIX digitando man 3 printf. Qualquer outro compilador de C que voc utilize provavelmente vir acompanhado de um manual ou arquivo de ajuda que contm uma descrio do printf. 8 Scanf A funo scanf permite aceitar entradas do dispositivo padro , que, para ns, geralmente o teclado. A funo scanf pode fazer muitas coisas diferentes, mas pode ter resultados incertos quando no usada de forma simples. falvel pois no lida muito bem com erros humanos. Mas para programas simples, ela boa o suficiente e fcil de usar. A aplicao mais simples de scanf se parece com: scanf("%d", &b); O programa ler um valor inteiro digitado pelo usurio usando o teclado (%d para inteiros, como em printf, assim b deve ser declarado como um int) e o colocar em b. A funo scanf usa as mesmas expresses de controle da printf: int usa %d; float usa %f; char usa %c; strings de caracteres (abordados mais tarde) usam %s. Voc DEVE colocar & na frente da varivel usada em scanf. A razo para isso ficar clara assim que voc aprender sobre os ponteiros. fcil esquecer o sinal &, e se voc esquecer, seu programa quase sempre apresentar problemas ao ser executado. Em geral, melhor usar scanf como mostrado aqui, lendo apenas um valor do teclado. Use mltiplas chamadas do scanf para ler valores mltiplos. Em qualquer programa real, voc usar as funes gets ou fgets em vez de ler o texto em uma linha por vez. Ento voc far a anlise da linha para ler seus valores. Isso serve para detectar erros na entrada e control-los da maneira que achar adequada. As funes printf e scanf exigiro um pouco de prtica para serem inteiramente compreendidas, mas uma vez dominadas sero extremamente teis.

Tente isto! Modifique este programa para que ele aceite 3 valores em vez de 2 e some todos eles juntos: #include <stdio.h> int main() { int a, b, c; printf("Entre o primeiro valor:"); scanf("%d", &a); printf("Entre o segundo valor:"); scanf("%d", &b); c = a + b; printf("%d + %d = %d\n", a, b, c); return 0; } Tente apagar ou adicionar caracteres ou palavras aleatrias em um dos programas anteriores e veja como o compilador reage a tais erros de compilao. Por exemplo, apague a varivel b na primeira linha do programa anterior e veja o que o compilador faz quando voc se esquece de declarar uma varivel. Apague um ponto-e-vrgula e veja o que acontece. Omita uma das chaves. Remova um dos parnteses prximos funo principal. Faa uma alterao por vez e compile o programa para ver o que acontece. Simulando erros como esses voc pode aprender sobre diferentes erros de compilao, o que facilitar futuras deteces quando voc os cometer. Erros a serem evitados na linguagem C Usar letras maisculas e minsculas aleatoriamente. Letras maisculas e minsculas so importantes na linguagem C, portanto voc no pode digitar Printf ou PRINTF. obrigatrio que seja printf; Esquecer de usar o & em scanf; Parmetros em excesso ou a falta deles aps a instruo de formato em printf ou scanf; Esquecer de declarar o nome de uma varivel antes de utiliz-la.

9 Desvio e looping Em C, as instrues if e loops while regem-se pelos princpios das expresses Booleanas. Eis um programa simples em C que demonstra uma instruo if:

#include <stdio.h> int main() { int b; printf("Digite um valor:"); scanf("%d", &b); if (b < 0) printf("O valor negativo "); return 0; } Este programa aceita um nmero do usurio. Ele ento testa esse nmero utilizando uma instruo if para ver se ele menor que 0. Se for, o programa imprime uma mensagem. Caso contrrio, o programa no faz nada. A parte (b < 0) do programa a expresso booleana. A linguagem C avalia esta expresso para decidir se imprime ou no a mensagem. Se a expresso booleana se mostra Verdadeira, ento a linguagem C executa a linha imediatamente posterior instruo if (ou um bloco de linhas entre chaves logo aps a instruo if). Se a expresso booleana se mostrar Falsa, ento a linguagem C pula a linha ou bloco de linhas logo aps a instruo if.

10

Eis um exemplo um pouco mais complexo: #include <stdio.h> int main() { int b; printf("Digite um valor:"); scanf("%d", &b); if (b < 0) printf("O valor negativo "); else if (b == 0) printf("O valor zero "); else printf("O valor positivo "); return 0; } Neste exemplo, as sees else if e else avaliam tanto para valores positivos como zero. 11

Eis uma expresso booleana mais complicada: if ((x==y) && (j>k)) z=1; else q=10; Esta instruo diz: Se o valor da varivel x for igual ao valor da varivel y, e se o valor da varivel j for maior que o valor da varivel k, ento defina a varivel z como 1; de outro modo, defina a varivel q como 10. Voc usar instrues if como esta em todos os seus programas C para tomar decises. De um modo geral, a maioria das decises ser simples como o primeiro exemplo, mas, eventualmente, as coisas podem ser um pouco mais complicadas. Observe que a linguagem C utiliza == para testar a igualdade, enquanto utiliza = para atribuir um valor a uma varivel. O smbolo && em C representa uma operao booleana AND. Aqui esto todos os operadores booleanos em C: igualdade == menor que < maior que > menor ou igual <= maior ou igual >= desigualdade != e && ou || no ! Voc descobrir que as instrues while so to fceis de utilizar como as instrues if. Por exemplo: while (a < b) { print("%d\n", a); a = a + 1; } Isso faz com que duas linhas entre chaves sejam executadas repetidamente at que a seja maior ou igual a b. Em geral, a instruo while funciona assim:

A linguagem C tambm oferece uma estrutura do-while: 12

do { printf("%d\n", a); a = a + 1; } while (a < b); O loop for na linguagem C apenas um atalho para expressar uma instruo while. Por exemplo, suponha que voc tenha o seguinte cdigo em C: x=1; while (x<10) { bl bl bl x++; /* x++ o mesmo que dizer x=x+1 */ } Voc pode converter isso em um loop for da seguinte forma: for(x=1; x<10; x++) { bl bl bl } Observe que o loop while contm uma etapa de inicializao ( x=1), uma etapa de teste (x<10) e uma de incremento (x++). O loop for permite colocar as trs partes em uma nica linha, mas voc pode colocar qualquer coisa nelas. Por exemplo, suponha que voc tenha o seguinte loop: a=1; b=6; while (a < b) { a++; printf("%d\n",a); } Voc tambm pode coloc-lo para indicao: for (a=1,b=6; a < b; a++,printf("%dn",a)); um pouco confuso, mas possvel. O operador vrgula permite separar diversas instrues diferentes nas sees de inicializao e incremento do loop for (porm no na seo de teste). Muitos programadores de linguagem C gostam de concentrar muitas informaes em uma nica linha de cdigo. Outros acham que isto torna o cdigo mais difcil de entender, e portanto desmembram essas instrues.

13

= versus == em expresses booleanas O sinal == um problema na linguagem C pois frequentemente voc se esquece e digita apenas = em uma expresso booleana. Este um erro comum de ocorrer, mas para o compilador h uma diferena significativa. A linguagem C aceitar = e == em uma expresso booleana, mas o comportamento do programa mudar consideravelmente quando usamos um ou outro. As expresses booleanas em C avaliam os inteiros e os inteiros podem ser usados dentro de expresses booleanas. O valor inteiro 0 em C Falso, enquanto qualquer outro valor inteiro Verdadeiro. cdigo seguinte permitido em C: #include <stdio.h> int main() { int a; printf("Digite um nmero:"); scanf("%d", &a); if (a) { printf("O valor verdadeiro\n"); } return 0; } Se a for qualquer nmero diferente de 0, a instruo printf executada. Em C, uma instruo como if (a=b) significa: Atribuir b para a, e ento testar a para seu valor Booleano". Assim, se a retornar o valor 0, a instruo if Falsa. Caso contrrio, Verdadeira. O valor de a muda durante o processo. Este no o comportamento desejado se voc pretendeu digitar == (embora esta caracterstica seja til quando usada corretamente), assim, tenha cuidado com o uso de = e ==. 10 Looping: um exemplo real Suponhamos que voc queira criar um programa que imprima uma tabela de converso Fahrenheit para Celsius. Isso pode ser facilmente obtido com um loop for ou loop while: #include <stdio.h> int main() { int a; a = 0;

14

while (a <= 100) { printf("%4d graus F = %4d graus C\n", a, (a - 32) * 5 / 9); a = a + 10; } return 0; } Se executar este programa, ele produzir uma tabela de valores iniciando em 0 graus F e terminando em 100 graus F. O resultado ser este:
0 10 20 30 40 50 60 70 80 90 100 graus graus graus graus graus graus graus graus graus graus graus F F F F F F F F F F F = = = = = = = = = = = -17 -12 -6 -1 4 10 15 21 26 32 37 graus graus graus graus graus graus graus graus graus graus graus C C C C C C C C C C C

Os valores da tabela esto em incrementos de 10 graus. Observe como fcil alterar os valores iniciais, finais ou de incremento da tabela que o programa produz. Para valores mais precisos, voc pode usar valores de ponto flutuante: #include <stdio.h> int main() { float a; a = 0; while (a <= 100) { printf("%6.2f graus F = %6.2f graus C\n", a, (a - 32.0) * 5.0 / 9.0); a = a + 10; } return 0; } Voc pode ver que a declarao de a foi alterada para flutuante, e o smbolo %f substitui o smbolo %d na instruo printf. Alm disso, o smbolo %f possui alguma formatao: o valor ser impresso com 6 dgitos inteiros e 2 decimais. Agora vamos supor que queiramos modificar o programa para que a temperatura 98.6 seja inserida na tabela na posio adequada. Isto , queremos que a tabela aumente a cada 10 graus e que inclua uma linha extra para 98.6 graus F, que a temperatura corprea normal do ser humano. O programa seguinte atende essa finalidade:

15

#include <stdio.h> int main() { float a; a = 0; while (a <= 100) { if (a > 98.6) { printf("%6.2f graus F = %6.2f graus C\n", 98.6, (98.6 - 32.0) * 5.0 / 9.0); } printf("%6.2f graus F = %6.2f graus C\n", a, (a - 32.0) * 5.0 / 9.0); a = a + 10; } return 0; } Este programa funciona se o valor final for 100, mas se voc alterar o valor final para 200, ver que o programa tem um bug. Ele imprime a linha de 98.6 vrias vezes. Este problema pode ser corrigido de vrias formas. Eis uma: #include <stdio.h> int main() { float a, b; a = 0; b = -1; while (a <= 100) { if ((a > 98.6) && (b < 98.6)) { printf("%6.2f graus F = %6.2f graus C\n", 98.6, (98.6 - 32.0) * 5.0 / 9.0); } printf("%6.2f graus F = %6.2f graus C\n", a, (a - 32.0) * 5.0 / 9.0); b = a; a = a + 10; } return 0; }

16

Tente isto Tente alterar o programa conversor Fahrenheit-Celsius de modo que ele utilize scanf para aceitar o valor inicial, final e de incremento daquele usurio; Adicione uma linha de cabealho tabela que gerada; Tente encontrar uma soluo diferente para o bug corrigido pelo exemplo anterior; Crie uma tabela que converta libras em quilogramas ou milhas em quilmetros.

Erros a serem evitados na linguagem C Colocar = quando se deseja == em uma instruo if ou while; Esquecer de aumentar o contador dentro do loop while. Isso causa um loop infinito (o loop nunca acaba). Acidentalmente colocar um ; no final de um loop for ou instruo if, pois isto anula a instruo. Por exemplo: for (x=1; x<10; x++); printf("%d\n",x); imprime apenas um valor, pois o ponto-e-vrgula aps a instruo for atua como uma linha para a execuo do loop for. 11 Matrizes Nesta seo, criaremos um pequeno programa em C que gera 10 nmeros aleatrios e os ordena. Para tal, utilizaremos uma nova disposio de varivel denominada matriz. Uma matriz permite declarar e trabalhar com uma coleo de valores de mesmo tipo. Por exemplo, voc pode querer criar uma coleo de 5 inteiros. Uma forma para fazer isso seria declarar 5 inteiros diretamente: int a, b, c, d, e; Isso est certo, mas e se voc precisasse de milhares de nmeros inteiros? Uma forma mais fcil declarar uma matriz de 5 inteiros. int a[5]; Os cinco inteiros individuais dentro desta matriz so acessados por um ndice. Todas as matrizes iniciam em zero e vo at n-1 no C. Assim, int a[5]; contm 5 elementos. Por exemplo:

17

int a[5]; a[0] = 12; a[1] = 9; a[2] = 14; a[3] = 5; a[4] = 1; Uma das vantagens sobre a indexao de matriz que voc pode usar um loop para manipular o ndice. Por exemplo, o cdigo a seguir inicializa todos os valores na matriz em 0: int a[5]; int i; for (i=0; i<5; i++) a[i] = 0; O cdigo seguinte inicializa seqencialmente os valores na matriz e ento os imprime: #include <stdio.h> int main() { int a[5]; int i; for (i=0; i<5; i++) a[i] = i; for (i=0; i<5; i++) printf("a[%d] = %dn", i, a[i]); } As matrizes so usadas a toda hora em C. Para entender seu uso, inicie um editor e digite o seguinte cdigo: #include <stdio.h> #define MAX 10 int a[MAX]; int rand_seed=10; /* from K&R - retorna um nmero aleatrio entre 0 e 32767.*/ int rand() { int main() { int i,t,x,y; /* preenche a matriz */ for (i=0; i < MAX; i++) { a[i]=rand(); 18

printf("%dn",a[i]); } /* mais coisas aparecero aqui em breve */ return 0; } } Este cdigo contm vrios conceitos novos. A linha #define declara uma constante denominada MAX e a define em 10. Os nomes de constantes em geral so escritos em letras maisculas para destac-los no cdigo. A linha int a[MAX]; mostra como declarar uma matriz de inteiros em C. Observe que por causa da posio da declarao da matriz, ela global ao programa. A linha int rand_seed=10 tambm declara uma varivel global, desta vez denominada rand_seed, que inicializada em 10 sempre que o programa inicia. Este valor o inicial para o cdigo de nmeros aleatrios que segue. Em um gerador de nmeros aleatrios reais, o seed deve inicializar como um valor aleatrio, como a hora do sistema. Aqui, a funo rand produzir os mesmos valores sempre que executar o programa. A linha int rand() uma instruo de funo. A funo rand no aceita parmetros e retorna um resultado inteiro: aprenderemos mais sobre as funes em breve. As quatro linhas que seguem implementam a funo rand. Por hora, ns as ignoraremos. A funo principal normal. Quatro inteiros locais so declarados e a matriz preenchida com 10 valores aleatrios usando um loop for. Observe que a matriz a contm 10 inteiros individuais. Voc aponta para um inteiro especfico na matriz usando colchetes. Assim a[0] refere-se ao primeiro inteiro na matriz, a[1] refere-se ao segundo, e assim por diante. A linha que comea com /* e termina com */ denominada de comentrio. O compilador ignora completamente a linha de comentrio. Voc pode colocar notas para si prprio ou outros programadores nos comentrios. Agora adicione o seguinte cdigo no lugar do comentrio mais coisas...: /* ordenao por bolha da matriz */ for (x=0; x < MAX-1; x++) for (y=0; y < MAX-x-1; y++) if (a[y] > a[y+1]) { t=a[y]; a[y]=a[y+1]; a[y+1]=t; } /* imprime matriz classificada */ printf("--------------------n"); for (i=0; i < MAX; i++) printf("%dn",a[i]); Esta codificao classifica os valores aleatrios e os imprime ordenadamente. Sempre que o executar, voc obter os mesmos valores. Para alterar os valores classificados, altere o valor de rand_seed sempre que executar o programa. O nico modo fcil para realmente entender o que o cdigo est fazendo execut-lo mo. Isto , assuma que MAX 4 para facilitar, pegue uma folha de papel e 19

finja que o computador. Desenhe a matriz no papel e coloque 4 valores aleatrios e noclassificados na matriz. Execute cada linha da seo de classificao do cdigo e desenhe exatamente o que acontece. Voc ver que, sempre que submetidos ao loop interno, os valores maiores na matriz so empurrados para baixo e os valores menores vo para o topo da matriz. Tente isto No primeiro trecho do cdigo, tente alterar o loop for que preenche a matriz para uma nica linha de cdigo. Certifiquese de que o resultado seja igual ao do cdigo original; Pegue o cdigo de ordenao por bolha e coloque-o em sua prpria funo. O cabealho de funo ser void bubble_sort(). Depois transfira as variveis utilizadas pela ordenao por bolha para a funo e torne-as locais. Por ser uma matriz local, voc no precisa passar parmetros. Inicialize a semente do nmero aleatrio para diferentes valores.

Erros que devem ser evitados na linguagem C A linguagem C no tem nenhuma verificao de dimenso, portanto, se voc indexar alm do fim da matriz, ele no o informar a respeito. Ele provavelmente travar ou apresentar dados incorretos; A chamada de funo deve incluir () mesmo se nenhum parmetro for informado. Por exemplo, C aceitar x=rand; mas a chamada no funcionar. O endereo de memria da funo rand ser colocado em x. Voc deve dizer x=rand();.

12 Mais sobre matrizes Tipos de variveis H 3 tipos padro de variveis em linguagem C: inteiro: int; ponto flutuante: float; caractere: char. Um int um valor inteiro de 4 bytes. Um float um valor de ponto flutuante de 4 bytes. Um char um caractere nico de 1 byte (como a ou 3). Uma string de caracteres declarada como uma matriz de caracteres. H vrios tipos derivados: duplo (valor de ponto flutuante de 8 bytes); curto (inteiro de 2 bytes); curto sem sinal ou int sem sinal (nmeros inteiros positivos, sem bit de sinal). Operadores e precedncia de operadores Os operadores em C so semelhantes aos operadores na maioria das linguagens: +: adio; 20

-: subtrao; /: diviso; *: multiplicao; %: mod. O operador / realiza a diviso de inteiros se ambos os operandos forem nmeros inteiros; caso contrrio, realiza a diviso por ponto flutuante. Por exemplo: void main() { float a; a=10/3; printf("%f\n",a); } Este cdigo imprime um valor de ponto flutuante desde que a seja declarado como tipo float, mas a ser 3.0 porque o cdigo executou uma diviso de inteiros. A precedncia de operador em C tambm similar para a maioria das linguagens. A diviso e multiplicao ocorrem primeiro, depois a adio e a subtrao. O resultado do clculo 5+3*4 17, e no 32. Em C, o operador * tem precedncia sobre o +. Voc pode usar parnteses para alterar a ordem de precedncia normal: (5+3)*4 32. O 5+3 executado primeiro porque est entre parnteses. Abordaremos a parte da precedncia mais tarde, j que ela se torna um pouco complicada em C quando os ponteiros so introduzidos. Converso de tipo A linguagem C permite executar converses de tipo na sua execuo. Voc utiliza este recurso com freqncia ao usar ponteiros. A converso de tipo tambm ocorre durante a operao de atribuio para determinados tipos. Por exemplo, no cdigo acima, o valor inteiro foi automaticamente convertido para um flutuante. Voc faz a converso de tipo em C colocando o nome de tipo entre parnteses e colocando-o na frente do valor que deseja alterar. Assim, no cdigo acima, ao substituir a linha a=10/3; por a=(float)10/3; produz 3.33333 como resultado, pois 10 convertido em um valor de ponto flutuante antes da diviso. Typedef Voc declara tipos nomeados e definidos pelo usurio em C com a instruo typedef. O exemplo a seguir mostra um tipo que aparece freqentemente no cdigo C: #define TRUE 1 #define FALSE 0 typedef int boolean; void main() { boolean b; b=FALSE; bl bl bl }

21

Este cdigo permite declarar tipos booleanos em programas C. Se voc no gosta da palavra float para nmeros reais, voc pode dizer: typedef float real; e mais tarde dizer: real r1,r2,r3; Voc pode colocar declaraes typedef em qualquer lugar em um programa C contanto que venham antes de sua primeira utilizao na codificao. Estruturas Estruturas em C permitem agrupar variveis em um pacote. Eis um exemplo: struct rec { int a,b,c; float d,e,f; }; struct rec r; Como demonstrado, sempre que desejar declarar estrutura do tipo rec, voc precisa dizer struct rec. Esta linha muito fcil de esquecer, e o compilador gera muitos erros caso voc omita o struct. Voc pode compactar o cdigo desse modo: struct rec { int a,b,c; float d,e,f; } r; onde a declarao de tipo para rec e a varivel r so declaradas na mesma instruo. Ou voc pode criar uma instruo typedef para o nome de estrutura. Por exemplo, se voc no gosta de dizer struct rec r sempre que deseja declarar um registro, pode dizer: typedef struct rec rec_type; e ento declarar os registros de tipo rec_type dizendo: rec_type r; Voc acessa os campos da estrutura usando um ponto, por exemplo, r.a=5;. Matrizes Voc declara matrizes ao inserir um tamanho de matriz aps a instruo normal, como abaixo: int a[10]; /* matriz de inteiros */ char s[100]; /* matriz de caracteres (uma string em C) */ float f[20]; /* matriz de reais */ struct rec r[50]; /* matriz de registros */ Incrementao Caminho Longo Caminho Curto i=i+1; i++; i=i-1; i--; 22

i=i+3; i=i*j;

i += 3; i *= j; Tente isto Tente diferentes trechos da codificao para investigar a converso de tipo e precedncia. Tente int, char, float e assim por diante; Crie uma matriz de registros e digite um cdigo para classificar a matriz em um campo de nmero inteiro.

Erros que devem ser evitados na linguagem C Como descrito acima, o uso do operador / com 2 inteiros quase sempre produzir um resultado inesperado, portanto pense bem ao utiliz-lo. 13 Funes A maioria das linguagens permite criar funes de algum tipo. Funes permitem dividir um longo programa em sees nomeadas, de forma que as sees possam ser reutilizadas ao longo do programa. As funes aceitam parmetros e retornam um resultado. Funes de C podem aceitar um nmero ilimitado de parmetros. Em geral, a linguagem C no se preocupa em qual ordem voc coloca suas funes no programa, contanto que o nome da funo seja conhecido pelo compilador antes de ser invocado. J falamos um pouco sobre funes. A funo rand previamente descrita bastante simples. Ela no aceita parmetros e retorna um resultado inteiro: int rand() /* de K&R - produz um nmero aleatrio entre 0 e 32767.*/ { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; } A linha int rand() declara a funo rand para o resto do programa e especifica que aquele rand no aceitar nenhum parmetro e retornar um resultado inteiro. Esta funo no tem nenhuma varivel local, mas se elas fossem necessrias, iriam logo abaixo da abertura {. A linguagem C permite declarar variveis aps qualquer {: elas existem at o programa atingir o } correspondente e ento desaparecem. Desta forma, as variveis locais de uma funo desaparecem assim que o } correspondente atingido na funo. Enquanto existirem, as variveis locais residem na pilha do sistema. Observe que no h nenhum ; depois de () na primeira linha. Caso tenha colocado um, acidentalmente, voc receber uma enorme cascata de mensagens de erro do compilador, que no faro sentido algum. Observe tambm que, mesmo sem parmetros, voc precisa usar o (). Eles indicam ao compilador que voc est declarando uma funo em vez de simplesmente declarar um int. A instruo return importante para qualquer funo que retorna um resultado. Ela especifica o valor que a funo retornar e a encerra imediatamente. Isto significa que voc pode colocar diversas instrues de retorno na funo para proporcionar vrios pontos de sada. Se voc no colocar uma instruo de retorno em uma funo, a funo retorna

23

quando atinge } e retorna um valor aleatrio (muitos compiladores o advertiro se voc no retornar um valor especfico). Em C, uma funo pode retornar valores de qualquer tipo: int, float, char, struct, etc. H vrios modos corretos para executar a funo rand. Por exemplo: x=rand();. A varivel x recebe o valor retornado por rand nesta instruo. Observe que voc deve usar () na chamada da funo, mesmo que nenhum parmetro seja passado. Caso contrrio, x recebe o endereo de memria da funo rand, que geralmente no desejvel. Voc tambm pode invocar rand desta forma: if (rand() > 100) Ou deste modo: rand(); No ltimo caso, a funo chamada, mas o valor retornado por rand descartado. Talvez voc nunca faa isso com rand, mas muitas funes retornam algum tipo de cdigo de erro por meio da funo, e se voc no est muito preocupado com o cdigo de erro (por exemplo, por saber que um erro impossvel), voc poder descart-lo. Funes podem usar um retorno tipo void caso se deseje que no se retorne nada. Por exemplo: void print_header() { printf("Programa Nmero 1n"); printf("Programa Nmero 1n"); printf("Verso 1.0, lanada em 26/12/91\n"); } Esta funo no retorna valor algum. Voc pode invoc-la com a seguinte instruo: print_header(); Voc deve incluir () na chamada. Se no o fizer, a funo no invocada, embora possa ser compilada corretamente em outros sistemas. As funes em C podem aceitar qualquer tipo de parmetro. Por exemplo: int fact(int i) { int j,k; j=1; for (k=2; k<=i; k++) j=j*k; return j; } retorna o fatorial de i, que passado como um parmetro inteiro. Separe vrios parmetros com vrgulas: int add(int i, int j) { return i+j; } A linguagem C evoluiu ao longo dos anos. s vezes, voc ver funes como add escritas do modo antigo, como mostrado abaixo: int add(i,j) int i; 24

int j; { return i+j; } importante saber ler o cdigo escrito no estilo antigo. No h nenhuma diferena na forma em que o cdigo executado, apenas a notao diferente. Voc deve usar o novo estilo (conhecido como ANSI C) com o tipo declarado, como parte da lista de parmetros, a menos que tenha conhecimento prvio de que o cdigo ser enviado a algum com um compilador em estilo antigo (sem suporte a ANSI). 14 Funes: prottipos de funo considerada boa prtica utilizar prottipos de funo para todas as funes em seu programa. Um prottipo declara o nome de funo, seus parmetros e seu tipo de retorno para o resto do programa antes da declarao real da funo. Para entender a utilidade dos prottipos, digite o seguinte cdigo e execute-o: #include <stdio.h> void main() { printf("%d\n",add(3)); } int add(int i, int j) { return i+j; } Este cdigo compila em diversos compiladores sem emitir um aviso, apesar de add esperar dois parmetros e receber apenas um. Ele funciona porque muitos compiladores C no verificam se o parmetro corresponde ao tipo ou quantidade. Voc pode perder horas na depurao de um cdigo no qual voc est declarando parmetros a mais ou a menos por engano. O cdigo anterior compila corretamente, mas produz a resposta errada. Para resolver este problema, a linguagem C permite colocar prottipos de funo no incio (na verdade, em qualquer lugar) de um programa. Se fizer isso, a linguagem C verifica tipos e quantidades de todas as listas de parmetros. Tente compilar o seguinte: #include <stdio.h> int add (int,int); /* prottipo de funo para add */ void main() { printf("%d\n",add(3)); } int add(int i, int j) { return i+j; }

25

O prottipo faz com que o compilador sinalize um erro na instruo printf. Coloque um prottipo para cada funo no incio de seu programa. Os proptipos podem economizar bastante tempo de depurao e tambm resolver o problema que ocorre ao compilar com funes que so utilizadas antes de serem declaradas. Por exemplo, o seguinte cdigo no compilar: #include <stdio.h> void main() { printf("%d\n",add(3)); } float add(int i, int j) { return i+j; } Voc pode perguntar: Por que ele compilar quando add retorna um int mas no quando retorna um float? Porque os compiladores em C mais antigos padronizaram um valor de retorno para int. O uso de um prottipo resolver este problema. Os compiladores antigos (sem suporte a ANSI) permitem prottipos, porm a lista de parmetros do prottipo deve estar vazia. Os compiladores antigos no fazem a verificao de erros em listas de parmetros. Tente isto Retorne ao exemplo de classificao bubble sort anteriormente apresentado e crie uma funo; Retorne aos programas anteriores e crie uma funo para obter a entrada de dado do usurio, em vez de obter a entrada de dado da funo principal. 15 Bibliotecas Bibliotecas so muito importantes na linguagem C, pois a linguagem suporta apenas os recursos mais bsicos de que necessita. A linguagem C no contm sequer funes de I/O para ler a partir do teclado e digitar na tela. Qualquer coisa que v alm da linguagem bsica deve ser escrita por um programador. Geralmente, os trechos de cdigo resultantes so colocados em bibliotecas para torn-los facilmente reutilizveis. Vimos a biblioteca padro de I/O padro ou stdio: as bibliotecas padro existem para I/O padro, funes matemticas, manipulao da string de caracteres, manipulao de tempo e assim por diante. Voc pode utilizar as bibliotecas em seus prprios programas para dividi-los em mdulos. Isso os torna fceis de entender, testar e depurar, como tambm possibilita a reutilizao do cdigo por outros programas que voc criar. Voc pode criar suas prprias bibliotecas facilmente. Como exemplo, pegaremos um cdigo do artigo anterior desta srie e criaremos uma biblioteca a partir de duas de suas funes. Eis o cdigo com o qual iniciaremos: #include <stdio.h> #define MAX 10

26

int a[MAX]; int rand_seed=10; int rand() /* de K&R - produz um nmero aleatrio entre 0 e 32767.*/ { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; } void main() { int i,t,x,y; /* preenche a matriz*/ for (i=0; i < MAX; i++) { a[i]=rand(); printf("%d\n",a[i]); } /* ordenao por bolha da matriz */ for (x=0; x < MAX-1; x++) for (y=0; y < MAX-x-1; y++) if (a[y] > a[y+1]) { t=a[y]; a[y]=a[y+1]; a[y+1]=t; } /* imprime matriz classificada */ printf("--------------------n"); for (i=0; i < MAX; i++) printf("%d\n",a[i]); } Este cdigo preenche uma matriz com nmeros aleatrios, ordena-os em bubble sort e depois os exibe na lista ordenada. Pegue o cdigo bubble sort e use o que aprendeu no artigo anterior para criar uma funo a partir dele. Visto que a matriz a e a constante MAX so globalmente conhecidas, a funo que voc cria no precisa de parmetros, tampouco apresentar um resultado. Porm, voc deve usar variveis locais para x, y e t. Uma vez testada a funo para verificar se ela est funcionado, passe o nmero de elementos como um parmetro em vez de utilizar MAX:
#include <stdio.h> #define MAX 10 int a[MAX]; int rand_seed=10;

27

/* de K&R - retorna um nmero aleatrio entre 0 e 32767.*/ int rand() { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; } void bubble_sort(int m) { int x,y,t; for (x=0; x < m-1; x++) for (y=0; y < m-x-1; y++) if (a[y] > a[y+1]) { t=a[y]; a[y]=a[y+1]; a[y+1]=t; } } void main() } int i,t,x,y; /* preenche a matriz */ for (i=0; i < MAX; i++) { a[i]=rand(); printf("%d\n",a[i]); } bubble_sort(MAX); /* imprime matriz classificada */ printf("--------------------n"); for (i=0; i < MAX; i++) printf("%d\n",a[i]); }

Voc tambm pode generalizar ainda mais a funo bubble_sort passando a como um parmetro: bubble_sort(int m, int a[]) Esta linha diz: Aceitar a matriz do inteiro a de qualquer tamanho como parmetro. Nada precisa ser alterado no corpo da funo bubble_sort. Para chamar bubble_sort, altere a chamada para: bubble_sort(MAX, a); Observe que &a no foi usado na chamada de funo, embora a classificao v mudar a. O motivo para tal ficar mais evidente depois que voc entender os ponteiros. 16 Criando uma biblioteca Uma vez que as funes rand e bubble_sort nos programas anteriores so teis, voc provavelmente vai querer reutiliz-las em outros programas que criar. Voc pode coloc-las em uma biblioteca de utilitrios para facilitar sua reutilizao. 28

Toda biblioteca possui duas partes: um arquivo de cabealho e o arquivo de cdigo. O arquivo de cabealho, em geral indicado por um sufixo .h, contm informaes sobre a biblioteca que os programas que o utilizam precisam saber. Em geral, o arquivo de cabealho contm constantes e tipos, junto com prottipos de funes disponveis na biblioteca. Digite o seguinte arquivo de cabealho e salve-o com o nome util.h. /* util.h */ extern int rand(); extern void bubble_sort(int, int []); Estas duas linhas so prottipos de funo. A palavra extern em C representa funes que sero posteriormente vinculadas. Se estiver usando um compilador antigo, remova os parmetros da lista de parmetros do bubble_sort. Digite o seguinte cdigo em um arquivo denominado util.c. /* util.c */ #include "util.h" int rand_seed=10; /* de K&R - produz um nmero aleatrio entre 0 e 32767.*/ int rand() { rand_seed = rand_seed * 1103515245 +12345; return (unsigned int)(rand_seed / 65536) % 32768; } void bubble_sort(int m,int a[]) { int x,y,t; for (x=0; x < m-1; x++) for (y=0; y > m-x-1; y++) if (a[y] < a[y+1]) { t=a[y]; a[y]=a[y+1]; a[y]=a[y+1]; } } Este cdigo inclui a biblioteca de utilitrios. O benefcio principal em utilizar uma biblioteca que a codificao no programa principal fica muito menor. Compilando e executando com uma biblioteca Para compilar a biblioteca, digite o seguinte na linha de comando (assumindo que voc est usando UNIX) (substitua gcc por cc, se seu sistema usa cc): gcc -c -g util.c O -c faz com que o compilador produza um arquivo de objeto para a biblioteca. O arquivo objeto contm o cdigo de mquina da biblioteca. Ele no pode ser executado at ser vinculado a um arquivo de programa que contenha a funo principal. O cdigo de mquina reside em um arquivo separado chamado util.o.

29

Para compilar o programa principal, digite o seguinte: gcc -c -g main.c Esta linha cria um arquivo denominado main.o que contm o cdigo de mquina do programa principal. Para criar o executvel final que contm o cdigo de mquina de todo o programa, vincule os dois arquivos objetos digitando o seguinte: gcc -o main main.o util.o Isso vincula main.o e util.o para formar um executvel denominado main. Para execut-lo, digite main. Makefiles facilitam o trabalho com as bibliotecas. Voc descobrir mais sobre os makefiles na prxima pgina. 17 Makefiles Pode ser inconveniente ter que digitar todas as linhas do gcc novamente, especialmente se voc estiver realizando inmeras alteraes no cdigo e possui vrias bibliotecas. O utilitrio make resolve este problema. Voc pode usar o seguinte makefile para substituir a seqncia de compilao anterior: main: main.o util.o gcc -o main main.o util.o main.o: main.c util.h gcc -c -g main.c util.o: util.c util.h gcc -c -g util.c Digite isto em um arquivo chamado makefile e digite make para criar o executvel. Observe que voc deve preceder todas as linhas gcc com um espao de pargrafo (8 espaos no bastaro, necessrio uma tabulao; as demais linhas devem estar alinhadas esquerda). Este makefile contm dois tipos de linhas. As linhas que aparecem alinhadas esquerda so linhas de dependncia. As linhas precedidas por uma tabulao so linhas executveis, que podem conter qualquer comando UNIX vlido. Uma linha de dependncia indica que um arquivo dependente de outro conjunto de arquivos. Por exemplo, main.o: main.c util.h diz que o arquivo main.o depende dos arquivos main.c e util.h. Se qualquer um destes dois arquivos for alterado, as linhas executveis seguintes devem ser executada(s) para recriar main.o. Observe que o executvel final produzido pelo makefile inteiro main, na linha 1 no makefile. O resultado final do makefile deve sempre ir na linha 1, onde este makefile diz que o arquivo main depende de main.o e util.o. Em caso de alteraes, execute a linha gcc -o main main.o util.o para recriar main. possvel colocar mltiplas linhas para serem executadas abaixo da linha de dependncia (todas devem comear com uma tabulao). Um programa grande pode ter vrias bibliotecas e um programa principal. O makefile recompila automaticamente tudo o que precisa ser recompilado por conta de uma alterao. Se no estiver trabalhando em uma mquina UNIX, seu compilador provavelmente tem a funcionalidade equivalente dos makefiles. Leia a documentao de seu compilador para aprender como utiliz-lo.

30

Agora voc entende por que se tem includo stdio.h nos programas anteriores. Ele simplesmente uma biblioteca padro que algum criou h muito tempo e disponibilizou a outros programadores para facilitar suas vidas. 18 Arquivos de texto Arquivos de texto em C so diretos e fceis de entender. Todas as funes e tipos de arquivo de texto em C vm da biblioteca stdio. Quando voc precisa de I/O de texto em um programa em C e precisa apenas de uma fonte para informao de entrada e uma fonte para informao de sada, voc pode usar o stdin (entrada padro/teclado) e stdout (sada padro/monitor). Voc pode usar o redirecionamento de entrada e sada na linha de comando para mover diferentes fluxos de informaes pelo programa. H 6 comandos diferentes de I/O em <stdio.h> que voc pode usar com stdin e stdout: Printf imprime a sada formatada para stdout; Scanf l a entrada formatada do stdin; Puts imprime uma string de caracteres para stdout; Gets l uma seqncia de caracteres do stdin; Putc imprime um caractere para stdout; gets, getchar l um caractere do stdin. A vantagem do stdin e stdout que eles so fceis de usar. Da mesma forma, a capacidade para redirecionar a I/O muito poderosa. Por exemplo, talvez voc queira criar um programa que leia de stdin e conta o nmero de caracteres: #include <stdio.h> #include <string.h> void main() { char s[1000]; int count=0; while (gets(s)) count += strlen(s); printf("%d\n",count); } Digite este cdigo e execute-o. Ele aguardar pela entrada de stdin, portanto digite algumas linhas. Quando terminar, pressione CTRL-D para indicar o final do arquivo (eof, de end-of-file). A funo gets l uma linha at detectar o eof, depois retorna um 0, encerrando o loop while. Ao pressionar CTRL-D, voc v uma contagem do nmero de caracteres em stdout (a tela). Use man gets ou a documentao de seu compilador para saber mais sobre a funo gets. Agora, suponha que voc queira contar os caracteres em um arquivo. Se o programa for compilado em um executvel nomeado xxx, voc pode digitar o seguinte: xxx < nome do arquivo Em vez de aceitar a entrada do teclado, ser usado o contedo do arquivo denominado filename. Voc pode obter o mesmo resultado usando o caractere barra vertical: cat < filename | xxx

31

Voc tambm pode redirecionar o resultado para um arquivo: xxx < filename > out Este comando coloca a contagem de caracteres produzida pelo programa em um arquivo de texto denominado out. s vezes, voc precisa usar um arquivo de texto diretamente. Por exemplo, voc pode precisar abrir um arquivo especfico e ler ou escrever nele. Talvez voc queira gerenciar vrios fluxos de entrada ou sada, ou criar um programa como um editor de textos que possa salvar e recuperar arquivos de dados ou configuraes no comando. Nesse caso, use as funes de arquivo de texto em stdio: fopen abre um arquivo de texto; fclose fecha um arquivo de texto; feof detecta o marcador de final de arquivo em um arquivo; fprintf imprime a sada formatada em um arquivo; fscanf l a entrada formatada de um arquivo; fputs imprime uma string de caracteres para um arquivo; fgets l uma string de caracteres de um arquivo; fputc imprime um caractere para um arquivo; fgetc l um caractere de um arquivo. 19 Arquivos de texto: abrindo Use fopen para abrir um arquivo. Ele abre um arquivo para um modo especificado (os trs mais comuns so r, w e a, para leitura, gravao e anexao). Ele ento retorna um ponteiro de arquivo que voc usa para acessar o arquivo. Por exemplo, suponha que voc queira abrir um arquivo e escrever nele os nmeros 1 a 10. Voc pode usar a seguinte codificao: #include <stdio.h> #define MAX 10 int main() { FILE *f; int x; f=fopen("out","w";); if (!f) return 1; for(x=1; x<=MAX; x++) fprintf(f,"%d\n",x); fclose(f); return 0; } A instruo fopen abre um arquivo denominado out com o modo w. Este um modo de escrita destrutivo, o que significa que se out no existir ele ser criado, porm se ele j existir, ser destrudo e um novo arquivo ser criado em seu lugar. O comando fopen retorna um ponteiro para o arquivo, que armazenado na varivel f. Esta varivel usada para referncia ao arquivo. Se o arquivo no pode ser aberto por algum motivo, f ir conter NULL.

32

Valores de retorno da funo principal Este programa o primeiro programa desta srie que retorna um valor de erro do programa principal. Se o comando fopen falhar, f contm um valor NULL (zero). Vamos testar este erro com a instruo if. A instruo if verifica o valor de Verdadeiro/Falso da varivel f. Lembre-se de que em C, 0 Falso e qualquer outro valor verdadeiro. Se houvesse um erro ao abrir o arquivo, f conteria zero, que Falso. O ! o operador NOT. Ele inverte um valor booleano. Assim, a instruo if poderia ter sido escrita desta forma: if (f == 0) As duas formas so equivalentes. Porm, if (!f) mais comum. Se houver um erro de arquivo, retornamos um 1 da funo principal. Em UNIX, voc pode testar este valor na linha de comando. Veja a documentao de shell para mais detalhes. A instruo fprintf deve parecer bastante familiar: ela igual a printf, mas usa o ponteiro de arquivo como seu primeiro parmetro. A instruo fclose fecha o arquivo quando voc termina. 20 Arquivos de texto: lendo Para ler um arquivo, abra-o com o modo r. Em geral, no uma boa idia usar fscanf para leitura: a menos que o arquivo esteja perfeitamente formatado, fscanf no o manipular corretamente. Em vez disso, use fgets para ler cada linha e ento analisar gramaticalmente as partes necessrias. O seguinte cdigo demonstra o processo de leitura de um arquivo e o descarregamento de seu contedo na tela: #include <stdio.h> int main() {
FILE *f; char s[1000]; f=fopen("infile","r"); if (!f) return 1; while (fgets(s,1000,f)!=NULL) printf("%s",s); fclose(f); return 0; }

A instruo fgets retorna um valor NULL ao marcador de final de arquivo. Ele l uma linha (at 1 mil caracteres, neste caso) e depois a imprime para stdout. Observe que a instruo printf no inclui n na string de formatao, pois fgets adiciona n ao final de cada linha que l. Assim, voc pode saber se uma linha no est completa no evento que excede o comprimento mximo da linha especificado no segundo parmetro para fgets. 33

Erros que devem ser evitados na linguagem C No digite acidentalmente close em vez de fclose. A funo close existe, portanto o compilador a aceita. Ela at parecer funcionar se o programa apenas abrir ou fechar alguns arquivos. Entretanto, se o programa abrir e fechar um arquivo em um loop, eventualmente ficar sem manipuladores de arquivos disponveis e/ou espao em memria e travar, pois close no est fechando os arquivos corretamente. 21 Ponteiros Ponteiros so bastante usados em C. Assim, se deseja usar a linguagem C por completo, voc precisa ter uma boa compreenso sobre ponteiros. Eles devem se tornar fceis de usar para voc. A meta desta e das vrias prximas sees ajud-lo a construir um entendimento completo sobre os ponteiros e como a linguagem C os utiliza. Para a maioria das pessoas, isto demora um pouco, pois requer prtica familiarizar-se com os ponteiros, mas depois de domin-los, voc se torna um programador completo em linguagem C. A linguagem C utiliza ponteiros de trs modos diferentes: a linguagem C usa ponteiros para criar estruturas dinmicas de dados , que so estruturas de dados criadas a partir de blocos de memria localizados na pilha durante o tempo de execuo; a linguagem C usa ponteiros para manipular parmetros de variveis passados para as funes; os ponteiros em C oferecem um modo alternativo para acessar informaes armazenadas em matrizes. As tcnicas de ponteiro so especialmente valiosas quando se trabalha com strings de caracteres. H uma estreita relao entre matrizes e ponteiros em linguagem C. Em alguns casos, os programadores de C tambm usam ponteiros porque eles tornam o cdigo um pouco mais eficiente. O que voc descobrir que, depois de se sentir familiarizado com os ponteiros, ir a us-los o tempo todo. Iniciaremos esta discusso com uma introduo bsica sobre ponteiros e seus conceitos relacionados, e depois passaremos para as trs tcnicas descritas acima. Voc provavelmente vai querer ler este artigo duas vezes. Ao l-lo da primeira vez, voc aprender os conceitos. A segunda leitura permitir ligar os conceitos e criar um todo integrado em sua mente. Aps ler o material pela segunda vez, ele far muito mais sentido. 22 Ponteiros: por qu? Imagine que voc gostaria de criar um editor de texto: um programa que permite editar arquivos de texto ASCII normal, como o vi, do UNIX, ou o Bloco de notas, do Windows. Um editor de textos uma coisa bastante comum para algum criar, pois provavelmente o segmento de software mais usado pelo programador. O editor de textos a ligao ntima de um programador com o computador, pois nele que o programador digita todas as suas idias e as organiza. Obviamente, como qualquer coisa que voc utilize com

34

tanta freqncia e com a qual trabalhe constantemente, voc tambm vai desejar que o editor de textos funcione perfeitamente. Assim, muitos programadores criam seus prprios editores e os personalizam para atender seus estilos e preferncias de trabalho individuais. Ento, em um certo dia, voc se senta para comear a trabalhar em seu editor. Depois de pensar sobre as caractersticas que deseja, voc comea a pensar sobre a estrutura de dados de seu editor. Quer dizer, comea a pensar sobre como armazenar o documento que est editando na memria, de forma que possa manipul-lo em seu programa. O que voc precisa de um modo para armazenar as informaes inseridas de forma que possam ser manipuladas rpida e facilmente. Voc acredita que um modo de fazer isso organizar os dados com base na linha de caracteres. Visto o que j foi discutido, a nica coisa disponvel aqui a matriz. Voc pensa: Bem, uma linha tpica tem 80 caracteres de comprimento e um arquivo tpico no tem mais do que mil linhas. Assim, voc declara uma matriz bi-dimensional como esta: char doc[1000][80]; Esta instruo solicita uma matriz de mil linhas com 80 caracteres. Esta matriz tem um tamanho total de 80 mil caracteres. Enquanto pensa sobre o editor e sua estrutura de dados, voc pode se dar conta de trs coisas: alguns documentos so listas longas. Cada linha curta, mas h milhares de linhas; alguns arquivos de texto com finalidade especial possuem linhas muito longas. Por exemplo, certo arquivo de dados poderia ter linhas contendo 542 caracteres, com cada caractere representando um par de aminocidos do DNA; na maioria dos editores modernos, voc pode abrir mltiplos arquivos de uma s vez. Digamos que voc determinou um mximo de 10 arquivos abertos por vez, um comprimento mximo de linha de 1 mil caracteres e um tamanho de arquivo mximo de 50 mil linhas. Sua declarao agora se parecer com: char doc[50000][1000][10]; No parece uma coisa absurda, at voc pegar sua calculadora, multiplicar 50 mil por 1 mil e depois por 10 e constatar que a matriz ir conter 500 milhes de caracteres! Atualmente, a maioria dos computadores tem problemas com matrizes deste tamanho. Eles no tm a memria RAM, ou at mesmo o espao de memria virtual para suportar uma matriz desse tamanho. Se os usurios tentassem executar trs ou quatro cpias deste programa simultaneamente, mesmo num sistema multiusurio maior, isso poderia representar um esforo muito grande a suas instalaes. Mesmo que o computador aceitasse uma solicitao para uma matriz to grande, voc veria que isto seria um desperdcio extravagante de espao. Parece estranho declarar uma matriz de 500 milhes de caracteres se, na maioria dos casos, voc executar este editor para pesquisar arquivos de 100 linhas, que consomem no mximo 4 ou 5 mil bytes. O problema com uma matriz o fato de que voc precisa declar-la como tendo o tamanho mximo em cada dimenso, desde o comeo. Os tamanhos mximos freqentemente se multiplicam para formar nmeros muito grandes. Tambm, se precisar editar um arquivo que ocasionalmente possua 2 mil carcacteres em uma linha, voc ter problemas. No h como prever e manipular o comprimento mximo de uma linha de um arquivo de texto, pois, tecnicamente, este nmero infinito. Os ponteiros so projetados para resolver esse problema. Com eles, voc pode criar estruturas dinmicas de dados. Em vez de declarar o pior caso de consumo de memria com antecedncia em uma matriz, voc aloca memria da pilha enquanto o programa est 35

sendo executado. Dessa forma, voc pode usar a quantia exata de memria que o documento precisa, sem desperdcio. Alm disso, quando voc fecha um documento, pode devolver a memria pilha para que outros trechos do programa possam us-la. Com os ponteiros, a memria pode ser reciclada enquanto o programa est sendo executado. A propsito, se voc leu a discusso anterior e uma de suas grandes dvidas continua sendo o que , de fato, um byte?, ento o artigo Como funcionam os bits e os bytes ir ajud-lo a compreender esses conceitos e tambm coisas como mega, giga e ter. D uma olhada nele e depois volte aqui. 23 Fundamentos sobre ponteiros Para compreender os ponteiros, convm compar-los s variveis normais. Uma varivel normal um local na memria que pode conter um valor. Por exemplo, quando voc declara uma varivel i como um inteiro, 4 bytes de memria so separados para isso. Em seu programa, voc se refere quele local na memria pelo nome i. No nvel da mquina, aquele local tem um endereo de memria. Os 4 bytes naquele endereo so conhecidos pelo programador como i, e os 4 bytes podem conter valores inteiros. Um ponteiro diferente. Um ponteiro uma varivel que aponta para outra varivel. Isto significa que um ponteiro mantm o endereo de memria de outra varivel. Em outras palavras, o ponteiro no contm um valor no sentido tradicional, mas sim o endereo de outra varivel. Um ponteiro aponta para esta outra varivel mantendo uma cpia de seu endereo. Como um ponteiro contm um endereo, e no um valor, ter duas partes. O ponteiro contm um endereo e o endereo aponta para um valor. H o ponteiro e o valor para o qual ele aponta. Este fato pode ser um tanto confuso, at voc se familiarizar com ele. Superada a etapa da familiarizao, ele se torna extremamente eficaz. A codificao exemplificada a seguir mostra um tpico ponteiro: #include <stdio.h> int main() { int i,j; int *p; /* um ponteiro para um inteiro */ p = &i; *p=5; j=i; printf(("%d %d %d\n", i, j, *p); return 0; } A primeira declarao neste programa mostra duas variveis inteiras normais, nomeadas i e j . A linha int *p declara um ponteiro denominado p. Esta linha pede que o compilador declare uma varivel p que seja um ponteiro para um inteiro. O * indica que foi declarado um ponteiro em vez de uma varivel normal. Voc pode criar um ponteiro para qualquer coisa: um flutuante, uma estrutura, um caractere e assim por diante. Basta usar um * para indicar que deseja um ponteiro em vez de uma varivel normal. 36

A linha p = &i; trar algo totalmente novo para voc. Em linguagem C, & denominado operador de endereo. A expresso &i significa: O endereo de memria da varivel i. Assim, a expresso p = &i; significa: Atribuir para p o endereo de i. Uma vez executada esta instruo, p aponta para i. Antes de prosseguir, lembre-se que p contm um endereo aleatrio e desconhecido e que sua utilizao causar uma falha de segmentao ou erro de programa similar. Um bom mtodo para visualizar o que est acontecendo fazer um desenho. Depois que i, j e p so declarados, as coisas parecero assim:

Neste desenho, as trs variveis i, j e p foram declaradas, mas nenhuma delas foi inicializada. As duas variveis inteiras foram desenhadas como caixas contendo os pontos de interrogao (elas poderiam conter qualquer valor nesta altura da execuo do programa). O ponteiro desenhado como um crculo para distingui-lo de uma varivel normal que contm um valor e as setas aleatrias indicam que ele pode estar apontando para qualquer lugar neste momento. Depois da linha p = &I;, p inicializado e aponta para i, desta forma:

Assim que p aponta para i, o local de memria i tem 2 nomes. Ele ainda conhecido por i , mas agora tambm conhecido como *p. assim que a linguagem C se comunica com as duas partes de uma varivel de ponteiro: p o local que mantm o endereo, enquanto *p o local apontado por aquele endereo. Logo, *p=5 significa que o local apontado por p deve ser definido como 5, assim:

Visto que o local *p tambm i, i tambm assume o valor 5. Conseqentemente, j=i; define j como 5 e a instruo printf produz 5 5 5. A principal caracterstica de um ponteiro sua dupla natureza. O prprio ponteiro contm um endereo. O ponteiro tambm aponta para um valor de um tipo especfico: o valor no endereo do ponteiro. O prprio ponteiro, neste caso, p. O valor apontado *p.

37

24 Ponteiros: entendendo os endereos da memria A discusso anterior torna-se um pouco mais clara se voc compreender como funciona o endereamento de memria em um computador. Caso ainda no tenha lido, agora uma boa hora para ler Como funcionam os bits e os bytes para entender sobre bits, bytes e palavras. Todos os computadores tm memria, tambm conhecida por RAM (memria de acesso aleatrio). Por exemplo, seu computador pode ter 16, 32 ou 64 megabytes de memria RAM instalados. A memria RAM mantm os programas que atualmente esto em execuo no computador, junto aos dados que esto sendo manipulados (suas variveis e estruturas de dados). A memria pode ser vista como uma matriz de bytes. Nesta matriz, cada local de memria tem seu prprio endereo. O endereo do primeiro byte 0, seguido por 1, 2, 3, e assim sucessivamente. O endereo de memria atua como os ndices de uma matriz normal. O computador pode acessar qualquer endereo na memria, a qualquer momento (por isso o nome memria de acesso aleatrio). Ele tambm pode agrupar os bytes necessrios para formar matrizes, variveis e estruturas maiores. Por exemplo, uma varivel de ponto flutuante consome 4 bytes contnuos em memria. Voc pode fazer a seguinte declarao global em um programa: float f; Esta instruo diz: Declare um local denominado f que possa manter um valor de ponto flutuante. Quando o programa executado, o computador reserva espao para a varivel f em algum lugar na memria. Aquele local tem um endereo fixo no espao de memria, assim:

A varivel f consome 4 bytes de RAM na memria. Este local tem um endereo especfico, neste caso 248,440

Enquanto voc pensa na varivel f, o computador pensa em um endereo especfico na memria (por exemplo, 248,440). Assim, ao criar uma instruo como esta: f = 3.14;

38

O compilador poderia traduzir isso como: Carregar o valor 3.14 no local de memria 248,440. O computador est sempre pensando na memria em termos de endereo e valores para tais endereos. A propsito, existem vrios efeitos colaterais interessantes na forma com a qual seu computador gerencia a memria. Por exemplo, suponhamos que voc incluiu a seguinte codificao em um de seus programas: int i, s[4], t[4], u=0; for (i=0; i<=4; i++) { s[i] = i; t[i] =i; } Printf("s:tn"); for (i=0; i<=4; i++) printf("("%d:%dn", , s[i], t[i]); printf("u = %dn", u); O resultado que voc v no programa provavelmente ser algo assim: s:t 1:5 2:2 3:3 4:4 5:5 u=5 Por que t[0] e u esto incorretos? Se olhar cuidadosamente o cdigo, voc ver que os loops for esto escrevendo um elemento alm do final de cada matriz. Na memria, as matrizes so colocadas lado a lado, como mostrado abaixo:

39

Logo, ao tentar escrever para s[4], que no existe, o sistema escreve para t[0], uma vez que t[0] onde s[4] deveria estar. Ao escrever para t[4], na verdade voc est escrevendo em u. No que se refere ao computador, s[4] simplesmente um endereo e pode ser escrito. Como voc pode ver, mesmo que o computador execute o programa, ele no est correto ou vlido. O programa corrompe a matriz t no processo de execuo. A execuo da seguinte instruo pode resultar em conseqncias mais graves: s[1000000] = 5; O local s[1000000] provavelmente est fora do espao de memria de seu programa. Em outras palavras, voc est escrevendo na memria que seu programa no possui. Em um sistema com espaos protegidos de memria (UNIX, Windows 98/NT), este tipo de instruo far com que o sistema encerre a execuo do programa. Em outros sistemas (Windows 3.1, Mac), todavia, o sistema no sabe o que voc est fazendo. Voc acaba danificando o cdigo ou as variveis em outro aplicativo. O efeito da violao pode variar de nenhum a uma tremenda pane do sistema. Na memria, i, s, t e u so todos colocados prximos uns aos outros em endereos especficos. Portanto, se voc escrever alm dos limites de uma varivel, o computador far o que foi solicitado, porm acabar corrompendo outro local de memria. As linguagens C e C++ no executam qualquer tipo de verificao de extenso ao acessar um elemento na matriz. essencial que voc, como programador, preste ateno s extenses de matriz e respeite seus limites. A leitura ou escrita no-intencional fora dos limites da matriz quase sempre leva ao comportamento errneo do programa. Como outro exemplo, tente o seguinte: #include int main() { int i,j; int *p; /* um ponteiro para um inteiro */ printf("%d %dn", p, &i); p = &i; printf("%d %dn", p, &i); return 0; } Este cdigo diz ao compilador para imprimir o endereo contido em p, junto com o endereo de i. A varivel p comea com um valor estranho ou com 0. O endereo de i geralmente um valor alto. Por exemplo, quando executei este cdigo, recebi o seguinte resultado:
0 2147478276 2147478276 2147478276

que significa que o endereo de i 2147478276. Aps executar a instruo p = &i;, p contm o endereo de i. Tente tambm: #include void main() {

40

int *p; /* um ponteiro para um inteiro */ printf("%dn",*p); } Este cdigo diz ao compilador para imprimir o valor para o qual p aponta. Porm, p ainda no foi inicializado. Ele contm o endereo 0 ou algum endereo aleatrio. Na maioria dos casos, resulta em uma falha de segmentao (ou algum outro erro no tempo de execuo), o que significa que voc usou um ponteiro que aponta para uma rea invlida da memria. Quase sempre, um ponteiro no inicializado ou um endereo de ponteiro invlido a causa das falhas de segmentao. Dito isso, agora podemos ver os ponteiros por uma tica totalmente diferente. Veja este programa, por exemplo: #include int main() { int i; int *p; /* um ponteiro para um inteiro*/ p = &i; *p=5; printf("%d %dn", i, *p); return 0; } Eis o que est acontecendo:

41

A varivel i consome 4 bytes de memria. O ponteiro p tambm consome 4 bytes (na maioria das mquinas atuais, um ponteiro consome 4 bytes de memria; os endereos de memria tem 32 bits de extenso na maioria das CPUs, embora exista uma tendncia de crescimento para o endereamento de 64 bits). O local de i tem um endereo especfico, neste caso 248,440. O ponteiro p contm aquele endereo, uma vez que voc disse que p = &i;. As variveis *p e i so, portanto, equivalentes. printf("%d", p); o resultado o endereo corrente da varivel i. 25 Ponteiros: apontando para o mesmo endereo Eis um aspecto interessante da linguagem C: qualquer nmero de ponteiros pode apontar para o mesmo endereo . Por exemplo, voc pode declarar p, q, e r como ponteiros de inteiros e defini-los para apontar para i, assim: int i; int *p, *q, *r; p = &i; q = &i; r = p; Observe que neste cdigo, r aponta para o mesmo que p, que i. Voc pode atribuir ponteiros uns aos outros, e o endereo copiado da direita para a esquerda durante a atribuio. Ao executar o cdigo acima, voc ver algo assim:

A varivel i agora tem 4 nomes: i, *p, *q e *r. No h limite de nmero de ponteiros que podem conter (e apontar para) o mesmo endereo. 26 Ponteiros: bugs comuns Bug n.1 - Ponteiro no inicializado Um dos meios mais fceis de criar um bug de ponteiro tentar fazer referncia ao valor de um ponteiro, mesmo que ele no seja inicializado e ainda no aponte para um endereo vlido. Por exemplo: int *p; 42

*p = 12; O ponteiro p no inicializado e aponta para um local aleatrio na memria ao ser declarado. Ele poderia apontar para a pilha do sistema, variveis globais, espao de cdigo do programa ou o sistema operacional. Quando voc diz *p=12;, o programa simplesmente tentar escrever um 12 em qualquer local aleatrio para onde p apontar. O programa pode explodir imediatamente ou daqui a meia hora, ou ainda pode corromper sutilmente os dados em qualquer outra parte do seu programa sem que voc nunca perceba. Isso pode dificultar bastante o rastreamento de um erro. Certifique-se de inicializar todos os ponteiros para um endereo vlido antes de referenci-los. Bug n. 2 - Referncias de ponteiro invlidas Uma referncia invlida ocorre quando o valor de um ponteiro referido, mesmo que o ponteiro no aponte para um bloco vlido. Uma forma de criar este erro dizer p=q;, quando q no inicializado. O ponteiro p se tornar no inicializado e qualquer referncia a*p ser uma referncia de ponteiro invlida. O nico modo para evitar este bug desenhar imagens de cada etapa do programa e se certificar de que todos os ponteiros apontam para algum lugar. Referncias de ponteiro invlidas fazem com que o programa trave inexplicavelmente pelos mesmos motivos citados no bug n 1. Bug n 3 - Referncias de ponteiro Uma referncia nula ocorre sempre que um ponteiro indicando zero utilizado em uma instruo que tenta fazer referncia a um bloco. Por exemplo, se p um ponteiro de um inteiro, o seguinte cdigo est invlido: p = 0; *p = 12; Como no h um bloco apontado por p, tentar ler ou escrever a partir de qualquer coisa ou para aquele bloco uma referncia de ponteiro nula e invlida. Existem motivos bons e vlidos para apontar um ponteiro para zero, como veremos nos artigos posteriores. Entretanto, no fazer referncia a um ponteiro desta maneira invlido. Todos estes bugs so fatais para um programa. Voc deve prestar ateno sua codificao para que estes bugs no ocorram. A melhor forma para tal desenhar imagens de cada etapa de execuo do cdigo. 27 Usando ponteiros para parmetros de funo A maioria dos programadores em C usa ponteiros para implementar algo chamado de parmetros variveis em funes. Na verdade, voc tem usado parmetros variveis na funo scanf, por isso voc precisou usar & (o operador de endereo) em variveis com scanf. Agora que voc j entende os ponteiros, pode ver o que realmente est acontecendo. Para entender como os parmetros variveis funcionam, vamos ver como nos samos ao implementar uma funo swap em C. Para implementar uma funo swap, talvez voc queira passar por duas variveis fazendo-as trocar seus valores. Eis uma tentativa de implementao. Digite e execute a seguinte codificao e veja o que acontece: #include <stdio.h>

43

void swap(int i, int j) { int t; t=i; i=j; j=t; } void main() { int a,b; a=5; b=10; printf("%d %d\n", a, b); swap(a,b); printf("%d %d\n", a, b); } Ao executar este programa, voc ver que nenhuma troca ocorrer. Os valores de a e b so passados para troca, a funo swap os troca, porm quando a funo retorna, nada acontece. Para que esta funo opere corretamente, voc pode usar ponteiros: #include <stdio.h> void swap(int *i, int *j) { int t; t = *i; *i = *j; *j = t; } void main() { int a,b; a=5; b=10; printf("%d %d\n",a,b); swap(&a,&b); printf("%d %d\n",a,b); } Para ter uma idia do que esta codificao faz, imprima-a e desenhe os inteiros a e b e digite 5 e 10 neles. Agora desenhe os dois ponteiros i e j, juntamente com o inteiro t. Quando swap executado, ele passa os endereos de a e b. Assim, i aponta para a (desenhe uma seta de i para a) e j aponta para b (desenhe outra seta de b para j). Depois que os ponteiros so inicializados pela funo call, *i outro nome para a, e *j outro nome para b. Agora execute o cdigo em swap. Quando o cdigo usa *i e *j, na verdade quer dizer a e b. Quando a funo se completa, a e b foram trocados. 44

Suponha que voc se esquea do & quando a funo swap executada, e que a linha swap acidentalmente se parea assim: swap(a,b);. Isso causa uma falha de segmentao. Quando voc omite o &, o valor de a passado em vez de seu endereo. Assim, i aponta para local invlido na memria e o sistema trava quando *i usado. por isso tambm que scanf trava se voc se esquecer do & em variveis passadas a ele. A funo scanf est usando ponteiro para colocar o valor que ele l de volta varivel que voc passou. Sem o &, scanf recebe um endereo invlido e trava. Os parmetros variveis so um dos usos mais comuns de ponteiros em C. Agora voc entende o que est acontecendo. 28 Estruturas de dados dinmicas As estruturas de dados so aquelas que crescem e encolhem conforme voc precisa alocar e desalocar memria de um lugar chamado pilha. Elas so extremamente importantes em C, pois permitem ao programador controlar exatamente o consumo de memria. Elas alocam blocos de memria a partir da pilha conforme as necessidades e vinculam estes blocos em um tipo de estrutura de dados que usa ponteiros. Quando a estrutura de dados j no precisar de um bloco de memria, ela retorna o bloco pilha para reutilizao. Esta reciclagem faz uso eficiente da memria. Para entender completamente estruturas de dados dinmicas, precisamos comear com a pilha. 29 Estruturas de dados dinmicas: a pilha Um computador pessoal ou estao de trabalho tem normalmente, algo entre 16 e 64 megabytes de RAM instalados. Usando uma tcnica chamada memria virtual, o sistema pode fazer trocas entre a memria e o disco rgido, criando para a CPU a iluso de que ela possui mais memria, por exemplo, 200 a 500 megabytes. Enquanto esta iluso se completa para a CPU, ela pode tornar as coisas extremamente lentas para o usurio. Apesar desta desvantagem, a memria virtual uma tcnica extremamente til para aumentar a quantidade de RAM em uma mquina de um modo bem barato. Vamos supor, para fins de argumentao, que um computador tradicional possui espao de memria total de, por exemplo, 50 megabytes (independentemente de a memria ser implementada no formato RAM fsico ou virtual).

45

O sistema operacional em uma mquina est encarregado dos 50 megabytes de espao de memria. O sistema operacional usa o espao de diversas maneiras, como exemplificamos aqui:

O sistema operacional e diversos aplicativos, juntamente com suas variveis globais e espaos de pilha, consomem partes da memria. Quando um programa completa a execuo, libera sua memria para ser reutilizada por outros programas. Observe que parte do espao de memria permanece inutilizada em qualquer momento

Isto, obviamente, uma idealizao, porm os princpios bsicos esto corretos. Como voc pode ver, a memria contm o cdigo executvel para os diferentes aplicativos em execuo na mquina, junto com o cdigo executvel do prprio sistema operacional. Cada aplicativo tem determinadas variveis globais associadas a ele. Estas variveis tambm consomem memria. Finalmente, cada aplicativo usa uma rea da memria chamada pilha, que mantm todas as variveis locais e parmetros usados por qualquer funo. A pilha tambm mantm a ordem de execuo das funes, para que os retornos das funes ocorram corretamente. Sempre que uma funo executada, suas variveis locais e parmetros so empurrados sobre a pilha. Quando a funo retorna, estes locais e

46

parmetros so lidos e removidos. Por isso, o tamanho da pilha de um programa flutua constantemente conforme a execuo do programa, mas tem um tamanho mximo. Ao terminar a execuo de um programa, o sistema operacional descarrega o programa, seus globais e seu espao de pilha da memria. Um novo programa pode usar aquele espao posteriormente. Desta forma, a memria em um sistema de computador constantemente reciclada e reutilizada por programas, medida que so executados e concludos. Em geral, cerca de 50% do espao total de memria do computador pode estar ocioso em um dado momento. O sistema operacional possui e administra a memria no utilizada, coletivamente denominada de pilha. A pilha extremamente importante, pois est disponvel para aplicativos durante a execuo usando as funes em C malloc (alocao de memria) e free. A pilha permite que programas aloquem a memria exatamente quando precisarem dela, durante a execuo de um programa, em vez de pralocar a memria com uma declarao de matriz de tamanho especfico. 30 Estruturas de dados dinmicas: Malloc e Free Vamos supor que voc queira alocar certa quantidade de memria durante a execuo de seu aplicativo. Voc pode chamar a funo malloc a qualquer momento e ela solicitar um bloco de memria da pilha. O sistema operacional reservar um bloco de memria para seu programa e voc poder us-lo da maneira que quiser. Quando voc termina de usar o bloco, voc o retorna ao sistema operacional para reciclagem, invocando a funo free. Depois, os outros aplicativos podem reserv-lo para seu prprio uso. Por exemplo, o cdigo a seguir demonstra o uso mais simples possvel da pilha: int main() { int *p; p = (int *)malloc(sizeof(int)); if (p == 0) { printf("ERRO: Sem memria\n"); return 1; } *p = 5; printf("&d\n", *p); free(p); return 0; } A primeira linha neste programa invoca a funo malloc. Esta funo faz trs coisas: 1. primeiro, a instruo malloc analisa a quantidade de memria disponvel na pilha e pergunta: H memria suficiente disponvel para alocar um bloco de memria do tamanho solicitado?. A quantidade de memria necessria para o bloco conhecida a partir do parmetro passado em malloc - neste caso, sizeof(int) 4 bytes. Se no houver memria suficiente disponvel, a funo malloc retorna o endereo zero para indicar o erro (outro nome para zero NULL e voc ver que ele usado por todo o cdigo C). Caso contrrio, a funo malloc prossegue; 47

2. se houver memria disponvel na pilha, o sistema aloca ou reserva um bloco da pilha do tamanho especificado. O sistema reserva o bloco de memria de forma que ele no seja usado acidentalmente por mais de uma instruo malloc; 3. o sistema ento coloca na varivel do ponteiro (neste caso, p) o endereo do bloco reservado. A prpria varivel do ponteiro contm um endereo. O bloco alocado capaz de manter um valor do tipo especificado e o ponteiro aponta para ele. O seguinte diagrama mostra o estado de memria depois de executar malloc:

O bloco direita o bloco de memria malloc alocada

O programa ento verifica o ponteiro para garantir que a solicitao de alocao ocorreu com a linha if (p == 0) (que tambm poderia ter sido escrita como if (p == NULL) ou mesmo if (!p) Em caso de falha da alocao (se p for zero) o programa encerra. Em caso de xito na alocao, o programa inicializa o bloco com o valor 5, imprime o valor e executa a funo free para retornar a memria pilha antes do programa encerrar. No existe diferena entre este cdigo e o cdigo anterior que determine p igual ao endereo de um inteiro existente i. A nica distino que, no caso da varivel i, a memria existiu como parte do espao de memria pr-alocado do programa e tem dois nomes: i e *p. No caso da memria alocada da pilha, o bloco tem o nico nome de *p e alocado durante a execuo do programa. Duas dvidas comuns: mesmo importante verificar se o ponteiro zero aps cada alocao? Sim. Como a pilha varia de tamanho constantemente, dependendo dos programas em execuo e quantidade de memria que alocaram, etc., no h garantias de que uma invocao de malloc ser bem sucedida. Voc deve verificar o ponteiro depois de qualquer chamada a malloc para conferir se o ponteiro vlido; o que acontece se eu esquecer de apagar um bloco de memria antes que o programa encerre? Quando um programa encerra, o sistema operacional faz a limpeza depois do encerramento, liberando o espao de cdigo executvel, pilha, espao de memria global e qualquer alocao de pilha para reciclagem. Assim, no h conseqncias de longo prazo em deixar as alocaes pendentes no trmino do programa. Todavia, considerada uma forma inadequada e os vazamentos de memria durante a execuo de um programa so prejudiciais, como discutido abaixo. Os dois programas a seguir mostram dois usos vlidos e distintos de ponteiros. Tente distingi-los usando um ponteiro e um valor de ponteiro: void main() { int *p, *q; p = (int *)malloc(sizeof(int)); q = p; *p = 10; priprintf("%d\n", *q); *q = 20; pr printf("%d\n", *q); 48

} O resultado final deste cdigo seria 10 n linha 4 e 20 na linha 6. Eis um diagrama:

O seguinte cdigo um pouco diferente: void main() { int *p, *q; p = (int *)malloc(sizeof(int)); q = (int *)malloc(sizeof(int)); *p = 10; *q = 20; *p = *q; prprintf("%d\n", *p); } O resultado final deste cdigo seria 20 na linha 6. Eis um diagrama:

49

Observe que o compilador permitir *p = *q pois *p e *q so ambos inteiros. Esta instruo diz: Mova o valor inteiro apontado por q em um valor inteiro apontado por p. A instruo move os valores. O compilador tambm permitir p = q, pois p e q so ambos ponteiros e apontam para o mesmo tipo (se s for um ponteiro para um caractere, p = s no permitido pois eles apontam para tipos diferentes). A instruo p = q diz: Aponte p para o mesmo bloco para o qual q aponta. Em outras palavras, o endereo apontado por q transferido para p, assim ambos apontam para o mesmo bloco. Esta instruo transfere os endereos. De todos estes exemplos, voc pode ver que h quatro modos diferentes para inicializar um ponteiro. Quando um ponteiro declarado, como em int *p, ele inicia no programa em um estado no inicializado. Como ele pode apontar para qualquer lugar, remover sua referncia um erro. A inicializao de uma varivel de ponteiro envolve apont-la para um local conhecido na memria: 1. um modo, como j visto, usar a instruo malloc. Esta instruo aloca um bloco de memria da pilha e aponta o ponteiro para o bloco. Isso inicializa o ponteiro, pois ele agora aponta para um local conhecido. O ponteiro inicializado pois foi preenchido com um endereo vlido: o endereo do novo bloco; 2. o segundo modo, como visto recentemente, usar uma instruo como p = q para que p aponte para o mesmo lugar que q. Se q estiver apontando para um bloco vlido, ento p inicializado. O ponteiro p carregado com o endereo vlido contido em q. Entretanto, se q no for inicializado ou for invlido, p obter o mesmo endereo intil; 3. o terceiro modo apontar o ponteiro para um endereo conhecido, como o endereo de uma varivel global. Por exemplo, se i um inteiro e p um ponteiro de um inteiro, ento a instruo p=&i inicializa p apontando-o para i; 4. a quarta forma de inicializao de um ponteiro usar um valor zero. Zero um valor especial usado com ponteiros, como mostrado aqui: p = 0; ou p = NULL; O que isto faz fisicamente colocar um zero em p. O endereo do ponteiro p zero. Isso geralmente diagramado como:

Qualquer ponteiro pode ser definido para apontar para zero. Quando p aponta para zero, ele no aponta para um bloco. O ponteiro simplesmente contm o endereo zero e este valor til como uma tag. Voc pode us-lo em declaraes como: if (p == 0) { ... } ou while (p != 0) { 50

... } O sistema tambm reconhece o valor zero e gerar mensagens de erro se voc remover a referncia de um ponteiro zero. Por exemplo, no seguinte cdigo: p = 0; *p = 5; O programa geralmente travar. O ponteiro p no aponta para um bloco, aponta para zero, assim um valor no pode ser atribudo a *p. O ponteiro zero ser usado como sinalizador quando chegarmos s listas vinculadas. O comando malloc usado para alocar um bloco de memria. Tambm possvel desalocar um bloco de memria quando ele no mais necessrio. Quando um bloco desalocado, ele pode ser reutilizado por um comando malloc subseqente que permite ao sistema reciclar a memria. O comando usado para desalocar a memria chamado free e aceita um ponteiro como seu parmetro. O comando free faz duas coisas: 1. o bloco de memria apontado pelo ponteiro no est reservado e devolvido memria livre na pilha. Ele pode ser reutilizado posteriormente por novas declaraes; 2. o ponteiro permanece em estado no inicializado e deve ser reinicializado antes que possa ser novamente utilizado. A instruo free simplesmente retorna um ponteiro para seu estado original noinicializado e devolve o bloco para a pilha. O exemplo a seguir mostra como usar a pilha: ele aloca um bloco de inteiros, preenche, escreve e o descarta: #include <stdio.h> int main() { int *p; p = (int *)malloc(sizeof(int)); *p=10; printf("%d\n",*p); free(p); return 0; } Este cdigo s til para demonstrar o processo de alocao, desalocao e como usar um bloco em C. A linha malloc aloca um bloco de memria do tamanho especificado neste caso, sizeof(int) bytes (4 bytes). O comando sizeof em C retorna o tamanho, em bytes, de qualquer tipo. A codificao poderia ter dito malloc(4), uma vez que sizeof(int) igual a 4 bytes na maioria das mquinas. Todavia, o uso de sizeof torna a codificao mais porttil e legvel. A funo malloc retorna um ponteiro para o bloco alocado. Este ponteiro genrico. O uso do ponteiro sem converso de tipo geralmente produz um tipo de aviso do compilador. O (int *) converte o ponteiro genrico retornado por malloc em um ponteiro para um nmero inteiro, que esperado por p. A instruo free em C retorna um bloco para a pilha para reutilizao. O segundo exemplo ilustra as mesmas funes que o exemplo anterior, mas usa uma estrutura em vez de um inteiro. Em C, o cdigo se parece com: #include <stdio.h> 51

struct rec { int i; float f; char c; }; int main() { struct rec *p; p=(struct rec *) malloc (sizeof(struct rec)); (*p).i=10; (*p).f=3.14; (*p).c='a'; printf("%d %f %c\n",(*p).i,(*p).f,(*p).c); free(p); return 0; } Observe a seguinte linha: (*p).i=10; Muitos se perguntam por que no funciona: *p.i=10; A resposta est relacionada com a precedncia de operadores em C. O resultado do clculo 5+3*4 17, e no 32, pois o operador * tem maior precedncia do que + na maioria das linguagens de computao. Em C, o operador . tem precedncia maior que *, portanto os parnteses foram a devida precedncia. A maioria das pessoas se cansa de digitar (*p).i o tempo todo, de forma que o C oferece uma anotao de taquigrafia. As duas instrues a seguir so equivalentes, porm a segunda mais fcil de digitar: (*p).i=10; p->i=10; Voc ver a segunda instruo mais freqentemente do que a primeira ao ler a codificao de outras pessoas. 31 Ponteiros avanados Em geral, voc usar os ponteiros de formas mais complexas do que aquelas mostradas em alguns dos exemplos anteriores. Por exemplo, muito mais fcil criar um inteiro normal e trabalhar com ele do que criar e usar um ponteiro para um inteiro. Nesta seo sero explorados alguns dos modos mais comuns e avanados de trabalhar com ponteiros. Tipos de ponteiros possvel, aceitvel e benfico criar tipos de ponteiros em C, como mostrado abaixo: typedef int *IntPointer; ...

52

IntPointer p; Isso o mesmo que dizer: int *p; Esta tcnica ser usada em muitos exemplos nas pginas a seguir. A tcnica freqentemente torna uma declarao de dados mais fcil de ler e compreender, facilitando tambm a incluso de ponteiros em estruturas ou a passagem de parmetros de ponteiros em funes. 32 Ponteiros para estruturas possvel criar um ponteiro para virtualmente qualquer tipo em C, incluindo tipos definidos pelo usurio. extremamente comum criar ponteiros para estruturas. Um exemplo mostrado abaixo: typedef struct { char nome[21]; char cidade[21]; char estado[3]; } Rec; typedef Rec *RecPointer; RecPointer r; r = (RecPointer)malloc(sizeof(Rec));

O pointeiro r um ponteiro de estrutura. Observe que, como r um ponteiro, ele retira 4 bytes de memria como qualquer outro ponteiro. Porm, a instruo malloc aloca 45 bytes de memria da pilha. *r uma estrutura como qualquer estrutura de tipo Rec. A codificao seguinte mostra os usos freqentes de varivel de ponteiro: strcpy((*r).nome, "Leigh"); strcpy((*r).cidade, "Raleigh"); strcpy((*r).estado, "NC"); printf("%s\n", (*r).cidade); free(r); Voc lida com *r como lida com uma varivel normal de estrutura, porm precisa ter cuidado com a precedncia de operadores em C. Se deixasse de usar parnteses ao redor de *r, o cdigo no compilaria, pois o operador . tem maior precedncia sobre o operador *. Por se tornar tedioso digitar tantos parnteses ao trabalhar com ponteiros em estrutura, o C inclui uma anotao de taquigrafia que faz exatamente a mesma coisa: strcpy(r->nome, "Leigh"); A notao r-> equivalente ao (*r)., mas tem 2 caracteres a menos.

53

Ponteiros de matrizes Tambm possvel criar ponteiros para matrizes: int *p; int i; p = (int *)malloc(sizeof(int[10])); for (i=0; i<10; i++) p[i] = 0; free(p); ou int *p; int i; p = (int *)malloc(sizeof(int[10])); for (i=0; i<10; i++) *(p+i) = 0; free(p);

Observe que ao criar um ponteiro para uma matriz de inteiros, voc s precisa criar um ponteiro normal para int. A chamada para malloc aloca uma matriz de qualquer tamanho desejvel e o ponteiro aponta para o primeiro elemento daquela matriz. Voc pode indexar pela matriz apontada por p usando a indexao de matriz normal ou pode fazer isso usando a aritmtica de ponteiros. A linguagem C v ambas as formas como equivalentes. Esta tcnica particular extremamente til ao se trabalhar com strings. Ela permite alocar armazenamento suficiente para manter exatamente um string de determinado tamanho. Matrizes de ponteiros s vezes, pode-se economizar bastante espao ou resolver problemas de memria declarando uma matriz de ponteiros. No cdigo de exemplo abaixo, uma matriz de 10 ponteiros de estruturas declarada, em vez de declarar uma matriz de estruturas. Em vez 54

disso, se uma matriz de estruturas tivesse sido criada, 243 * 10 = 2.430 bytes seriam necessrios para a matriz. Usar a matriz de ponteiros permite que a matriz ocupe o menor espao possvel at que os registros reais sejam alocados com as instrues malloc. O cdigo abaixo simplesmente aloca um registro, coloca um valor e descarta o registro para demonstrar o processo: typedef struct { char s1[81]; char s2[81]; char s3[81]; } Rec; Rec *a[10]; a[0] = (Rec *)malloc(sizeof(Rec)); strcpy(a[0]->s1, "ol"); free(a[0]);

Estruturas contendo ponteiros As estruturas podem conter ponteiros, como mostrado abaixo: typedef struct { char nome[21]; char cidade[21]; char telefone[21]; char *comentario; } Addr; Addr s; char comm[100];

55

gets(s.nome, 20); gets(s.cidade, 20); gets(s.telefone, 20); gets(com., 100); s.comentrio = (char *)malloc(sizeof(char[strlen(comm)+1])); strcpy(s.comentario, com.);

Esta tcnica til quando apenas alguns registros realmente contm um comentrio no campo de comentrio. Se no houver nenhum comentrio para o registro, ento o campo consiste em apenas um ponteiro (4 bytes). Depois, estes registros que tm um comentrio alocam exatamente o espao necessrio para manter a string de caracteres do comentrio, com base na extenso do string digitada pelo usurio. 33 Ponteiros para ponteiros possvel e frequentemente til criar ponteiros de ponteiros. Esta tcnica s vezes chamada de handle e til em certas situaes nas quais o sistema operacional precisa da capacidade de mover blocos de memria na pilha a seu critrio. O exemplo a seguir demonstra um ponteiro de ponteiro: int **p; int *q; p = (int **)malloc(sizeof(int *)); *p = (int *)malloc(sizeof(int)); **p = 12; q = *p; printf("%d\n", *q); free(q); free(p);

56

O Windows e o Mac OS usam esta estrutura para permitir a consolidao de memria na pilha. O programa administra o ponteiro p, enquanto o sistema operacional administra o ponteiro *p. Uma vez que o sistema operacional gerencia *p, o bloco apontado por *p (**p) pode ser movido e *p pode ser alterado para refletir a transferncia sem afetar o programa que utiliza p. Os ponteiros de ponteiros tambm so freqentemente usados em C para identificar parmetros de ponteiros em funes. Ponteiros para estruturas contendo ponteiros Tambm possvel criar ponteiros para estruturas que contenham ponteiros. O exemplo seguinte usa o registro Addr da seo anterior: typedef struct { char nome[21]; char cidade[21]; char telefone[21]; char *comentrio; } Addr; Addr *s; char comm[100]; s = (Addr *)malloc(sizeof(Addr)); gets(s->nome, 20); gets(s->cidade, 20); gets(s->telefone, 20); gets(comm, 100); s->comentrio = (char *)malloc(sizeof(char[strlen(comm)+1])); strcpy(s->comentrio, comm); O ponteiro s aponta para uma estrutura que contm um ponteiro que aponta para uma string de caracteres:

Neste exemplo, muito fcil criar blocos perdidos se voc no for cuidadoso. Por exemplo, aqui est uma verso diferente do exemplo AP. s = (Addr *)malloc(sizeof(Addr)); gets(comm, 100); s->comentrio = (char *)malloc(sizeof(char[strlen(comm)+1])); strcpy(s->comentrio, comm); free(s);

57

Este cdigo cria um bloco perdido, pois a estrutura que contm o ponteiro que aponta para a string descartada antes que o bloco de string seja descartado, como mostrado abaixo:

Ligao Finalmente, possvel criar estruturas capazes de apontar para estruturas idnticas. Esta capacidade pode ser usada para ligar uma string de registros idnticos em uma estrutura denominada lista encadeada. typedef struct { char nome[21]; char cidade[21]; char estado[21]; Addr *next; } Addr; Addr *first; O compilador permitir que voc faa isso e, com um pouco de experincia, ele pode ser usado para criar estruturas como esta demonstrada abaixo:

34 Um exemplo de pilha encadeada Um bom exemplo de estruturas dinmicas de dados uma biblioteca de pilha simples que utiliza uma lista dinmica e inclui funes init, clear, push e pop. O arquivo de cabealho da biblioteca tem o seguinte aspecto: /* Biblioteca Esttica. Esta biblioteca oferece as operaes bsicas de pilha para uma pilha de inteiros (facilmente altervel) */ typedef int stack_data; extern void stack_init(); 58

/* Inicializa esta biblioteca. Chama primeiro antes de chamar qualquer outra coisa. */ extern void stack_clear(); /* Apaga todas as entradas da pilha. */ extern int stack_empty(); /* Retorna 1 se a pilha estiver vazia, caso contrrio, 0. */ extern void stack_push(stack_data d); /* Fora o valor d na pilha. */ extern stack_data stack_pop(); /* Retorna o elemento de cima da pilha, e o remove. Retorna lixo se a pilha estiver vazia. */ O arquivo de cdigo da biblioteca vem a seguir: include "stack.h" #include <stdio.h> /* Biblioteca Esttica. Esta biblioteca oferece as operaes bsicas de pilha para uma pilha de inteiros */ struct stack_rec { stack_data data; struct stack_rec *next; }; struct stack_rec *top=NULL; void stack_init() /* Inicializa esta biblioteca. Chama antes de chamar qualquer outra coisa. */ { top=NULL; } void stack_clear() /* Apaga todas as entradas da pilha. */ { stack_data x; while (!stack_empty()) x=stack_pop(); } int stack_empty() /* Retorna 1 se a pilha estiver vazia, caso contrrio, 0. */ { if (top==NULL) return(1); else 59

return(0); } void stack_push(stack_data d) /* Fora o valor d na pilha. */ { struct stack_rec *temp; temp = (struct stack_rec *)malloc(sizeof(struct stack_rec)); temp->data=d; temp->next=top; top=temp; } stack_data stack_pop() /* Retorna o elemento de cima da pilha, e o remove. Retorna lixo se a pilha estiver vazia. */ { struct stack_rec *temp; stack_data d=0; if (top!=NULL) { d=top->data; temp=top; top=top->next; free(temp); } return(d); } Observe como esta biblioteca realiza a ocultao de informaes: quem vir apenas o arquivo de cabealho no poder dizer se a pilha implementada com matrizes, ponteiros, arquivos ou de outro modo. Observe tambm que a linguagem C usa NULL. NULL est definido em stdio.h, de forma que quase sempre voc ter que incluir stdio.h quando usar ponteiros. NULL o mesmo que zero. Tente isto Adicione uma funo dup, count e add para a biblioteca de pilha duplicar o elemento do topo da pilha, retornar uma soma do nmero de elementos na pilha e adicionar os dois elementos de cima da pilha. Crie um programa de driver e um makefile e compile a biblioteca de pilha com o driver para verificar se funciona. Erros que devem ser evitados na linguagem C Esquecer de incluir os parnteses quando voc fizer referncia a um registro, como em (*p).i acima. Falhar em descartar um bloco alocado. Por exemplo, voc no deve dizer top=NULL na funo stack, pois tal ao deixa rfos os blocos que precisam ser descartados.

60

Esquecer de incluir stdio.h com qualquer operao de ponteiro, de forma que tenha acesso a NULL

35 Como utilizar ponteiros com matrizes Matrizes e ponteiros esto intimamente ligados em C. Para usar matrizes efetivamente, voc precisa saber como usar os ponteiros com elas. O total entendimento da relao entre os dois provavelmente vai requerer dias de estudo e experincias, porm esse esforo ser recompensado. Vamos comear com um exemplo simples de matrizes em C: #define MAX 10 void main() { int a[MAX]; int i; int *p; p=a; for(i=0; i<MAX; i++) a[i]=i; printf("%d ",*p); } Digite este cdigo e tente compil-lo. Voc ver que a linguagem C no compilar. Para copiar a em b, voc precisa digitar algo como: Ou, trocando em midos: Melhor ainda, use o utilitrio memcpy em string.h. As matrizes em C so incomuns, enquanto as variveis a e b no so, tecnicamente, matrizes. Em vez disso, elas so ponteiros permanentes para matrizes. a e b apontam permanentemente para os primeiros elementos de suas respectivas matrizes (elas mantm os endereos de a[0] e b[0], respectivamente). Visto que so ponteiros permanentes, voc no pode alterar seus endereos. Logo, a instruo a=b; no funciona. Sendo a e b ponteiros, voc pode fazer vrias coisas interessantes com ponteiros e matrizes. Por exemplo, o cdigo seguinte funciona: A instruo p=a; funciona pois a um ponteiro. Tecnicamente, a aponta para o endereo do elemento zero da matriz. Como este elemento um inteiro, a um ponteiro de inteiro simples. Assim sendo, declarar p como um ponteiro de um inteiro e defini-lo como igual a a funciona. Outro modo de dizer a mesma coisa seria substituir p=a; por p=&a[0];. Considerando que a contm o endereo de a[0], a e &a[0] significam a mesma coisa. A figura abaixo mostra o estado das variveis antes de iniciar a execuo do loop for:

61

Agora que p est apontando para o elemento 0 de a, voc pode fazer algumas coisas incomuns com ele. A varivel a um ponteiro permanente e no pode ser alterada, mas p no est sujeita a tais restries. Na verdade, a linguagem C o estimula a mud-lo usando a aritmtica de ponteiro. Por exemplo, se voc disser p++;, o compilador sabe que p aponta para um inteiro e esta instruo aumenta p o nmero apropriado de bytes para transferi-lo para o prximo elemento na matriz. Se p apontasse para uma matriz de estruturas de 100 bytes, p++; mudaria p para mais de 100 bytes. A linguagem C se encarrega dos detalhes de tamanho de elemento. Voc tambm pode copiar a matriz a em b usando ponteiros. O seguinte cdigo pode substituir (para i=0; i<MAX; a[i]=b[i], i++);: Voc pode abreviar este cdigo da seguinte maneira: Voc pode abreviar ainda mais: E se voc for alm do final da matriz a ou b com os ponteiros p ou q? Para a linguagem C isso no importa, pois ela continua aumentando p e q, copiando sobre outras variveis. Voc precisa ter cuidado ao indexar em matrizes em C, porque a linguagem C assume que voc sabe o que est fazendo. Voc pode passar uma matriz como a ou b para uma funo de dois modos diferentes. Imagine uma funo dump que aceita uma matriz de inteiros como um parmetro e imprime o contedo da matriz para stdout. H duas maneiras de codificar dump: ou: A varivel nia (number_in_array) necessria para que o tamanho da matriz seja conhecido. Note que apenas um ponteiro para a matriz, em vez do contedo da matriz, passa para a funo. Observe tambm que as funes em C podem aceitar matrizes de tamanho varivel como parmetros. 36 Strings de caracteres As strings de caracteres em C so profundamente interligadas aos ponteiros. Voc precisa se familiarizar com os conceitos de ponteiros abordados nos artigos anteriores para usar as strings de caracteres em C de forma eficaz. Uma vez habituado com elas, voc pode realizar manipulaes de strings de caracteres de forma bastante eficiente. Uma string de caracteres em C apenas uma matriz de caracteres. A seguinte linha declara uma matriz que pode conter uma string de at 99 caracteres: char str[100];

62

Ela contm caracteres, como voc pode esperar: str[0] o primeiro caractere da string, str[1] o segundo caractere e assim por diante. Mas por que uma matriz de 100 elementos no capaz de suportar 100 caracteres? Porque a linguagem C utiliza strings de caracteres de terminao nula, significando que o final de qualquer string de caracteres marcado por um valor ASCII de 0 (o caractere nulo), tambm representado em C como ''. A terminao nula muito diferente do modo que outras linguagens lidam com strings de caracteres. Por exemplo, em Pascal, cada string de caracteres consiste em uma matriz de caracteres, com byte de extenso que mantm a contagem do nmero de caracteres armazenada na matriz. Esta estrutura d ao Pascal uma vantagem definitiva ao indagar a extenso de uma string de caracteres. O Pascal pode simplesmente devolver o byte de extenso, enquanto a linguagem C precisa contar os caracteres at encontrar. Este fato torna a linguagem C muito mais lenta do que a Pascal em alguns casos, mas mais rpida em outros, como veremos nos exemplos a seguir. Visto que a linguagem C no oferece qualquer apoio explcito para strings de caracteres na prpria linguagem, todas as funes de manipulao de strings de caracteres so implementadas em bibliotecas. As operaes de I/O de strings de caracteres (gets, puts, etc.) so implementadas em <stdio.h> e um conjunto de funes de manipulao de strings de caracteres bastante simples implementado em <string.h> (em alguns sistemas, <strings.h>). O fato das strings de caracteres no serem nativas em C fora a criao de alguma codificao indireta. Por exemplo, suponha que voc queira atribuir uma string de caracteres a outra, ou seja, deseja copiar o contedo de uma string de caracteres para outra. Em C, como vimos no ltimo artigo, voc no pode simplesmente atribuir uma matriz outra. Voc precisa copiar elemento por elemento. A biblioteca de strings (<string.h> ou <strings.h>) contm uma funo chamada strcpy para esta tarefa. Eis uma parte extremamente comum de codificao encontrada em um programa em C comum: char s[100]; strcpy(s, "ol"); Depois que estas duas linhas so executadas, o seguinte diagrama mostra o contedo de s:

O diagrama acima mostra a matriz com seus caracteres. O diagrama inferior mostra os valores em ASCII equivalentes aos caracteres e assim que a linguagem C v uma string de caracteres (como uma matriz de bytes contendo valores inteiros). Veja Como funcionam os bits e os bytes para uma explicao sobre os cdigos ASCII. O seguinte cdigo mostra como usar strcpy em C: #include <string.h>

63

int main() { char s1[100],s2[100]; strcpy(s1,"Ol"); /* copy "Ol" em s1 */ strcpy(s2,s1); /* copy s1 em s2 */ return 0; } strcpy usado sempre que uma string de caracteres inicializada em C. Voc utiliza a ao strcmp na biblioteca de strings de caracteres para comparar duas seqncias. Ela retorna um inteiro que indica o resultado da comparao. Zero significa duas strings de caracteres iguais, um valor negativo significa que s1 menor que s2 e um valor positivo significa que s1 maior que s2. #include <stdio.h> #include <string.h> int main() { char s1[100],s2[100]; gets(s1); gets(s2); if (strcmp(s1,s2)==0) printf("igual\n"); else if (strcmp(s1,s2)<0) printf("s1 menor que s2\n"); else printf("s1 maior que s2\n"); return 0; } Outras funes comuns na biblioteca de strings de caracteres incluem strlen, que retorna a extenso de uma string, e strcat, que concatena duas strings. A biblioteca de strings de caracteres contm vrias outras funes que voc pode usar lendo a pgina principal. Para comear a criar funes de strings de caracteres e ajud-lo a compreender o cdigo de outro programador (cada um tem seu prprio conjunto de funes para fins especiais em um programa), veremos 2 exemplos, strlen e strcpy. Veja a seguir uma verso estritamente em Pascal de strlen: int strlen(char s[]) { int x; x=0; while (s[x] != '\0') x=x+1; return(x); } A maioria dos programadores de C evita esta abordagem por parecer ineficiente. Em vez disso, quase sempre usam uma abordagem com base em ponteiros: int strlen(char *s) 64

{ int x=0; while (*s != '\0') { x++; s++; } return(x); } Voc pode abreviar esta codificao da seguinte maneira: int strlen(char *s) { int x=0; while (*s++) x++; return(x); } Acredito que um verdadeiro especialista em C encurtaria ainda mais esta codificao. Quando compilo estes trs trechos de cdigo em um MicroVAX com gcc, sem qualquer otimizao, e executo cada um 20 mil vezes em uma string de 120 caracteres, a primeira parte do cdigo gera um tempo de 12,3 segundos, a segunda 12,3 segundos e a terceira 12,9 segundos. O que isso significa? Para mim, significa que voc deve escrever o cdigo do modo mais fcil para o seu entendimento. Os ponteiros em geral produzem um cdigo mais rpido, porm o cdigo strlen acima mostra que nem sempre isso ocorre. Podemos passar pela mesma evoluo com strcpy: strcpy(char s1[],char s2[]) { int x; for (x=0; x<=strlen(s2); x++) s1[x]=s2[x]; } Observe aqui que <= importante no loop for porque a codificao copia o ''. Certifique-se de copiar ''. Os bugs importantes ocorrem mais tarde se voc no os excluir, pois a string de caracteres no tem fim e, portanto, tamanho desconhecido. Observe tambm que este cdigo muito ineficiente, pois strlen sempre invocado pelo loop for. Para resolver este problema, voc pode usar a seguinte codificao: strcpy(char s1[],char s2[]) { int x,len; len=strlen(s2); for (x=0; x<=len; x++) s1[x]=s2[x]; } A verso de ponteiro similar: strcpy(char *s1,char *s2) { 65

while (*s2 != '\0') { *s1 = *s2; s1++; s2++; } } Voc pode abreviar esta codificao ainda mais: strcpy(char *s1,char *s2) { while (*s2) *s1++ = *s2++; } Se quiser, pode at mesmo dizer while (*s1++ = *s2++);. A primeira verso de strcpy leva 415 segundos para copiar uma string de 120 caracteres 10 mil vezes, a segunda verso leva 14,5 segundos, a terceira verso 9,8 segundos e a quarta 10,3 segundos. Como voc pode ver, aqui os ponteiros oferecem um aumento significativo de desempenho. O prottipo para a funo strcpy na biblioteca de strings indica que ele foi projetado para retornar um ponteiro para uma string: char *strcpy(char *s1,char *s2) A maioria das funes de strings de caracteres retorna um ponteiro de seqncia como resultado, e strcpy retorna o valor de s1 como resultado. s vezes, usar ponteiros com strings de caracteres pode resultar em melhorias definidas em velocidade, e voc pode tirar vantagem dela se pensar um pouco a respeito. Por exemplo, suponha que queira remover os espaos em branco iniciais de uma string. Voc talvez queira transferir os caracteres para cima das partes em branco para remov-las. Em C, voc pode evitar o movimento completamente: #include <stdio.h> #include <string.h> int main() { char s[100],*p; gets(s); p=s; while (*p==' ') p++; printf("%s\n",p); return 0; } Isto muito mais rpido do que a tcnica de transferncia, especialmente para strings de caracteres longas. Voc aprender muitos outros truques com strings de caracteres, medida que praticar e ler outras codificaes. Praticar a chave.

66

37 Nota especial sobre strings de caracteres Suponha que voc crie os seguintes fragmentos de cdigos e os execute: Fragment 1 { char *s; s="hello"; printf("%s\n",s); } Fragment 2 { char s[100]; strcpy(s,"hello"); printf("%s\n",s); } Estes dois fragmentos produzem o mesmo resultado, porm seu comportamento interno bastante diferente. No fragmento 2, voc no pode dizer s="hello";. Para entender as diferenas, voc precisa entender como funciona a tabela de constantes de strings de caracteres em C. Quando seu programa compilado, o compilador cria o arquivo objeto que contm seu cdigo de mquina e uma tabela com todas as constantes de strings declaradas no programa. No fragmento 1, a instruo s="hello"; faz com que s aponte para o endereo da string de caracteres hello na tabela de constantes de string. Como esta string est na tabela de constantes de string, tecnicamente parte do cdigo executvel, no pode ser modificada. Voc s pode apontar para ela e utiliz-la para leitura. No fragmento 2, a seqncia hello tambm existe na tabela de constantes, portanto voc pode copi-la na matriz de caracteres denominada s. Visto que s no um ponteiro, a instruo s="hello"; no funcionar no fragmento 2. Ele sequer compilar. Nota especial sobre o uso de seqncias de caracteres com malloc Suponha que voc escreveu o seguinte programa: int main() { char *s; s=(char *) malloc (100); s="hello"; free(s); return 0; } Ele compila corretamente, mas indica uma falha de segmentao na linha free ao ser executado. A linha malloc aloca um bloco de memria de 100 bytes e aponta s para ele, 67

porm agora a linha s="hello"; um problema. Ela est sintaticamente correta, pois s um ponteiro, mas quando s="hello"; executado, s aponta para a seqncia na tabela de constante de seqncias e o bloco alocado deixado rfo. Considerando que s est apontando para a tabela de constante de string, a string de caracteres no pode ser alterada e free falha, pois no consegue desalocar um bloco na regio executvel. A codificao correta a seguinte: int main() { char *s; s=(char *) malloc (100); strcpy(s,"hello"); free(s); return 0; } Tente isto Crie um programa que l em uma seqncia contendo um nome seguido por um espao, seguido por um sobrenome. Escreva funes para remover espaos em branco no incio ou no final. Escreva outra funo que retorne o sobrenome; Escreva uma funo que converte uma seqncia em maisculas; Escreva uma funo que obtenha a primeira palavra de uma string de caracteres e retorne o restante da string.

Erros que devem ser evitados na linguagem C Perder o caractere , o que fcil se voc no for cuidadoso, pode resultar em alguns bugs. Certifique-se de copiar ao copiar as strings de caracteres. Se voc criar uma nova string, certifique-se de colocar nela. Se copiar uma string para outra, certifique-se de que a string receptora suficientemente grande para comportar a string de origem, incluindo . Finalmente, se voc apontar um ponteiro de caracteres para alguns caracteres, certifique-se de termin-los com . 38 Precedncia de operador A linguagem C contm muitos operadores, e por causa do modo como funciona a precedncia, as interaes entre mltiplos operadores podem se tornar confusas. x=5+3*6; X recebe o valor 23, no 48, pois em C a multiplicao e diviso tm maior precedncia do que a adio e subtrao. char *a[10]; a um ponteiro simples para uma matriz de 10 caracteres, ou uma matriz de 10 para caractere? A menos que voc saiba as convenes de precedncia em C, no h como 68

descobrir. Semelhantemente, em E.11, vimos que por conta das instrues de precedncia como *p.i = 10; no funciona. Em vez disso, a forma (*p).i = 10; deve ser usada para forar a precedncia correta. A tabela a seguir de Linguagem de programao em C, de Kernighan e Ritchie, mostra a hierarquia de precedncia em C. A linha de cima tem a maior precedncia. Operadores Associao ([-. Esquerda para direita ! - ++ -{- + * & (type-cast) sizeof Direita para esquerda (na linha acima , +, - e * esto em formas unrias) */% Esquerda para direita +Esquerda para direita << >> Esquerda para direita < <= > >= Esquerda para direita == != Esquerda para direita & Esquerda para direita ^ Esquerda para direita | Esquerda para direita && Esquerda para direita || Esquerda para direita ?: Esquerda para direita = += -= *= /= %= &= ^= |= <<= >>= Direita para esquerda , Esquerda para direita Usando esta tabela, voc pode ver que char *a[10]; uma matriz de 10 ponteiros para caractere. Voc tambm pode ver por que os parnteses so necessrios se (*p).i for manipulado corretamente. Aps adquirir prtica, voc memorizar a maior parte desta tabela, mas, s vezes, alguma coisa poder no funcionar devido a algum problema de precedncia. 39 Argumentos de linha de comando A linguagem C oferece um mecanismo bastante simples para recuperar parmetros de linha de comando digitados pelo usurio. Ele passa um parmetro argv para a funo principal no programa. As estruturas argv aparecem em nmero expressivo das mais avanadas chamadas de biblioteca, portanto compreend-las til a qualquer programador de C. Digite o seguinte cdigo e compile-o: #include <stdio.h> int main(int argc, char *argv[]) { int x; printf("%d\n",argc); for (x=0; x<argc; x++) printf("%s\n",argv[x]); return 0; }

69

Neste cdigo, o programa principal aceita dois parmetros, argv e argc. O parmetro argv uma matriz de ponteiros para string que contm os parmetros digitados quando o programa foi chamado na linha de comando do UNIX. O inteiro argc contm a soma do nmero de parmetros. Esta parte do cdigo representa os parmetros da linha de comando. Para testar, compile o cdigo em um arquivo executvel denominado aaa e digite aaa xxx yyy zzz. O cdigo imprimir os parmetros de linha de comando xxx, yyy e zzz, um em cada linha. A linha char *argv[] uma matriz de ponteiros para string. Em outras palavras, cada elemento da matriz um ponteiro e cada ponteiro aponta para uma string (tecnicamente, ao primeiro caractere da string). Assim, argv[0] aponta para uma string de caracteres que contm o primeiro parmetro na linha de comando (o nome do programa), argv[1] aponta para o prximo parmetro e assim sucessivamente. A varivel argc indica quantos ponteiros na matriz so vlidos. Voc ver que o cdigo precedente no faz nada alm de imprimir cada string vlida apontada por argv. Por haver o argv, voc pode permitir que o programa reaja aos parmetros de linha de comando digitados pelo usurio de forma fcil. Por exemplo, voc pode querer que seus programas detectem a palavra help como o primeiro parmetro depois do nome do programa e esvaziem um arquivo de ajuda em stdout. Os nomes de arquivos tambm podem ser passados e usados em suas instrues fopen. 40 Arquivos binrios Arquivos binrios so bem parecidos com matrizes de estruturas, exceto pelas estruturas, que residem no arquivo de disco, em vez de uma matriz na memria. Uma vez que as estruturas em um arquivo binrio esto em disco, voc pode criar colees muito grandes desses arquivos (limitadas apenas pelo espao em disco disponvel). Elas tambm so permanentes e esto sempre disponveis. A nica desvantagem a lentido decorrente do tempo de acesso ao disco. Os arquivos binrios tm duas caractersticas que os distinguem de arquivos de texto: voc pode pular instantaneamente para qualquer estrutura no arquivo que oferea acesso aleatrio como em uma matriz; voc pode mudar o contedo de uma estrutura em qualquer lugar e hora no arquivo. Arquivos binrios geralmente tm tempos de leitura e escrita mais rpidos que os arquivos de texto, j que uma imagem binria do registro armazenada diretamente da memria para o disco (ou vice-versa). Em um arquivo de texto, tudo precisa ser convertido de trs para frente em texto e isto consome tempo. A linguagem C suporta o conceito de arquivo de estruturas de forma bsica. Ao abrir o arquivo, voc pode ler uma estrutura, escrever uma estrutura ou pesquisar qualquer estrutura no arquivo. Este conceito de arquivo suporta o conceito de um ponteiro de arquivo. Quando ele aberto, o ponteiro aponta para o registro 0 (o primeiro registro no arquivo). Qualquer operao de leitura l a estrutura apontada e move o ponteiro um nvel abaixo na estrutura. Qualquer operao de escrita grava na estrutura apontada e move o ponteiro um nvel abaixo na estrutura. Seek move o ponteiro para o registro solicitado. Lembre-se de que a linguagem C considera tudo no arquivo de disco como blocos de bytes lidos do disco na memria ou lidos da memria para o disco. A linguagem C usa um ponteiro de arquivo, mas pode apontar para qualquer byte no arquivo. 70

O programa abaixo ilustra estes conceitos: #include <stdio.h> /* descrio de registro aleatrio - pode ser qualquer uma */ struct rec { int x,y,z; }; /* grava e depois l 10 registros arbitrrios do arquivo "junk". */ int main() { int i,j; FILE *f; struct rec r; /* cria o arquivo de 10 registros */ f=fopen("junk","w"); if (!f) return 1; for (i=1;i<=10; i++) { r.x=i; fwrite(&r,sizeof(struct rec),1,f); } fclose(f); /* l os 10 registros */ f=fopen("junk","w"); if (!f) return 1; for (i=1;i<=10; i++) { fread(&r,sizeof(struct rec),1,f); printf("%d\n",r.x); } fclose(f); printf("\n"); /* usa fseek para ler os 10 registros em ordem inversa */ f=fopen("junk","w"); if (!f) return 1; for (i=9; i>=0; i--) { fseek(f,sizeof(struct rec)*i,SEEK_SET); fread(&r,sizeof(struct rec),1,f);

71

printf("%d\n",r.x); } fclose(f) printf("\n"); /* usa fseek para ler todo os outros registros */ f=fopen("junk","r"); if (!f) return 1; fseek(f,0,SEEK_SET); for (i=0;i<5; i++) { fread(&r,sizeof(struct rec),1,f); printf("%d\n",r.x); fseek(f,sizeof(struct rec),SEEK_CUR); } fclose(f); printf("\n"); /* usa fseek para ler o 4 registro, altere-o e escreva-o de volta */ f=fopen("junk","r+"); if (!f) return 1; fseek(f,sizeof(struct rec)*3,SEEK_SET); fread(&r,sizeof(struct rec),1,f); r.x=100; fseek(f,sizeof(struct rec)*3,SEEK_SET); fwrite(&r,sizeof(struct rec),1,f); fclose(f); printf("\n"); /* l os 10 registros para garantir que o 4 arquivo foi alterado */ f=fopen("junk","r"); if (!f) return 1; for (i=1;i<=10; i++) { fread(&r,sizeof(struct rec),1,f); printf("%d\n",r.x); } fclose(f); return 0; } Neste programa, uma descrio de estrutura rec foi usada, mas voc pode usar a descrio de estrutura que quiser. Voc tambm pode ver que fopen e fclose trabalham exatamente da mesma forma que nos arquivos de texto.

72

As novas funes aqui so fread, fwrite e fseek. A funo fread captura quatro parmetros: um endereo de memria; o nmero de bytes a ser lido por bloco; o nmero de blocos a ser lido; a varivel de arquivo. Assim, a linha fread(&r,sizeof(struct rec),1,f); diz para ler 12 bytes (o tamanho de rec) do arquivo f (a partir do local atual do ponteiro de arquivo) no endereo de memria &r. Um bloco de 12 bytes solicitado. Seria fcil ler 100 blocos por disco em uma matriz na memria alterando 1 para 100. A funo fwrite opera da mesma forma, mas move o bloco de bytes da memria para o arquivo. A funo fseek move o ponteiro de arquivo para um byte no arquivo. Em geral, voc muda o ponteiro em incrementos de sizeof(struct rec) para manter o ponteiro nos limites do registro. Voc pode usar trs opes ao pesquisar: SEEK_SET SEEK_CUR SEEK_END SEEK_SET move o ponteiro x bytes para baixo, a partir do incio do arquivo (desde o byte 0 no arquivo). SEEK_CUR move o ponteiro x bytes para baixo da posio atual do ponteiro. SEEK_END move o ponteiro do final do arquivo (com esta opo voc precisa usar offsets negativos). Vrias opes diferentes aparecem no cdigo acima. Observe em particular a seo na qual o arquivo aberto com o modo r+. Isto abre o arquivo para leitura e gravao, permitindo que os registros sejam alterados. O cdigo busca um registro, l e altera um campo. Depois busca novamente, pois a leitura deslocou o ponteiro, e grava de volta a alterao.

Fonte: informatica.hsw.uol.com.br/programacao-em-c39

73

Você também pode gostar