You are on page 1of 815
2 Cc Completo e Total 3a Edicao Revista e Atualizada Herbert Schildt Traducao e Revisto Técnica Roberto Carlos Mayer Diretor da Mayer & Bunge Informética Prof. do Departamento de Cigncia da Computagao da USP-SP MAKRON Books Ltda. Rua Tabapua, 1.348 — Itaim-Bibi 747 — Lapa (CEP 04533-004 — Sao Paulo — SP CEP 05065-110— Sao Paulo — SP (11) 3849-8604 e (11) 3845-6622 (11) 3611-0740 e-mail: makron@books.com.br fax (11) 3611-0444 Siio Paulo + Rio de Janeiro * Ribeirdo Preto + Belém * Belo Horizonte + Brasilia * Campo Grande Cuiabéi + Curitiba + Floriandpolis + Fortaleca * Goidnia * Manaus + Porto Alegre + Recife + Salvador Brasil » Argentina * Colmbia * Costa Rica * Chile * Espanha * Guatemala + México * Pere + Porto Rico * Venezuela Do original C: The Complete Reference — Third Edition Copyright © 1995 McGraw-Hill, Inc Copyright © 1987 Makron Books do Brasil Editora Ltda. Todos os direitos para a lingua portuguesa reservados pela Bditora McGraw-Hill, Ltda, e Makron Books do Brasil Editora Lida. Nenhuma parte desta publicagie podera ser reproduzida, guardada pelo sistema “retrieval” ou transmitida de qualquer modo ou por qualquer outro meio, seja este eletrOnico, mecinico, de fotocépia, de gravagio, ou outros, sem prévia autorizacio, por escrito, das Editoras. EDITOR: MILTON MIRA DE ASSUMPGAO FILHO Produtora Editorial: Joana Figueiredo Produtor Gréfico: José Roberto Petroni Editoragao Eletronica: E.R.J. Informatica Ltda Dados de Catalogagao na Publicagao (CIP) Internacional (Camara Brasileira do Livro, SP, Brasil) Schildt, Herbert C, completo e total - 3! edicgdo revista e atualizada Herbert Schildt ; tradugdéo e revisdéo técnica Roberto Carlos Mayer. Sao Paulo : Makron Books, 1996. rence Titulo original: C: the complete re ISBN 85-346-0595-5 1. C (Linguagem de programagdéo) I. 6-0491 CDD-005.133 indices para catalogo sistematic 1. C : Linguagem de programacdo : Computadores Processamento de dados 005.133 Sumario © Que Ha de Novo Nesta Edicéo Dn XXIV O Que Ha n0 LivIO eccecetesstevtevssevesevestsesess : XXV Parte 1 — A Linguagem C0... 0s. eeeseeeeee sence tence eeeeneaneaee 1 1, Uma Visdo Geral de Co... ce seseeees sence ceca eeeee eens aeeree 3 As Origens de C 3 C E uma Linguagem de Médio Nivel . 5 4 CE uma Linguagem Estruturada ..... venteeeee cee 5 C E uma Linguagem para Programadores 7 Compiladores Versus Interpretadores . . 9 A Forma de um Programa em C ..... sete cee A Biblioteca ea Linkedigado .......... 000000 esceee eer 1 Compilagdo Separada ... 0.6... 06 00sec eve eee eeeeee eee cee 212 Compilando um Programa em C ..... 6.6... 000 eee eeeeeeeeeees se © Mapa de Memoria de C 13 C Versus C++ .. bebe tee et een teeeeee 14 Um Compilador C++ Funcionaré com Programas C? ...... cece D5 Uma Revisdo de Termos eee a eee 2. Expresses EMC... 0... e cece eee cece tenet eee e eee eee ee eee 16 Os Cinco Tipos Basicos de Dados 16 Modificando os Tipos Basicos .. 17 Nomes de Identificadores . 19 Varidveis 0.0000 c ce eeeee cece eeeeee : eo 20 VI C= Completo ¢ Total Onde as Varidveis Sao Declaradas Varidveis Locais Parametros Formais Variaveis Globais ... Modificadores de Tipo de Acesso const . 20 voiatile dores de Tipo de Classe de Armazenamento A Varidveis static : 7 Variaveis register Inicializagao de Variaveis Constantes . . Constantes Hexadecimais e Octais Constantes String Constantes Caractere de Barra Invertida Operadores . O Operador de Atribuicao Conversio de Tipos em Atribuigoes: Atribuigdes Multiplas . Operadores Aritméticos Incremento e Decremento . Operadores Relacionais e Logicos Operadores Bit a Bit O Operador ? .... 5 Os Operadores de Ponteiros & e* .... O Operador em Tempo de Compitacio s O Operador Virgula ... : Os Operadores Ponto () e Seta (>) Parénteses e Colchetes Como Operadores Resumo das Precedéncias Expressdes Ordem de Avaliagao : Conversio de Tipos em Expresses. Casts Espagamento e Parénteses C Reduzido izeof . Comandos de Controle do Programa . Verdadeiro e Falso em C bev eeveeeeseeceeeteevevtreeeees Comandos de Selegio beeteteenee if ifs Aninhados beeecens A Escada if-else-if .... feveeens O? Alternativo. . Sumo vil A Expresso Condicional ...... vec eeeteeeeettte ete t ttt e eee es 69 switch es 70 Comandos switch Aninhados ........0cc0s 0s veceeeeees 74 Comandos de Iteragdo . feetteeeees coven 74 O Lago for eee cece beeen voveeetnee 74, Variacées do Lago for .. teens ee .-76 O Lago Infinito 2... eee cece eee peer - 80 Lagos for sem Corpos pee neneeernrs eee ceceeeee BL O Lago while ee BT O Lace do-while - 84 Comandos de Desvio 85 O Comando return . 285 O Comando goto ... +86 © Comando break . 86 A Funcao exit)... - 88 © Comando continue - 89 Comandos de Expressées : feeee vee 91 Blocos de Comandos ......60066062000000 . cece OL Matrizes @ Strings .... 0.6.6 e cece cece cece eee ee ee teen cree es OD Matrizes Unidimensionais . ..... peer ceee eee 92 Gerando um Ponteiro para uma Matriz ee cece OM Passando Matrizes Unidimensionais para Fungdes .......... 02... cee DM Strings 96 Matrizes Bidimensionais ---98 Matrizes de Strings ...... 102 Matrizes Multidimensionais uG 104 Indexando Ponteiros = 105 Inicializagao de Matriz ..... bee 107 Inicializagao de Matrizes Nao-Dimensionadas ...... pubepnonecdl) Um Exemplo com o Jogo-da-Velha seer : 109 Ponteiros .........++ ee eee Bee eee eee eee eee eer atts) O Que Sao Ponteiros? ......... - pees 5 ceeeeegils) Varidveis Ponteiros settee oe 5 cee IB Os Operadores de Ponteiros ceteee ails Expressdes com Ponteiros 116 Atribuicao de Ponteiros ceeeees voce teteseteeneeeees 116 Aritmética de Ponteiros bocce ttetettteeteeeeeetersereeteeeess 116 Comparagao de Ponteiros . 118 Ponteiros e Matrizes ....... Bboeoneee coe ween 120 Matrizes de Ponteiros .........0....5 cevceee eee : 2121 Indiregao Multipla . ern weveeee 122 Inicializagao de Ponteiros .. : wo 124 Ponteiros para Fungées ... : poe08H ee 126 Vit C — Completo e Total As Fungées de Alocagao Dinamica em C 128 Matrizes Dinamicamente Alocadas 130 Problemas com Ponteiros. 133 L7. Jospocnousoapepbononnsoccodenocondonsupsuopanoonoo0 EL) A Forma Geral de uma Fungdo .....0606022 502+ 138 Regras de Escopo de Fungées ; 5 139 Argumentos de Fungdes . 139 Chamada por Valor, Chamada por Referéncia . 140 Criando uma Chamada por Referéncia 141 Chamando Fungdes com Matrizes 142 argc e argv — Argumentos para main() 147 O Comando return 5 150 Retornando de uma Fungao .. : 150 Retornando Valores .... 152 Fungdes Que Devolvem Valores Nao-Inteiros 154 Prototipos de Fungdes -- 156 Retornando Ponteiros = 158 Fung@es do Tipo void - 159 © Que main) Devolve? - 160 Recursao . 160, Declarando uma Lista de Parametros de Extensio Variavel +162 Declaracao de Parametros de Fungdes Moderna Versus Classica 162 Questdes sobre a Implementacio : - 164 Parametros e Fungoes de Propésite Geral 164 Eficiéncia 164 Bibliotecas e Arquivos v2 165 Arquivos Separados vee 165 Bibliotecas . : 165 De Que Tamanho Deve Ser um Arquivo de Programa? . 166 Estruturas, Unides, Enumeracées e Tipos Definidos pelo Usuario .... 167 Estruturas . 167 Referenciando Elementos de Estruturas 169 Atribuicao de Estruturas 170 Matrizes de Estruturas 171 Um Exemplo de Lista Postal 171 Passando Estruturas para Fungies 179 Passando Elementos de Estrutura para Fungoes . 179 Passando Estruturas Inteiras para Funcées . . 180 Ponteiros para Estruturas cee feces = 182 Declarando um Ponteiro para Estrutura ...... 00.22.22. - 182 Usando Ponteiros para Estruturas .... : : =. 182 Matrizes e Estruturas Dentro de Estruturas 185, Campos de Bits 186 Sumério IX Unides eee eee e eee ee beeen eee . eeeeeeeeee eens Enumeragbes BapaeHo eo! Usando sizeof para Assegurar Portabilidade ........ Beene od typedef Seen eee eter eee eee cee ety o6) B. E/S pelo Console .........e0eeeee eens eee Erneta re aber Lendo e Escrevendo Caracteres eee ee eee roo Um Problema com getchar() ......... 200 Alternativas para getchar() .......... +200 Lendo e Escrevendo Strings .........0605 2201 E/S Formatada pelo Console « beveeveestsseeetteseeerternaeseer ©1203 printf0 5 beceeeteveeeee ees 1.204 Escrevendo Caracteres : sete +205 Escrevendo Niimeros ........ : beceeetee +. 205 Mostrando um Endereco voce vceneteete reese e206 O Especificador fon .....-..-. veveee ee 207 Modificadores de Formato. . beveveeeeee ees 207 O Especificador de Largura Minima de Campos... 0. . = 207 O Especificador de Precisd0 ........00s00ceeecseeeeessesseeseeeess 209 Justificando a Saida....... vette ttttnteeeeeens cece ee 210 Manipulando Outros Tipos de Dados : 210 Os Modificadores * ¢ # 5 210 seanf()........... 2 Especificadores de Formato .......s....5 : 212 Inserindo Nuimeros ..... cee 212 Inserindo Inteiros sem Sinal_ feeteeteettnteeeeeeeseterseee 213 Lendo Caracteres Individuais com sean{).........0000000+ 213 Lendo Strings ... feetevtcnneernens eee 213 Inserindo um Endereco |... 24 O Especificador % occ 215 Utilizando um Scanset 215 Deseartando Espacos em Branco Indesejados . 216 Caracteres de Espaco Nao-Branco na String de Controle .............-..--.216 Deve-se Passar Enderegos para seanf() .......0000c00seecseeeeeeeeeeeersees216 Modificadores de Formato Suprimindo a Entrada 9. E/S com Arq See eee eee ee eee eee Eee ee Teri o) E/S ANSI Versus E/S UNIX ceveeeeseeees cece eee 219 217 218 E/S em C Versus E/S em C++ : : +220 Streams e Arquivos . , : 220 Streams ... : 220 Streams de Texto oo .s.ccss. eee nrrereer en 221 Streams Binérias ..... : beettteeeees 221 Arquivos ...... 1221 C— Completo.e Total Fundamentos do Sistema de Arquivos ..........0000006000ecceceeeeeeeee ee 222 O Ponteiro de Arquivo Bopode . veer 2B Abrindo um Arquivo veces cover e een 224 Fechando um Arquivo sevens fever eee e e225 Escrevendo um Caractere : ; - 226 Lendo um Caractere +226 Usando fopen0), getc(), putc() e fclose() tees +227 Usando feof() . . +228 Trabalhando com Strings: ‘puts e fgets vee 230 TeWINd() 2.0... ccc eeeeeeeeeeeeeeeeteeetseeeeeeteeeetretectterereeseese231 ferror() ....... beet t tt nttreeeeees eee 232 Apagando Arquivos ..........2-00005 ceteees ceceeeee nee 234 Esvaziando uma Stream beeen b ce tnteeteeeteeee tener 235 fread() e fwrite() coven 235 Usando fread() e fwrite) : 5 235 fseek() e E/S com Acesso Aleatério ceceeeeeee e242 fprintf) e fscanf) coven eee MB As Streams Padrao 2245 ‘A Conexio de E/S pelo Console . . 245 Usando freopen() para Redirecionar as Streams Padro.......s...... 246 O Sistema de Arquivo Tipo UNIX ...... feeeeees veeeeee e247 open() fees feet e cet trsneeteeeeeeen ere 1 248 creat() . : . coven 249) close) . sees wees foe cee 249) read() e write() vee ceeeeeeteetteeeeeteeteee rere 250 unlink() : eee veteees 222251 Acesso Aleat6rio Usando Iseek() beet eeeennnneeeeeeeee ees 252 |. © Pré-processador de C e Comentarios .......- 200s eee cere eens 254 O Pré-processador de Co... fees seen 254 define .. vette ete tcvtneeeeeeseeeener ee 0285 Definindo Macros Semelhantes a FungSes....0..0cccccscse beeeeee 1287 #error +258 include : veces 1 . Diretivas de Compilagao Condicional beseeeeeee veeeee e259 Hif, Helse, Helif e #endif ..............005 feeteeecevenrreeeeneesess1259 Hifdef e Hite 00... eeeeee eee fesse 261 #undef .. cove vc tevvsseetesteeteettnereaees 262 Usando defined 263 Hine . ve tet ee tecvttreeeeeteteeteesttttsteteetertreeesersss 2264 fipragma....... fete teteestreeereesenneee sees +264 Os Operadores # © ## do Pré-processador veeetneeeeeeeetee 2+ 264 Nomes de Macros Predefinidas ........2.200c00ccseeseceseeeeeennnsteeees+ +266 Comentarios 00.6... coe cece eee eeeseceneceeeteecteesenet nese oces - 266 Sumdrio XI Parte 2 — A Bibl teca C Padréo 11, Linkedigéo, Bibliotecas e Arquivos de Cabecalho O Linkeditor Compilagdo Separada . Cédigo Relocavel ...... Linkeditando com Overlays Linkeditando com DLLs A Biblioteca C Padrao Arquivos de Biblioteca Versus Arquivos-Objetos . Arquivos de Cabecalho . beeeee Macros em Arquivos de Cabecalho Redefinigdo das Fungées da Biblioteca . 12, Fungoes de E/S.....-... pee eer 13. Fungées de String e de Coracteres ........ 4.065 14, Fungdes Mateméticas ....... 66... cece eee cee eee e eee eee a 15. Fungées de Hora, Data e Outras Relacionadas com o Sistema... 16. Alocagéo Dinémica.......... 6.005 Pee Eee eee eee Cee eereer 17. Fungées Graficas e de Texto .... 60.6 e ee ece ee eee ee eee ent enene 442 18, Fungdes Miscelaneas .............-0--0 06 Seen cece eee eevee .. 472 Parte 3 — Algoritmos e Aplicagées ...... 19. Ordenagéo e Pesquisa .. peer Ordenacao Tipos de Algoritmos de Ordenacao Uma Avaliagio dos Algoritmos de Ordenacao A Ordenagao Bolha — O Deménio das Trocas .. Ordenacao por Selecéo Ordenacao por Insergio Ordenagées Melhores Ordenacao Shell Quicksort Escolhendo uma Ordenagao Ordenando Outras Estruturas de Dados Ordenagao de Strings ....... Ordenacao de Estruturas : Ordenando Arquivos de Acesso Aleatorio em Disco Pesquisa . Métodos de Pesquisa xi C—Completo e Total Pesquisa Seqiiencial feet coer BBB Pesquisa Bindtia - eee 23 20. Filas, Pilhas, Listas Encadeadas e Arvores Bindrias .............. 525 Fila oo... fee tteeeee oe - 526 A Fila Circular... vevceeeees ve 531 Pilhas bette teteeee serene Sas) Listas Encadeadas Hoobddnb popun0606 540 Listas Singularmente Encadeadas Listas Duplamente Encadeadas Um Exemplo de Lista Postal Arvores Bindrias . 21. Matrizes Esparsas ...... ‘A Matriz Esparsa com Lista Encadeada da Abordagem com Lista Encadeada | A Abordagem com Arvore Binaria para Matriz Esparsa Anélise da Abordagem com Arvores Binétias ‘A Abordagem com Matriz de Ponteiros para Matriz Esparsa Andlise da Abordagem com Matriz de Ponteiros de Hashing ... Escolhendo uma Abordagem . 22, Andlise de Expressées e Avaliagao ........+- Expressdes : Dissecando uma Expressio Anilise de Expressio .. : Um Analisador Simples de Expressdes Acrescentando Variaveis ao Analisador . Verificagao de Sintaxe em um Analisador Recursive Descendente 23. Solucdo de Problemas de Inteligéncia Artifical ........ Representacao ¢ Terminologia Explosies Combinatorias Técnicas de Pesquisa Avaliagao das Pesquisas Uma Representacao Grafica : A Pesquisa de Profundidade Primeiro .. i Uma Analise da Pesquisa de Profundidade Primeiro .. A Pesquisa de Extensdo Primeiro .... sees Uma Anidlise da Pesquisa de Extenso Primeiro Adicionando Heuristicas . A Pesquisa da Escalada da Montanha Anélise da Escalada da Montanha Sumario Xi A Pesquisa por Menor Esforgo ee vette bee 635) Andlise da Pesquisa por Menor Esforgo feet tenet n ee ee terre 636 Escolhendo uma Técnica de Pesquisa cece eee 637 Encontrando Multiplas Solugdes » 637 526 Remogao de Percurso eee eee 638 Remogao de NO . : 639 Encontrando a Solucéo Ideal 645 De Volta as Chaves Perdidas ... votes 651 24. Construindo o Esqueleto de um Programa Windows 95 ........... 655 A Perspectiva da Programagao Windows 95 ; = 656 O Modelo da Mesa de Trabalho . : ++ 656 O Mouse .......ceeeeeeeerreeeee betes cree e687 [cones e Mapas de Bits 657 Menus, Barras de Ferramentas, Barras de Status e Caixas de Didlogo 657 Como Windows 95 e Seu Programa Interagem 658 Windows 95 Usa Multitarefa Preemptiva 659 A API Win32: A API de Windows 95 ....... 659 Os Componentes de uma Janela ...... : : cee 660 Nocées Basicas sobre Aplicagées Windows 95. -661 WinMain() . . wee eee eee . . 661 A Fungao de Janela.........0000ccsevveseee veeees cece 662 Classes de Janelas 00.0.0... 662 A Repeticio de Mensagens . on 663 Os Tipos de Dados Windows .. ao 663 Um Esqueleto Windows 95 664 Definindo a Classe de Janela 667 Criando uma Janela 669 A Repeticio de Mensagens 671 A Fungao de Janela feeetevereeteteeteeteessrerses e673 Usando um Arquivo de Definicao Serene peeeereer Oc Convenes sobre Nomes 0... .....:::ccceeeee feeee eee 674 Parte 4 — Desenvolvimento de Software Usando C............00+4++ 677 25. Interfaceamento com Rotinas em Linguagem Assembly ........... 679 Interface com a Linguagem Assembly ...... : 679 As Convengdes de Chamada de um Compilador C 681 ‘As Convengdes de Chamada do Microsoft C/C++ 681 Criando uma Fungao em Cédigo Assembly +683 Uma Fungao Simples em Cédigo Assembly 683 Um Exemplo de Chamada por Referéncia 688 Utilizando 0 Modelo de Meméria Grande para Dados Codigo 690 Criando um Esqueleto de Cédigo Assembly ............5 ves 692 xiv C— Completa ¢ Total ‘Usando asm 7 = 694 Quando Codificar em Assembler. - 695 26. Engenharia de Software Usando C .... 6... cece cece eee s OIF Projeto em Top-DOWN 2.0... 000.2 00 eeeeeeeeee eee oo Delineando Seu Programa cee 698 Escolhendo uma Estrutura de Dados . .. 699 Fungoes a Prova de Bala .........- 700 Usando MAKE . 703 Usando Macros com MAKE cet eetteeeee ees 707 Usando um Ambiente Integrado de Desenvolvimento 708 , Portabilidade e Depuragao .......... 66.0.6. 05 ++ 710 710 Os Operadores de Incremento ¢ Decremento seteteeeeteeeeereee TIT Utilizando Variaveis em Registradores .............. 712 Ponteiros Versus Indexagao de Matrizes .......... 715 Uso de Fungées 716 Programas Portavei .720 Usando #define beens see . 1 72D Dependéncias do Sistema Operacional «0.0... .s..-. 721 Diferencas no Tamanho dos Dados ; 722 Depuragéo 723 Erros de Ordem de Processamento . 1 7B Problemas com Ponteiros .. 2.724 Erros Bizarros de Sintaxe ..............4... eee wes 726 Erros por Um ... 727 Erros de Limites . -728 Omissio de Prototipo de Funcio. 729 Erros de Argumentos 730 Colisées entre a Pilha eo Heap . 731 Teoria Geral de Depuragio 731 A Arte da Manutencao de Programas ............... 1733 Consertando Erros oe 733 Protegao do Cédigo-Fonte -734 Parte 5 — Unt Interpretador ©... 2... .ccc eee eceee ence eee enees 737 28, Interpretadores C...... 6c cece cece e eee ens ees eee e ene e ens 139 A Importancia Pratica dos Interpretadores ... See 740 A Especificagio de Little C i -7AL Uma Restricao Importante de Little C ...... 742 Interpretando uma Linguagem Estruturada 743 Uma Teoria Informal de C 744 Expressées C 745 Sumério xv Avaliando Expresses ....... 00000. 0e cece cee e eee eee ee eee eens 746 © Analisador de Express6es ... bevtecerseeee 747 Reduzindo 0 Cédigo-Fonte a Seus Componentes... : 748 Analisador Recursivo Descendente Little C : 755 Interpretador Little C 7 768 A Varredura Prévia do Interpretador . fetet tees 769 A Fungao Main0 een bectetsneeeeeeteertrceensnssT22 A Funcao Interp block ......0000c0cccseceeeeee eee veveeeree 7B Tratando Varidveis Locais 789 Chamando Fungdes Definidas pelo Usuario cect e ete ceree eee ee ees 2 790 Atribuindo Valores a Varidveis vee ee 794 Executando um Comando if ......600c00c0cceeeeeeeeeeevsssseeeeeeesees 20795 Processando um Lago While .....00000000000¢00000cerereeeees 2.79 Processando um Lago Do-While ............ beeteeeees 797 O Lago for eens 798 Fungdes da Biblioteca Little C . feeee eee etr ere e es 799 Compilando e Linkeditando o Interpretador Little Co... es scceceesess 1803 Demonstrando Little Co... ...ecceecccsessseeseeeseeceerenssaeeeeesee ss 1804 Melhorando Little Co... 0... ...eceeeecceesseeeeseeseeeeneeenes 807 Expandindo Little C........ en 809 Adicionando Novos Recursos de Cs... 809 Adicionando Recursos Auxiliares sete eee . 810 indice Analitico . MAKRON Books Parte 1 A Linguagem C A primeira parte deste livro apresenta uma discussao completa da linguagem de programacao C. O Capitulo 1 fornece uma rapida exposicao da linguagem C — o programador mais experiente talvez queira passar diretamente para o Capitulo 2. O Capitulo 2 examina os tipos de dados internos, varidveis, operadores e expressées. O Capitulo 3 apresenta os comandos de controle do programa. O Capitulo 4 discute matrizes e strings. O Capitulo 5 trabalha com ponteiros. O Capitulo 6 discute funcdes. O Capitulo 7 aborda estruturas, unides 0s tipos definidos pelo usuario. O Capitulo 8 examina as E/S pelo console. O Capitulo 9 aborda as E/S de arquivo e, finalmente, 0 Capitulo 10 discute o pré-processador e faz comentarios. O assunto desta parte (e a maior parte do material deste livro) reflete 0 padrao ANSI para C. No entanto, o padrao original de C, oriundo do UNIX versio 5, também é focalizado, e as diferencas mais importantes sao salientadas. O livro aborda tanto o C ANSI quanto o original como garantia de que vocé encontrara informagées pertinentes ao seu ambiemte de programagao em C. ‘iste Uma Visao Geral de C Se A finalidade deste capitulo é apresentar uma visao geral da linguagem de pro- gramagao C, suas origens, seus usos e sua filosofia. Este capitulo destina-se prin- cipalmente aos novatos em C. Biaso igens de C A linguagem C foi inventada e implementada primeiramente por Dennis Ritchie em um DEC PDP-11 que utilizava o sistema operacional UNIX. C é 0 resultado de um processo de desenvolvimento que comegou com uma linguagem mais antiga, chamada BCPL, que ainda esta em uso, em sua forma original, na Europa. BCPL foi desenvolvida por Martin Richards e influenciou uma linguagem cha- mada B, inventada por Ken Thompson. Na década de 1970, B levou ao desen- volvimento de C. Por muitos anos, de fato, o padrao para C foi a versdo fornecida com 0 sistema operacional UNIX versao 5. Ele é descrito em The C Programming Lan- guage, de Brian Kernighan e Dennis Ritchie (Englewood Cliffs, N.J.: Prentice Hall, 1978). Com a popularidade dos microcomputadores, um grande ntimero de im- plementacoes de C foi criado. Quase que por milagre, os cdigos-fontes aceitos por essas implementacées eram altamente compativeis. (Isto é, um programa escrito com um deles podia normalmente ser compilado com sucesso usando-se um outro.) Porém, por nao existir nenhum padrao, havia discrepancias. Para remediar essa situagao, o ANSI (American National Standards Institute) estabe- leceu, no vero de 1983, um comité para criar um padrao que definiria de uma vez por todas a linguagem C. No momento em que esta obra foi escrita, 0 comité 4 C—Completo e Total Cap. 1 do padrao ANSI estava concluindo 0 processo formal de adogao. Todos os prin- cipais compiladores C j4 implementaram 0 padrao C ANSI. Este livro aborda totalmente o padrao ANSI e enfatiza-o. Ao mesmo tempo, ele contém informa- cOes sobre a antiga versio UNIX de C. Em outras palavras, independentemente do compilador que esteja usando, vocé encontrar assuntos aplicdveis aqui. @ cE uma L nguagem de Meédio Nivel C ¢ freqiientemente chamada de linguagem de médio nivel para computadores. Isso nao significa que C seja menos poderosa, dificil de usar ou menos desen- volvida que uma linguagem de alto nivel como BASIC e Pascal, tampouco implica que C seja similar a linguagem assembly e seus problemas correlatos aos usua- ios. C é tratada como uma linguagem de médio nivel porque combina elementos: de linguagens de alto nivel com a funcionalidade da linguagem assembly. A Ta- bela 1.1 mostra como C se enquadra no espectro das linguagens de computador. Tabela 1.1. A posicéo de C no mundo das linguagens. Nivel mais alto Ada Medula-2 Pascal COBOL FORTRAN BASIC Médio nivel CH c FORTH Macro-assembler Nivel mais baixo Assembler Como uma linguagem de médio nivel, C permite a manipulagao de bits, bytes e enderecos — os elementos basicos com os quais 0 computador funciona. Um cédigo escrito em C é muito portavel. Portabilidade significa que é possivel adaptar um software escrito para um tipo de computador a outro. Por exemplo, se vocé pode facilmente converter um programa escrito para DOS de tal forma a executar sob Windows, entao esse programa é portavel. Todas as linguagens de programagio de alto nivel suportam 0 conceito de tipos de dados. Um tipo de dado define um conjunto de valores que uma varidvel pode armazenar e 0 conjunto de operacdes que pode ser executado com Cap. 1 Uma visto geral de C wo essa varidvel. Tipos de dados comuns sao inteiro, caractere ¢ real. Embora C tenha cinco tipos de dados internos, ela nao ¢ uma linguagem rica em tipos de dados como Pascal e Ada. C permite quase todas conversdes de tipos. Por exem- plo, os tipos caractere e inteiro podem ser livremente misturados na maioria das expressdes. C nao efetua nenhuma verificacao no tempo de execugao, como a validacao dos limites das matrizes. Esses tipos de verificacdes sao de responsa- bilidade do programador. As vers6es originais de C nao realizavam muitos (se é que realizavam algum) testes de compatibilidade entre um parametro de uma funcao e 0 argu- mento usado para chamar a funcao. Por exemplo, na versao original de C, vocé poderia chamar uma funcdo, usando um ponteiro, sem gerar uma mensagem de erro, mesmo que essa fungao tivesse sido definida, na realidade, como recebendo um argumento em ponto flutuante. No entanto, 0 padrao ANSI introduziu o conceito de protdtipos de fungdes, que permite que alguns desses erros em potencial sejam mostrados, conforme a intengao do programador. (Prototipos serao discu- tidos mais tarde no Capitulo 6.) Outro aspecto importante de C é que cle tem apenas 32 palavras-chaves (27 do padrao de fato estabelecido por Kernighan e Ritchie, mais 5 adicionadas pelo comité ANSI de padronizacao), que sao os comandos que compoem a lingua- gem C. As linguagens de alto nivel tipicamente tém varias vezes esse numero de palavras reservadas. Come comparacao, considere que a maioria das versdes de BASIC possuem bem mais de 100 palavras reservadas! @ cE uma Linguagem Estruturada Embora 0 termo linguagem estruturada em blocos nao seja rigorosamente aplicavel aC, ela é normalmente referida simplesmente como linguagem estruturada. C tem muitas semelhancas com outras linguagens estruturadas, como ALGOL, Pascal e Modula-2. KN NOTA: A razio pela qual C nao &, tecnicamente, uma linguagem estruturada em =” locos, é que as linguagens estruturadas em blecos permitem que procedimentos e fungées sejam declarados dentro de procedimentos e fungées. No entanto, como C nio permite a criagto de fungdes dentro de fungoes, niio pode ser chamada formal mente de uma linguagem estruturada em blocos. A caracteristica especial de uma linguagem estruturada é a compartimen- talizagdo do cédigo e dos dados. Trata-se da habilidade de uma linguagem seccionar e esconder do resto do programa todas as informacées nece: rias para se realizar uma tarefa especifica. Uma das maneiras de conseguir 6 ‘C — Complete e Total Cap. 1 essa compartimentalizacao é pelo uso de sub-rotinas que empregam varidveis locais (temporarias). Com 0 uso de variaveis locais é possivel escrever sub-rotinas de forma que os eventos que ocorrem dentro delas ndo causem nenhum efeito inesperado nas outras partes do programa. Essa capacidade permite que seus programas em C compartilhem facilmente secdes de cédigo. Se vocé desenvolve fungdes compartimentalizadas, so precisa saber 0 que uma fungao faz, nao como ela faz. Lembre-se de que 0 uso excessivo de variaveis globais (variaveis conhe- cidas por todo o programa) pode trazer muitos erros, por permitir efeitos cola- terais indesejados. (Qualquer um que ja tenha programado em BASIC esté bem ciente deste problema.) Uma linguagem estruturada permite muitas possibilidades na progra- magao. Ela suporta, diretamente, diversas construgdes de lagos (loops), como while, do-while e for. Em uma linguagem estruturada, 0 uso de goto é proibido ou desencorajado e também a forma comum de controle do programa, (que ocorre em BASIC e FORTRAN, por exemplo). Uma linguagem estruturada permite que voc insira sentengas em qualquer lugar de uma linha e nao exige um conceito rigoroso de campo (como em FORTRAN). A seguir estao alguns exemplos de linguagens estruturadas e nao estru- turadas. Nao estruturadas Estruturadas FORTRAN Pascal BASIC Ada COBOL, CH e Modula-2 Linguagens estruturadas tendem a ser modernas. De fato, a marca de uma linguagem antiga de computador é nao ser estruturada. Hoje, a maioria dos programadores considera as linguagens estruturadas mais faceis de programar e fazer manutencao. O principal componente estrutural de C é a fungdo — a sub-rotina iso- lada de C. Em C, funcées sao os blocos de construgéo em que toda a atividade do programa ocorre. Elas admitem que vocé defina e codifique separadamente as diferentes tarefas de um programa, permitindo, entdo, que seu programa seja modular. Apés uma fungéo ter sido criada, vocé pode esperar que ela trabalhe adequadamente em varias situacOes, sem criar efeitos inesperados em outras par- tes do programa. O fato de vocé poder criar fungGes isoladas é extremamente importante em projetos maiores nos quais um cddigo de um programador nao deve afetar acidentalmente o de outro. Cap. 1 ‘Uma visto gerat de C 7 Uma outra maneira de estruturar e compartimentalizar 0 cédigo em C é pelo uso de blocos de cddigo. Um bloco de cédigo é um grupo de comandos de programa conectado logicamente que é tratado como uma unidade. Em C, um bloco de cédigo ¢ criado colocando-se uma seqiiéncia de comandos entre chaves. Neste exemplo, if (x < 10) { printf ("muito baixo, tente novamente\n") ; scanf("%d", &x); } os dois comandos apés 0 if ¢ entre chaves sao executados se x for menor que 10. Esses dois comandos, junto com as chaves, representam um bloco de cédigo. Eles sao uma unidade légica: um dos comandos nao pode ser executado sem que © outro também seja. Atente para o fato de que todo comando em C pode ser um comando simples ou um bloco de comandos. Blocos de cédigo permitem que muitos algoritmos sejam implementados com clareza, elegancia e eficiéncia. Além disso, eles ajudam o programador a conceituar a verdadeira natureza da rotina. @ ce uma Linguagem para Programadores Surpreendentemente, nem todas as linguagens de computador sao para progra- madores. Considere os exemplos classicos de linguagens para nao-programado- res: COBOL e BASIC. COBOL nao foi destinada para facilitar a vida do progra- mador, aumentar a seguranca do cédigo produzido ou a velocidade em que o cédigo pode ser escrito. Ao contrario, COBOL foi concebida, em parte, para per- mitir que nao-programadores leiam e presumivelmente (embora isso seja impro- vavel) entendam o programa. BASIC foi criada essencialmente para permitir que nao-programadores programem um computador para resolver problemas relati- vamente simples. Em contraposicao, C foi criada, influenciada e testada em campo por programadores profissionais. O resultado final é que C dé ao programador o que ele quer: poucas restrig6es, poucas reclamacées, estruturas de bloco, fungdes isoladas e um conjunto compacto de palavras-chave. Usando C, um programador pode conseguir aproximadamente a eficiéncia de cédigo assembly combinada com a estrutura de ALGOL ou Modula-2, Nao é de admirar que C seja trangiii- lamente a linguagem mais popular entre excelentes programadores profissionais. 8 C— Completo e Total Cap. 1 O fato de C freqiientemente ser usada em lugar da linguagem assembly 6 0 fator mais importante para a sua popularidade entre os programadores. A linguagem assembly usa uma representagao simbélica do cédigo binario real que o computador executa diretamente. Cada operacao em linguagem assembly leva a uma tarefa simples a ser executada pelo computador. Embora a linguagem assembly dé aos programadores 0 potencial de realizar tarefas com maxima fle- xibilidade e eficiéncia, é notoriamente dificil de trabalhar quando se esta desen- volvendo ou depurando um programa. Além disso, como assembly nao é uma linguagem estruturada, o programa final tende a ser um cédigo “espaguete” — um emaranhado de jumps, calls e indices. Essa falta de estrutura torna os pro gramas em linguagem assembly dificeis de ler, aperfeicoar e manter. Talvez ma importante: as rotinas em linguagem assembly nao sao portaveis entre maquinas com unidades centrais de processamento (CPUs) diferentes. Inicialmente, C era usada na programacio de sistema. Um programa de istema forma uma porcao do sistema operacional do computador ou de seus utilitarios de suporte. Por exemplo, os programas que seguem sao freqiientemen- te chamados de programas de sistema Sistemas operacionais Interpretadores Editores Programas de planilhas eletronicas Compiladores ™ Gerenciadores de banco de dados Em virtude da sua portabilidade e eficiéncia, a medida que C cresceu em popularidade, muitos programadores comecaram a usé-la para programar todas as tarefas. Por haver compiladores C para quase todos os computadores, é possivel tomar um cédigo escrito para uma maquina, compilé-lo e rodé-lo em outra com pouca ou nenhuma modificacao. Esta portabilidade economiza tempo e dinheiro. Os compiladores C também tendem a produzir um cédigo-objeto muito compacto e rapido — menor e mais rapido que aquele da maioria dos compiladores BASIC, por exemplo. Além disso, os programadores usam C em todos os tipos de trabalho de programacao porque eles gostam de C! Ela oferece a velocidade da linguagem assembly e a extensibilidade de FORTH, mas poucas das restricdes de Pascal ow Modula-2, Cada programador C pode, de acordo com sua propria personalidade, criar e manter uma biblioteca tinica de fungdes customizadas, para ser usada em muitos programas diferentes. Por admitir — na verdade encorajar — a compi- lacao separada, C permite que os programadores gerenciem facilmente grandes projetos com minima duplicacao de esforco. Cap. 1 Uma visiio geral de C 9 @ Compiladores Versus Interpretadores Os termos compiladores e interpretadores referem-se & maneira como um programa é executado. Existem dois métodos gerais pelos quais um programa pode ser executado. Em teoria, qualquer linguagem de programagio pode ser compilada ou interpretada, mas algumas linguagens geralmente sao executadas de uma ma- neira ou de outra. Por exemplo, BASIC é normalmente interpretada e C, compi- lada (especialmente no auxilio 4 depuragao ou em plataformas experimentais como a desenvolvida na Parte 5). A maneira pela qual um programa é executado nao é definida pela linguagem em que ele é escrito. Interpretadores e compiladores sao simplesmente programas sofisticados que operam sobre o cédigo-fonte do seu programa. Como a diferenca entre um compilador e um interpretador pode nao ser Clara para todos os leitores, a breve descricao seguinte esclareceré o assunto. Um interpretador lé 0 cédigo-fonte do seu programa uma linha por vez, executando a instrugdo especifica contida nessa linha. Um compilador lé pro- grama inteiro e converte-o em um cédigo-objeto, que é uma tradugio do cédigo- fonte do programa em uma forma que 0 computador possa executar diretamente. cédigo-objeto é também conhecido como cédigo bindrio ou cédigo de maquina Uma vez que o programa tenha sido compilado, uma linha do cédigo-fonte, mesmo alterada, nao é mais importante na execucao do seu programa. Quando um interpretador 6 usado, deve estar presente toda vez que vocé executar o seu programa. Por exemplo, em BASIC vocé precisa primeiro executar © interpretador, carregar seu programa e digitar RUN cada vez que quiser usé-lo. O interpretador BASIC examina seu programa uma linha por vez para correcao e entSo executa-o. Esse processo lento ocorre cada vez que o pro- grama for executado. Um compilador, ao contrario, converte seu programa em um cédigo-objeto que pode ser executado diretamente por seu computador. Como © compilador traduz seu programa de uma s6 vez, tudo 0 que vocé precisa fazer 6 executar seu programa diretamente, geralmente apenas digitando seu nome. Assim, o tempo de compilacao sé é gasto uma vez, enquanto o cédigo interpre- tado incorre neste trabalho adicional cada vez que o programa executa. I A Forma de um Programa em C A Tabela 1.2 lista as 32 palavras-chave (ou palavras reservadas) que, combinadas com a sintaxe formal de C, formam a linguagem de programagio C. Destas, 27 foram definidas pela versdo original de C. As cinco restantes foram adicionadas pelo comité ANSI: enum, const, signed, void e volatile. 10 C— Completo e Total Cap. 1 Tabela 1.2 Uma lista das palavras-chave de C ANSI. auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while Além disso, muitos compiladores C acrescentaram diversas palavras- chave para explorar melhor a organizacio da meméria da familia de processa- dores 8088/8086, que suporta programacado interlinguagens ¢ interrupcoes. Aqui 6 mostrada uma lista das palavras-chave estendidas mais comuns: asm _cs ds es _ss cdecl far huge interrupt near pascal Seu compilador pode também suportar outras extensdes que ajudem a aproveitar melhor seu ambiente especifico. Todas as palavras-chave de C sao miniisculas. Em C, maitisculas e mi- ntisculas sao diferentes: else 6 uma palavra-chave, mas ELSE nao. Uma palavra- chave nao pode ser usada para nenhum outro propésito em um programa em C — ou seja, ela nao pode servir como uma varidvel ou nome de uma funcao. Todo programa em C consiste em uma ou mais fungdes. A tinica funcao que necessariamente precisa estar presente 6a denominada mainQ, que é a pri- meira funcao a ser chamada quando a execugao do programa comeca. Em um cédigo de C bem escrito, main() contém, em esséncia, um esboco do que o pro- grama faz. O esboco é composto de chamadas de funcdes. Embora main) nao seja tecnicamente parte da linguagem C, trate-a como se fosse. Nao tente usar main() como nome de uma variavel porque provavelmente confundira 0 com- pilador. A forma geral de um programa em C é ilustrada na Figura 1.1, onde £10 até £NQ representam fungoes definidas pelo usuario. Cop. 1 Uma bisito geval de C n declaragées globais tipo devolvide main(1i ( seqiiéncia de comandos } ta de parametros) tipo devolvido f1(lista de parametros) { seqgilénci } de comandos tipo devolvido £2(lista de pardmetros) { seqiiéncia de comandos } tipo devolvido £NW(lista de parametros) { Figura 1.1 A forma geral de um programa em C. Hf A Biblioteca e a Linkedigao Tecnicamente falando, é possivel criar um programa ttil e funcional que consista apenas nos comandos realmente criados pelo programador. Porém, isso ¢ muito raro porque C, dentro da atual definigao da linguagem, nao oferece nenhum método de executar operagies de entrada /saida (E/S). Como resultado, a maioria dos pro- gyamas inclui chamadas a varias fungdes contidas na biblioteca C padrao. Todo compilador C vem com uma biblioteca C padrao de fungées que realizam as tarefas necessérias mais comuns. O padrao C ANSI especifica 0 con- junto minimo de fungdes que estard contido na biblioteca. No entanto, seu com- pilador provavelmente conter4 muitas outras funcées. Por exemplo, 0 padrao C ANSI nao define nenhuma funcao grafica, mas seu compilador provavelmente inclui alguma. Em algumas implementacées de C, a biblioteca aparece em um grande arquivo; em outras, ela esta contida em muitos arquivos menores, uma organi- zagdo que aumenta a eficiéncia e a praticidade. Porém, para simplificar, este livro usa a forma singular em referénca a biblioteca. 2 C= Completo e Total Cap. Os implementadores do seu compilador C jf escreveram a maioria das fungées de propésito geral que voce usara. Quando chama uma fungao que nao faz parte do programa que vocé escreveu, o compilador C “memoriza” seu nome. Mais tarde, o linkeditor (linker) combina 0 cédigo que vocé escreveu com 0 c6- digo-objeto ja encontrado na biblioteca padrao. Esse processo é chamado de fin- kedicao. Alguns compiladores C tém seu proprio linkeditor, enquanto outros usam © linkeditor padrao fornecido pelo seu sistema operacional As funges guardadas na biblioteca estdo em formato relocdvel. Isso nifica que os enderecos de memoria das varias instrugdes em cédigo de maquina nao estao absolutamente definidos — apenas informacoes relativas s40 guarda- das. Quando seu programa é linkeditado com as fungées da biblioteca padrao, esses enderecos relativos sao utilizados para criar os enderecos realmente usados. Hi diversos manuais ¢ livros técnicos que explicam esse processo com mais de- talhes. Contudo, vocé nao precisa de nenhuma informagio adicional sobre 0 pro- cesso real de relocagio para programar em C. Muitas das fungdes de que vocé precisaré ao escrever seus programas estao na biblioteca padrao. Elas agem como blocos basicos que vocé combina. Se escreve uma funcao que usaré muitas vezes, vocé também pode colocé-la em uma biblioteca. Alguns compiladores permitem que vocé coloque sua funcdo na biblioteca padrao; outros exigem a criagdo de uma biblioteca adicional. De qual- quer forma, o cédigo estar la para ser usado repetidamente. Lembre-se de que 0 padrao ANSI apenas especifica uma biblioteca pa- drao minima. A maioria dos compiladores fornece bibliotecas que contém muito mais fungdes que aquelas definidas pelo ANSI. Além disso, algumas funcdes encontradas na versao original de C para UNIX nao sao definidas pelo padrao ANSI por serem redundantes. Este livro aborda todas as funcées definidas pelo ANSI como também as mais importantes e largamente usadas pelo padrao C UNIX antigo. Ele também examina diversas fungdes muito usadas, mas que nado sao definidas pelo ANSI nem pelo antigo padrao UNIX. (Fungées nao-ANSI serao indicadas para evitar confusac.) @ Compilagao Separada Muitos programas curtos de C estio completamente contidos em um arquivo- fonte. Contudo, quando o tamanho de um programa cresce, também aumenta seu tempo de compilacao (e tempos de compilacao longos contribuem para pa- ciéncias curtas!). Logo, C permite que um programa seja contido em muitos ar- quivos e que cada arquivo seja compilado separadamente. Uma vez que todos 08 arquivos estejam compilados, eles sao linkeditados com qualquer rotina de Cap. 1 Uma visio geral de C 3 biblioteca, para formar um cédigo-objeto completo. A vantagem da compilacdo separada que, se houver uma mudanca no cédigo de um arquivo, nao sera necessaria a recompilagdo do programa todo. Em tudo, menos nos projetos mais simples, isso economiza um tempo considerdvel. (Estratégias de compilagao se- parada sao abordadas em detalhes na Parte 4.) a Compilando um Programa em C Compilar um programa em C consiste nestes trés passos: 1. Criar o programa 2. Compilar o programa 3. Linkeditar o programa com as fungées necessarias da biblioteca Alguns compiladores fornecem ambientes de programagao integrados que incluem um editor. Com outros, € necessario usar um editor separado para criar seu programa. Os compiladores s6 aceitam a entrada de arquivos de texto padrao. Por exemplo, seu compilador nao aceitaré arquivos criados por certos processadores de textos porque eles tém cédigos de controle e caracteres nao- imprimiveis. O método exato que vocé utiliza para compilar um programa depende do compilador que esta em uso. Além disso, a linkedicao varia muito entre os compiladores e os ambientes. Consulte seu manual do usudrio para detalhes. Ho Mapa de Meméria de C Um programa C compilado cria e usa quatro regides, logicamente distintas na mem6ria, que possuem fungdes especificas. A primeira regido é a memoria que contém 0 cédigo do seu programa. A segunda é aquela onde as variaveis globais sao armazenadas. As duas regides restantes sao a pilha eo “heap”. A pilha tem diversos usos durante a execugao de seu programa. Ela possui 0 endereco de retorno das chamadas de fungao, argumentos para funcGes e varidveis locais, Ela também guarda o estado atual da CPU. O heap é uma regido de meméria livre que seu programa pode usar, via fungdes de alocagdo dinamica de C, em apli- cagdes como listas encadeadas e arvores. A disposicao exata de seu programa pode variar de compilador para compilador e de ambiente para ambiente. Por exemplo, a maioria dos compila- dores para a familia de processadores 8086 tem seis maneiras diferentes de or- 4 C~Completo e Total Cap. 1 ganizar a meméria em razao da arquitetura segmentada de meméria do 8086, Os modelos de meméria da familia de processadores 8086 sao discutidos mais adiante neste livro. Embora a disposicao fisica exata de cada uma das quatro regides possa diferir entre tipos de CPU e implementacées de C, 0 diagrama da Figura 1.2 mostra conceitualmente como seu programa aparece na meméria. Pilha | | Heap Varidveis Globais ‘Cédigo do Programa Figure 1.2, Um mapa conceituat de meméria de um programa em C. WC Versus C++ Antes de concluir este capitulo, 6 necessério dizer algumas palavras sobre C++. Algumas vezes os novatos confundem o que é C++ e como difere de C. Para ser breve, C++ é uma versao estendida e melhorada de C que é projetada para su- portar programagao orientada a objetos (OOP, do inglés Object Oriented Pro- gramming). C++ contém e suporta toda a linguagem C e mais um conjunto de extensdes orientadas a objetos. (Ou seja, C++ é um superconjunto de C.) Como C++ 6 construida sobre os fundamentos de C, vocé nao pode programar em C++ se nao entender C. Portanto, virtualmente todo o material apresentado neste livro aplica-se também a C++. 4 NOTA: Para uma descricdo completa da linguagem C++ veja 0 livro, C++ — The void £(voidl; void main(void) ors C — Comapleto ¢ Total } Cap. 2 int i; for(i ; isl0; ies) £0; void f (void) print£("*d ", 3); j++; /* esta linha n&o tem nenhum efeito */ ) Pardmetros Formais Se uma funcdo usa argumentos, ela deve declarar varidveis que receberdo os valores dos argumentos. Essas varidveis sao denominadas pardmetros formais da funcao. Elas se comportam como qualquer outra varidvel local dentro da funcao. Como é mostrado no fragmento de programa seguinte, suas declaragdes ocorrem depois do nome da funcao e dentro dos parénteses: /* Retorna 1 se c é parte da string s; 0 se ndo € o caso */ i (char *s, char c) while(*s) if(*s else s++; ¢) return return 0; ) A funcao is_in@ tem dois parametros: s e ¢. Essa fungdo devolve 1 se 0 caractere especificado em ¢ estiver contido na string 5; 0 se nao estiver. Vocé deve informar & C que tipo de variaveis so os parametros formais, declarando-os como mostrado acima. Uma vez feito isso, elas podem ser usadas dentro da fungao como varidveis locais normais. Tenha sempre em mente que, como variaveis locais, elas também sao dinamicas e sao destruidas na saida da fungao. Vocé deve ter certeza de que os parametros formais que estado declarados sao do mesmo tipo dos argumentos que vocé utiliza para chamar a fungao. Cap.2 Expressbes ean C : 25 Se ha uma discordancia de tipos, resultados inesperados podem ocorrer. Ao con- trario de muitas outras linguagens, C geralmente faré alguma coisa, inclusive em circunstancias nao usuais, mesmo que nao seja o que vocé quer. Ha poucos erros em tempo de execucdo e nenhuma verificagao de limites. Como programador, voce deve ter certeza de que eros de incongruéncia de tipo nao ocorrerao. Embora a linguagem C forneca os protétipos de funcdes, que podem ser usados para ajudar a verificar se os argumentos usados para chamar a fungao sao compativeis com os parametros, ainda podem ocorrer problemas. (Isto é, os protétipos de funcdo nao eliminam inteiramente incongruéncias de tipo de pa- rametro). Além disso, vocé deve incluir explicitamente protétipos de fungdes em seu programa para receber esse beneficio extra. (O uso de prototipos de fungées 6 discutido em profundidade no Capitulo 6.) Analogamente as variveis locais, vocé pode fazer atribuigées a parame- tros formais de uma funcao ou usé-los em qualquer expressdo permitida em C. Embora essas varidveis recebam o valor dos argumentos passados para a funcao, elas podem ser usadas como qualquer outra varidvel local. Varidveis Globais Ao contrario das varidveis locais, as varidueis globnis so reconhecidas pelo pro- grama inteiro e podem ser usadas por qualquer pedaco de cédigo. Além disso, elas guardam seus valores durante toda a execugao do programa. Vocé cria va- ridveis globais declarando-as fora de qualquer funcdo. Elas podem ser acessadas por qualquer expressao independentemente de qual bloco de cédigo contém a expresso. No programa seguinte, a varidvel count foi declarada fora de todas as fungdes. Embora sua declaracao ocorra antes da fungéo main0, ela poderia ter sido colocada em qualquer lugar anterior ao seu primeiro uso, desde que nao estivesse em uma fungao. No entanto, é melhor declarar varidveis globais no inicio do programa. #include int count; /* count é global */ void funcl (void) ; void func2 (void) ; void main(void) count = 100; funcl(}; %6 C— Completo e Total Cap. 2 void funcl (void) { int temp; temp = ccunt; func2 (}; printf (‘count é %d", count); /* imprimiré 100 */ void func2 (void) { int count; for (count=1; count<10; count++) putchar('.'); Olhe atentamente para esse programa. Observe que, apesar de nem main(Q) nem funcl() terem declarado a varidvel count, ambas podem usé-la. A fungéo func2(), porém, declarou uma varidvel local chamada count. Quando fune2() referencia count, ela referencia apenas sua varidvel local, nao a varidvel global. Se uma variavel global e uma variavel local possuem 0 mesmo nome, todas as referéncias ao nome da varidvel dentro do bloco onde a variavel local foi declarada dizem respeito a varidvel local e nao tém efeito algum sobre a varidvel global. Pode ser conveniente, mas esquecer-se disso poder fazer com que seu programa seja exe- cutado estranhamente, embora parega correto. O armazenamento de variaveis globais encontra-se em uma regiao fixa da meméria, separada para esse propésito pelo compilador C. Varidveis globais so titeis quando 0 mesmo dado é usado em muitas funcdes em seu programa. No entanto, vocé deve evitar usar varidveis globais desnecessarias. Elas ocupam meméria durante todo o tempo em que seu programa esta executando, nao ape- nas quando sao necessérias. Além disso, usar uma varidvel global onde uma varidvel local poderia ser usada torna uma fungao menos geral, porque ela conta com alguma coisa que deve ser definida fora dela. Finalmente, usar um grande muimero de varidveis globais pode levar a erros no programa por causa de des conhecidos — e indesejaveis — efeitos colaterais. Isso pode ser evidenciado no BASIC padrao, em que todas as varidveis sao globais. Um problema maior no desenvolvimento de grandes projetos é a mudanca acidental do valor de uma varidvel porque ela é usada em algum outro lugar do programa. Isso pode acon- tecer em C se vocé usar variaveis globais demais em seus programas. Cap.2 Expressdes em C a Uma das principais razdes para uma linguagem estruturada é a com partimentalizago ou separacdo de cédigo e dados. Em C, esse isolamento é con- seguido pelo uso de variaveis locais e fung6es. Por exemplo, a Figura 2.1 mostra duas maneiras de escrever mul{) — uma fungao simples que calcula 0 produto de dois inteiros. Ambas as fungdes retornam 0 produto das varidveis x e y. Contudo, a versao generalizada, ou parametrizada, pode ser usada para retornar o produto de quaisquer dois inteiros, enquanto a versao especifica s6 pode ser usada para encontrar 0 produto das varidveis globais x e y. Geral Especi int x, y; mul(int x, int y) mul( void ) { { return(xty); return (x*y); J ! Figura 2.1. Duas formas de escrever mul0. — Modificadores de Tipo de Acesso O C introduziu dois novos modificadores (também chamados quantificadores) que controlam a maneira como as varidveis podem ser acessadas ou modificadas. Esses modificadores sao const e volatile. Devem preceder os modificadores de tipo e os nomes que eles modificam. const Variaveis do tipo const néo podem ser modificadas por seu programa. (Uma varidvel const pode, entretanto, receber um valor inicial.) O compilador pode colocar varidveis desse tipo em meméria de apenas leitura (ROM). Por exemplo: W const int a= cria uma varidvel inteira chamada a, com um valor inicial 10, que seu programa no pode modificar. Voce pode, porém, usar a varidvel a em outros tipos de expressdes. Uma varidvel const recebe seu valor de uma inicializagao explicita ou por algum recurso dependente do hardware. 28 C-— Completo e Total Cap. 2 O qualificador const pode ser usado para proteger os objetos apontados pelos argumentos de uma fungdo de serem modificados por esta funcdo. Isto 6, quando um ponteiro é passado para uma fungao, esta funcao pode modificar a varidvel real apontada pelo ponteiro. Entretanto, se 0 ponteiro é especificado como const na declaracio dos parametros, 0 cddigo da fung3o nao sera capaz de modificar 0 que ele aponta. Por exemplo, a fungao sp_to_dash0), no programa seguinte, imprime um trago para cada espaco do seu argumento string. Ou me- Thor, a string “isso é um teste” sera impressa “isso-é-um-teste”. O uso de const na declaragao do parametro assegura que 0 cédigo dentro da fungao nao possa modificar 6 objeto apontado pelo parametro. #include void sp_to_dashiconst char *str); void main(void) sp_to_dash("isso 6 um teste"); void sp_to_dash(const char *str) ( while(*str) { if(*str ' 1) printt("’e", '-"); else printf("%c", *str); str++; Se voce escrevesse sp_to_dash() de forma que a string fosse modificada, ela nao seria compilada. Por exemplo, se voce tivesse codificado sp_to_dash() como se- gue, obteria um erro: /* isso esta errado */ void sp_to_dash(const char *str) { while (*str) if("stre=' / ) +str printé("tc", *str); stre+; /* n&o faga isto */ Cap. 2 Expressoes em C 29 Muitas fungées da biblioteca C padrao usam const em suas declaragées de pa- rametros. Por exemplo, a funcao strlen() tem este prototipo: size_t strlen(const char *str); Especificar str como const assegura que strlen() nao modificard a string apontada por str. Em geral, quando uma fungao da biblioteca padrao nao tem necessidade de modificar um objeto apontado por um argumento, ele 6 declarado como const Vocé também pode usar const para verificar se seu programa nao mo- difica uma varidvel. Lembre-se de que uma varidvel do tipo const pode ser mo- dificada por algo externo ao seu programa. Por exemplo, um dispositive de hardware pode ajustar seu valor. Porém, declarando uma varidvel como const, vocé pode provar que qualquer alteracao nesta varidvel ocorre devido a eventos externos. volatile O modificador volatile é usado para informar ao compilador que o valor de uma varidvel pode ser alterado de maneira nao explicitamente especificada pelo pro- grama. Por exemplo, um endereco de uma varidvel global pode ser passado para a rotina de relégio do sistema operacional e usado para guardar 0 tempo real do sistema. Nessa situacao, o contetido da variavel € alterado sem nenhum co- mando de atribuigéo explicito no programa. Isso é importante porque muitos compiladores C automaticamente otimizam certas expressdes, assumindo que o contetido de uma varidvel é imutavel, se sua referéncia nao aparecer no lado esquerdo da expressao; logo, ela pode nao ser reexaminada toda vez que for referenciada. Alem disso, alguns compiladores mudam a ordem de avaliacao de uma expressao durante o processo de compilacao. O modificador volatile previne a ocorréncia dessas mudancas. E possivel usar const e volatile juntos. Por exemplo, se 0x30 6 assumido como sendo 0 valor de uma porta que é mudado apenas por condigées externas, a declaragéo seguinte é precisamente 0 que vocé quer para prevenir qualquer possibilidade de efeitos colaterais acidentais, const volatile unsigned char *port = 0x30; Hf Especificadores de Tipo de Classe de Armazenamento Hé quatro especificadores de classe de armazenamento suportados por C. 30 C— Completo ¢ Total Cap.2 extern static register auto Esses especificadores sao usados para informar ao compilador como a varidvel deve ser armazenada. O especificador de armazenamento precede 0 res- to da declaracao da varidvel. Sua forma geral &: especificador_de_armazenamento tipo nome_da_varidvel; extern Uma vez que C permite que médulos de um programa grande sejam compilados separadamente para entao serem linkeditados juntos, uma forma de aumentar a velocidade de compilacao e ajudar no gerenciamento de grandes projetos, deve haver alguma maneira de dizer a todos os arquivos sobre as varidveis globais solicitadas pelo programa. Lembre-se de que vocé pode declarar uma varidvel global apenas uma vez. Se vocé tentar declarar duas varidveis com 0 mesmo nome dentro do mesmo arquivo, seu compilador C podera imprimir uma men- sagem de erro como “nome de varidvel duplicado” ou poderé simplesmente escolher uma varidvel. © mesmo problema ocorre se vocé simplesmente declara todas as varidveis globais necessarias ao seu programa em cada arquivo. Embora © compilador nao emita nenhuma mensagem de erro em tempo de compilacio, vocé estaria realmente tentando criar duas (ou mais) c6pias de cada varidvel. O transtorno comecaria quando vocé tentasse linkeditar seus médulos. O linke- ditor mostraria a mensagem de erro como “rétulo duplicado” porque ele nao saberia que varidvel usar. A solucdo seria declarar todas as suas varidveis globais em um arquivo e usar declaragGes extern nos outros, como na Figura 2.2. No arquivo 2, a lista de varidveis globais foi copiada do arquivo 1 e especificador extern foi adicionado as declaracées. O especificador extern diz ao compilador que os tipos e nomes de varidvel que o seguem foram declarados em outro lugar. Em outras palavras, extern deixa o compilador saber 0 que os tipes e nomes sao para essas varidveis globais sem realmente criar armazena- mento para elas novamente. Quando o linkeditor unir os dois médulos, todas as referéncias a varidveis externas serao resolvidas. Quando utiliza uma varidvel global dentro de uma fungao que est4 no mesmo arquivo que a declaracao da varidvel global, vocé pode usar extern, como mostrado aqui: Cap. 2 Expressées em C 31 Arquivo 1 Arquivo 2 int x, ys extern int x, y; char ch; extern char ch; main(void) func22(void) ( ( x= y/10; 1 1 func230) { funcl() y= 10; { 1 Figura 2.2. Uso de varidveis globais em médulos compilados separadamente. int first, last /* declaragao global de first e last */ void main(void) { extern int first; /* uso opcional da declaragao extern */ Embora as declaragées de variaveis extern possam ocorrer dentro do mesmo arquivo da declaracdo global, elas nao sio necessdrias. Se 0 compilador C en- contra uma varidvel que nao foi declarada, ele verifica se ela tem 0 mesmo nome de alguma variavel global. Se tiver, o compilador assumira que a variavel global esta sendo referenciada. Variaveis static Dentro de sua prépria funcgao ou arquivo, varidveis static sao varidveis perma- nentes. Ao contrario das variaveis giobais, elas nao sao reconhecidas fora de sua funcao ou arquivo, mas mantém seus valores entre chamadas. Essa caracteristica torna-as titeis quando vocé escreve funcées generalizadas e funcées de biblioteca que podem ser usadas por outros programadores. O especificador static tem efeitos diferentes em varidveis locais e em varidveis globais. 32 © — Completo ¢ Total Cap. 2 Variaveis Locais static Quando 0 modificador static 6 aplicado a uma varidvel local, 0 compilador cria armazenamento permanente para ela quase da mesma forma como cria armaze- namento para uma variavel global. A diferenca fundamental entre uma varidvel local static e uma varidvel global é que a variavel local static é reconhecida apenas no bloco em que estd declarada. Em termos simples, uma varidvel local static é uma varidvel local que retém seu valor entre chamadas de fungao Varidveis locais static sao muito importantes na criagdo de fungodes iso- ladas, porque diversos tipos de rotinas devem preservar um valor entre as cha- madas. Se varidveis static nao fossem permitidas, varidveis globais teriam de ser usadas, abrindo brechas para possiveis efeitos colaterais. Um exemplo de fungao que requer uma varidvel local static € um gerador de série de ntimeros que pro- duz um novo numero baseado no anterior. Seria possivel declarar uma varidvel global para reter esse valor. Porém, cada vez que a funcao € usada, vocé deve lembrar-se de declarar essa varidvel global e garantir que ela nao conflite com nenhuma outra varidvel global ja declarada. Além disso, usar uma variavel global tornaria essa funcao dificil de ser colocada em uma biblioteca de fungées. A melhor solugao € declarar a varidvel que retém o ntimero gerado como static, como neste fragmento de programa series (void) { static int series_num; series_num = series_num+23; return (series_num); ) Nesse exemplo, a varidvel series_num permanece existindo entre as chamadas da funcao em vez da criagio e exclusio que as variéveis locais normais fariam. Isso significa que cada chamada a series() pode produzir um novo membro da série, baseado no ntimero precedente, sem declarar essa varidvel globalmente. Vocé pode dar a varidvel local static um valor de inicializacao. Esse valor é atribufdo apenas uma vez — e nao toda vez que © bloco de cédigo é inserido, de forma andloga as varidveis locais normais. Por exemplo, essa versio de series() inicializa series_num com 100: series (void) i static int series_num = 100; Cap. 2 Expressbesiem C 33 series_num = series_num+23; return series_num; ) Da forma como a fungao se acha agora, a série sempre comega com 0 valor 123. Enquanto isso é aceitavel para algumas aplicagdes, a maioria dos geradores de séries permite ao usuario especificar 0 ponto inicial. Uma maneira de dar a series_num um valor especificado pelo usuario é tornar series_num uma varidvel global e, em seguida, ajustar seu valor de acordo com o especificado. Porém, series_num foi feita static justamente para nao ser definida como global. Isso leva ao segundo uso de static. veis Globais static Aplicar 0 especificador static a uma varidvel global informa ao compilador para criar uma variavel global que é reconhecida apenas no arquivo no qual a mesma foi declarada. Isso significa que, muito embora a varidvel seja global, rotinas em outros arquivos nao podem reconhecé-la ou alterar seu conteido diretamente; assim, nao esta sujeita a efeitos colaterais. Entretanto, para as poucas situacdes onde uma varidvel local static nao possa fazer o trabalho, vocé pode criar um pequeno arquivo que contenha apenas as funcdes que precisam da varidve! glo- bal static e compilar separadamente esse arquivo sem medo de efeitos colaterais. Para ilustrar uma varidvel global static, o exemplo de gerador de série da seco anterior foi recodificado de forma que um valor somente inicialize a série por meio de uma chamada a uma segunda funcao denominada series_start(. O arquivo inteiro, que contém series, series_start() e series_num é mostrado aqui: /* Isso deve estar em um Unico arquivo - preferencialmente isolado. */ static int series_num; void series_start (int seed); int series (void); series (void) t series_num = series_num+23; return series_num; /* inicializa series_num */ void series_start(int seed) { series_num = seed; } Ea C —Completo ¢ Total Cap. 2 Para inicializar o gerador de série, deve-se chamar series_start() com algum valor inteiro conhecido. Depois disso, chamadas a series() geram os proximos elemen- tos da série. Revisando: Os nomes das varidveis locais static sio reconhecidos apenas na fungao ou bloco de cédigo em que elas so declaradas. Os nomes das varid- veis globais static sao reconhecidos apenas no arquivo em que elas residem. Isso significa que, se vocé colocar as fungGes series() e series_start() em uma biblio- teca, poderd usar as fungdes, mas nao podera referenciar a varidvel series_num, que esta escondida do resto do cédigo do seu programa. De fato, vocé pode, inclusive, declarar e usar outra varidvel chamada series_num em seu programa (em outro arquivo, é claro). Em esséncia, 0 modificador static permite varidveis que sao reconhecidas pelas fungdes que precisam delas, sem confundir outras funcdes. As variaveis static admitem que vocé, 0 programador, esconda porcgées de seu programa das outras partes. Isso pode ser uma vantagem imensa quando se tenta gerenciar um programa muito grande e complexo. O especificador de classe de armazenamento static deixa vocé criar fungées gerais que podem ir para bibliotecas que serao utilizadas posteriormente. Varidveis register O especificador de armazenamento register tradicionalmente era aplicado apenas a variaveis dos tipos int e char. Contudo, o padrao C ANSI ampliou sua definigao de forma que ele pode ser aplicado a qualquer varidvel. Originalmente, o especificador register solicitava ao compilador C que armazenasse o valor das varidveis declaradas com esse especificador num regis- trador da CPU em vez da meméria, onde as varidveis normais s4o armazenadas. Isso significa que operacdes nas varidveis register poderiam ocorrer muito mais rapidamente que nas varidveis armazenadas na meméria, pois o valor dessas varidveis era realmente conservado na CPU e nao era necessario acesso & me- moria para determinar ou modificar seus valores. Hoje, uma vez que agora 0 padrao C ANSI permite que vocé modifique qualquer tipo de variavel com register, ele alterou a definicao do que register faz. O padrao C ANSI simplesmente determina que “o acesso ao objeto é 0 mais, rapido possivel”. Na pratica, caracteres e inteiros sao colocados nos registradores da CPU. Objetos maiores, como matrizes, obviamente nao podem ser armazena- dos em um registrador, mas eles ainda podem receber um tratamento diferen- ciado. Dependendo da implementagao do compilador C e de seu ambiente opera- cional, varidveis register podem ser manipuladas de quaisquer formas conside- Cap. 2 Expressbes em C 35 radas cabiveis pelo implementador do compilador. O padrao C ANSI também permite que o compilador ignore o especificador register e trate as varidvei modificadas por ele como se nao fossem, mas isso raramente ocorre na pratica. Vocé s6 pode aplicar o especificador register a varidveis locais e a pa- rametros formais em uma funcao. Assim, varidveis globais register nao sao per- mitidas. Aqui esté um exemplo de como declarar uma variavel register do tipo int e usa-la para controlar um lago. Essa funcao calcula o resultado de M®° para inteiros: int_pwr(register int m, register int e) ( ister int temp; temp = for(; e; e--) temp = temp * mj return temp; ) Neste exemplo, tanto e como m e temp sao declaradas como varidveis register porque sao usadas dentro do laco. O fato de varidveis register serem otimizadas para velocidade torna-as ideais ao controle de laco. Geralmente, variaveis register sao usadas quando mais apropriadas, isto é, em lugares onde so feitas muitas referéncias a uma mesma variavel. Isso é importante porque vocé pode declarar qualquer ntimero de variaveis como sendo do tipo register, mas nem todas re- cebem a mesma otimizagao de velocidade. O ntimero de variaveis em registradores dentro de qualquer bloco de c6digo é determinado pelo ambiente e pela implementagao especifica de C. Vocé nao deve preocupar-se em declarar muitas varidveis register porque o compila- dor C automaticamente transforma varidveis register em varidveis comuns quan- do o limite for alcancado. (Isso é feito para assegurar a portabilidade do codigo em C por meio de uma ampla linha de processadores.) Por todo este livro, muitas varidveis de controle de laco serao do tipo register. Normalmente, pelo menos duas variaveis register do tipo char ou int podem de fato ser colocadas em registradores da CPU. Como os ambientes va- riam enormemente, consulte o manual do usuario do seu compilador para de- terminar se vocé pode aplicar quaisquer outros tipos de opgdes de otimizacoes. Como uma varidvel register pode ser armazenada em um registrador da CPU, varidveis register ndo podem ter enderecos. Isto é, vocé nao pode en- contrar 0 endereco de uma varidvel register usando 0 operador & (discutido mais adiante neste capitulo). 36 C— Completo Total Cap. 2 Embora 0 padrao C ANSI tenha expandido a descrigdo de register, na pratica ele geralmente sé tem um efeito significativo com os tipos inteito e ca- ractere. Logo, vocé provavelmente nao deve contar com aumentos substanciais da velocidade para os outros tipos de varidveis @ Inicializag&o de Variaveis Vocé pode dar A maioria das variéveis em C um valor, no mesmo momento em que elas so declaradas, colocando um sinal de igual e uma constante apés 0 nome da varivel. A forma geral de uma inicializacao é tipo nome_da_varivel = constante; Alguns exemplos sao char ch = ‘a’; int first O; float balance = 123.23; Variaveis globais ¢ varidveis locais static sio inicializadas apenas no comeco do programa. Varidveis locais (incluindo variaveis locais static) sao inicializadas cada vez. que o bloco no qual estao declaradas for inserido. Variaveis locais e register que nao sao inicializadas possuem valores desconhecidos antes de ser efetuada a primeira atribuicao a elas. Varidveis globais nao inicializadas e variaveis locais estaticas sao inicializadas com zero. I constantes Em C, constantes referem-se a valores fixos que 0 programa nao pode alterar. Constantes em C podem ser de qualquer um dos cinco tipos de dados basicos. A maneira come cada constante é representada depende do seu tipo. Constantes de caractere sao envolvidas por aspas simples ('). Por exemplo, ‘a’ e “%’ so constantes tipo caractere. O padrao ANSI também define caracteres multi bytes (usados principalmente em ambientes de \ingua estrangeira). Constantes inteiras so especificadas como mimeros sem componentes fraciondrios. Por exemplo, 10 e -100 so constantes inteiras. Constantes em ponto flutuante requerem o ponto decimal seguido pela parte fracionaria do mimero. Por exemplo, 11.123 é uma constante em ponto flutuante. C também permite que vocé use notagao cientifica para ntimeros em ponto flutuante. Cap. 2 Expresses em C 37 Existem dois tipos de ponto flutuante: float e double. Ha também diversas variacbes dos tipos basicos que vocé pode gerar usando os modificadores de tipo. Por padrao, 0 compilador C encaixa uma constante numérica no menor tipo de dado compativel que pode conté-lo. Assim, 10 é um int, por padrao, mas 60,000 é unsigned e 100.000 é long. Muito embora o valor 10 possa caber em um tipo ca- ractere, 0 compilador nao atravessard os limites do tipo. A tinica excecdo para a regra do menor tipo so constantes em ponto flutuante, assumidas como doubles. Na maioria dos programas que vocé escrevera, os padres do compila- dor sao adequados. Porém, vocé pode especificar precisamente o tipo da cons- tante numérica que deseja por meio da utilizagao de um sufixo. Para tipos em ponto flutuante, se vocé colocar um F apés o niimero, ele sera tratado como float. Se vocé colocar um L, ele se tornard um long double, Para tipos inteiros, 0 sufixo U representa unsigned e 0 L representa long. Aqui estdo alguns exemplo: Tipo de dado fxemplos de constantes int 1 123 21000 -234 long int 35000L, -34L, short int 10 -12 90 unsigned int 10000U 987U 40000 float 123.23F 4.34e-3F double 123.23 12312333. -0.9876324 long double 1001.2L Constantes Hexadecimais e Octais As vezes é mais facil usar um sistema numérico na base 8 ou 16 em lugar de 10 (nosso sistema decimal padrao). O sistema numérico na base 8 é chamado octal e utiliza os digitos de 0 a 7. Em octal, o ntimero 10 é 0 mesmo que 8 em decimal. O sistema numérico na base 16 6 chamado hexadecimal e utiliza os digitos de 0 a9 mais as letras de A a F, que representam 10, 11, 12, 13, 14 e 15, respectiva- mente. Por exemplo, 0 mimero hexadecimal 10 é 16 em decimal. Em virtude de esses ntimeros serem usados freqiientemente, C permite especificar constantes inteiras em hexadecimal ou octal em lugar de decimal. Uma constante hexade- cimal deve consistir em um 0x seguido por uma constante na forma hexadecimal. Uma constante octa] comega com 0. Aqui estéo alguns exemplos: int hex = 0x80; /* 128 em decimal */ int oct = 012; 7* 10 em decimal */ 38 C~ Completo e Total Cap. 2 Constantes String C suporta outro tipo de constante: a string. Uma string é um conjunto de carac- teres colocado entre aspas duplas. Por exemplo, “isso é um teste” é uma string. Vocé viu exemplos de strings em alguns dos comandos printf() dos programas de exemplo. Embora C permita que vocé defina constantes string, ela nde possui formalmente um tipo de dado string. Vocé nao deve confundir strings com caracteres. Uma constante de um Unico caractere 6 colocada entre aspas simples, como em “a”. Contudo, “a” é uma string contendo apenas uma letra. Constantes Caractere de Barra Invertida Colocar entre aspas simples todas as constantes tipo caractere funciona para a maioria dos caracteres imprimiveis. Uns poucos, porém, como 0 retorno de carro (CR), sao impossiveis de inserir pelo teclado. Por essa razo, C criou as constantes especiais de caractere de barra invertida. C suporta diversos cédigos de barra invertida (listados na Tabela 2.2) de forma que vocé pode facilmente entrar esses caracteres especiais como cons- tantes. Vocé deve usar os cédigos de barra invertida em lugar de seus ASCII equivalentes para aumentar a portabilidade. Tabela 2.2. Cédigos de barra invertida, | |) Cédigo ignificado \b Retrocesso (BS) \F Alimentacao de formulério (FF) \n Nova linha (LF) \r Retorno de carro (CR) \t Tabulacao horizontal (HT) \" Aspas duplas \' Aspas simples \o Nulo \\ Barra invertida Ww Tabulacao vertical \a Alerta (beep) \N Constante octal (onde N é uma consiante octal) \xN Constante hexadecimal (onde N é uma constante hexadecimal) Por exemplo, o programa seguinte envia a tela um caractere de nova linha e uma tabulagdo e, em seguida, escreve a string isso é um teste. Cap. 2 Expresses em C 39 #include void main(void) t£("\n\tIsso é um teste"); a Operadores C € muito rica em operadores internos. (Na realidade, C dé mais énfase aos operadores que a maioria das outras linguagens de computador.) C define quatro classes de operadores: aritméticos, relacionais, légicos e bit a bit. Além disso, C tem alguns operadores especiais para tarefas particulares. O Operador de Atribuigao Em C, vocé pode usar 0 operador de atribuigdo dentro de qualquer expressio valida de C. Isso nao acontece na maioria das linguagens de computador (in- cluindo Pascal, BASIC e FORTRAN), que tratam os operadores de atribuigio como um caso especial de comando. A forma geral do operador de atribuicao é nome_da_varidvel = expressio; onde expresso pode ser tao simples como uma tinica constante ou tao complexa quanto vocé necessite. Como BASIC e FORTRAN, C usa um iinico sinal de igual para indicar atribuicao (ao contrario de Pascal e Modula-2, que usam a constru- Gao :=). O destino, ou a parte esquerda, da atribuigao deve ser uma varidvel ou um ponteiro, ndo uma fungao ou uma constante. Freqiientemente, em literaturas de C e nas mensagens de erro dos com- piladores, vocé verd esses dois termos: lualue e realue. Exposto de forma simples, um loalue 6 qualquer objeto que pode ocorrer no lado esquerdo de um comando de atribuigo. Para todos os propésitos praticos, “Ivalue” significa “variavel’. termo rvalue refere-se as expressdes do lado direito de uma atribuicao e sig- nifica simplesmeste 0 valor da expressao. ConversGo de Tipos em Atribuigées Conversdo de tipos refere-se a situagdo em que varidveis de um tipo sdo misturadas com variaveis de outro tipo. Em um comando de atribuigao, a regra de conversao de tipos 6 muito simples: 0 valor do lado direito (o lado da expressio) de uma atribuigao é convertido no tipo do lado esquerdo (a varidvel destino), como ilus- trado por este exemplo: 0 C— Conpleto e Total Cop. int x; char ch; float £; void func (void) t ch = x; /* linha 1 */ Bae /* linha 2 */ £ = ch; y* linha 3 */ £= x; y* linha 4 */ Na linha 1, os bits mais significativos da variavel inteira x sao ignorados, dei- xando ch com os 8 bits menos significativos. Se x esta entre 256 e 0, entéo ch e x tém valores idénticos. De outra forma, o valor de ch reflete apenas os bits menos significativos de x. Na linha 2, x recebe a parte inteira de f. Na linha 3, f converte o valor inteiro de 8 bits armazenado em ch no mesmo valor em for- mato de ponto flutuante. Isso também acontece na linha 4, exceto por f converter um valor inteiro de 16 bits no formato de ponto flutuante. Quando se converte de inteiros para caracteres, inteiros longos para in- teiros ¢ inteiros para inteiros curtos, a regra basica é que a quantidade apropriada de bits significativos sera ignorada. Isso significa que 8 bits sio perdidos quando se vai de inteiro para caractere ou inteiro curto, e 16 bits si perdidos quando se vai de um inteiro longo para um inteiro, A Tabela 2.3 retine essas conversdes de tipos. Lembre-se de que a con- versao de um int em um float ou float em double etc. nao aumenta a precisao ‘ou exatidao. Esses tipos de conversio apenas mudam a forma em que 0 valor é representado, Além disso, alguns compiladores C (e processadores) sempre tra- tam uma varidvel char como positiva, nao importando que valor ela tenha quan- do é convertida para int ou float. Outros compiladores tratam valores de varidveis char maiores que 127 como ntimeros negativos. De forma geral, vocé deve usar varidveis char para caracteres e usar ints, short ints ou signed chars quando for necessario evitar um pessivel problema de portabilidade. Para utilizar a Tabela 2.3 para fazer uma conversdo nao mostrada, sim- plesmente converta um tipo por vez até acabar. Por exemplo, para converter double em int, primeiro converta double em float e, entdo, float em int. Linguagens como Pascal profbem converséo automatica de tipos. No entanto, C foi projetada para simplificar a vida do programador, permitindo que 0 trabalho seja feito em C em vez de assembler. Para substituir o assembler, C tem de permitir essas conversées de tipos. Cap. ‘ Expressbes em C 41 Tabela 2.3 ‘Conversées de tipos comuns (assumindo uma palavra de 16 bits). Tipo do destino Tipo da expressao _Possivel informagéo perdida signed char char Se valor > 127, 0 destino é negativo char short int Os 8 bits mais significativos char int Os 8 bits mais significativos char long int Os 24 bits mais significativos int long int Os 16 bits mais significativos int float A parte fraciondria e possivelmente mais float double Precisao, o resultado é arredondado © resultado € arredondado double long double Precis Atribuigdes Multiplas C permite que vocé atribua o mesmo valor a muitas varidveis usando atribuigdes miultiplas em um tnico comando. Por exemplo, esse fragmento de programa atribui a x, y e z 0 valor 0: Bx-y-e- Em programas profissionais, valores comuns sao atribuidos a varidveis usando esse método. Operadores Aritméticos A Tabela 2.4 lista os operadores aritméticos de C. Os operadores -, +, * e / tra- balham em C da mesma forma em que na maioria das outras linguagens. Eles podem ser aplicados em quase qualquer tipo de dado interno permitido em C. Quando / é aplicado a um inteiro ou caractere, qualquer resto é truncado. Por exemplo, 5/2 ser igual a 2 em uma divisdo inteira. Tabela 2.4 Operadores aritméticos. Operador — Acao - Subtracdo, também menos undrio + Adigao * Multiplicagao / Divisao % Médulo da divisao (resto) —— Decremento ++ Incremento 2 C— Completo ¢ Totat Cap. 2 O operador médulo % também trabalha em C da mesma forma que em outras linguagens, devolvendo o resto de uma divisao inteira. Contudo, % nao pode ser usado nos tipos em ponto flutuante. O seguinte fragmento de codigo ilustra %. yi x= 5; ys2: print£("%a", x/y); /* mostraré 2 */ printf (*"%d",xty); /* mostrara 1, o resto da divisdo inteira */ x=: y=2: printf("%d ta", x/y, xy); /* mostraré 0 1 */ A Ultima linha imprime 0 ¢ 1 porque 1/2 em uma divisao inteira é 0 com resto 1 © menos unério multiplica seu tinico operando por -1. Isto 6, qualquer ndmero precedido por um sinal de subtracao troca de sinal. Incremento e Decremento C inclui dois operadores titeis geralmente nao encontrados em outras linguagens. ‘Sao os operadores de incremento e decremento, ++ ¢ --. O operador ++ soma 1 ao seu operando, ¢ -- subtrai 1. Em outras palavras: @ x xn: 0 mesmo que “yo e B x= x-1; 6 0 mesmo que B x Cap. 2 Expressées em © 43 Ambos 0s operadores de incremento e decremento podem ser utilizados como prefixo ou sufixo do operando. Por exemplo: Bx =; pode ser escrito ou Cee Hé, porém, uma diferenca quando esses operadores so usados em uma expres- 4. Quando um operador de incremento ou decremento precede seu operando, C executa a operacao de incremento ou decremento antes de usar o valor do operando. Se o operador estiver apés seu operando, C usard o valor do operando antes de incrementa-lo ou decrementé-lo. O exemplo a seguir: x = 10; y = +4; coloca 11 em y. Porém, se 0 cédigo fosse es x = 10; y= xt; y receberia 10. Em ambos os casos, x recebe 11; a diferenga est4 em quando isso acontece. A maioria dos compiladores C produz cédigo-objeto para as operacdes de incremento e decremento muito rapidas e eficientes — cddigo esse que 6 melhor que aquele gerado pelo uso da sentenca de atribuigéo equivalente. Por essa razo, vocé deve usar os operadores de incremento e decremento sempre que puder. A precedéncia dos operadores aritméticos é a seguinte: Mais alta tee = (menos unario) "/% Mais baixa a Operadores do mesmo nivel de precedéncia sao avaliados pelo compilador da esquerda para a direita. Obviamente, parénteses podem ser usados para alterar a ordem de avaliagao. C trata parénteses da mesma forma que todas as outras “4 C-— Completo e Total Cap. 2 linguagens de programacdo. Parénteses forcam uma operacdo, ou um conjunto de operacées, a ter um nivel de precedéncia maior. Operadores Relacionais e Logicos No termo operador relacional, relacional refere-se as relagdes que os valores podem ter uns com os outros. No termo operador l6gico, légico refere-se as maneiras como essas relag6es podem ser conectadas. Uma vez que os operadores l6gicos e re- lacionais freqiientemente trabalham juntos, eles serao discutidos aqui em conjunto. A idéia de verdadeiro e falso é a base dos conceitos dos operadores logicos e relacionais. Em C, verdadeiro é qualquer valor diferente de zero. Falso 6 zero. As expressées que usam operadores relacionais ou légicos devolvem zero para falso e I para verdadeiro. A Tabela 2.5 mostra os operadores l6gicos ¢ relacionais. A tabela verdade dos operadores légicos é mostrada a seguir, usando 1s e¢ 0s. P q p&&q Pig 0 0 0 0 0 1 0 1 1 1 1 1 1 0 1 0 0 1 0 Ambos os operadores sio menores em precedéncia do que os operadores arit- méticos. Isto é, uma expressao como 10 > 1 + 12 6 avaliada como se fosse escrita 10 > (1412). O resultado é, obviamente, falso Tabela 2.5. Operadores légicos e relacionais. Operadores relacionais Operador Acao > Maior que Maior que ou igual Menor que Menor que ou igual Igual Diferente Operadores légicos Operador Acao && AND it OR ! NOT Cap.2 Expressoes em'C 45 E permitido combinar diversas operagées em uma expressio como mostrado aqui 10>5 && 110 < 9) 11 3<=4 Neste caso, o resultado é verdadeiro. Embora C nao tenha um operador légico OR exclusive (XOR), vocé pode facilmente criar uma fungao que execute essa tarefa usando os outros operadores logicos. O resultado de uma operagao XOR é verdadeiro se, e somente se, um operando (mas nao os dois) for verdadeiro. O programa seguinte contém a fun- cdo xor0, que devolve o resultado de uma operacao OR exclusivo realizada nos dois argumentos: #include int xor(int a, int b); void main (void) print£("%a", xor(1, 0)); print£("$d", xor(1, 1)); print£("$a", xor(0, 1)); print£("$d", xor(0, 0)); /* Executa uma operagaéo légica XOR usando os dois argument xor(int a, int b) ( return(a |! b) && !(a && b); } A tabela seguinte mostra a precedéncia relativa dos operadores relacionais e 16- gicos maior ! menor a Como no caso das expressées aritméticas, é possivel usar parénteses para alterar a ordem natural de avaliagéo de uma expressao relacional e/ou légica. Por exemplo, 0 && 0110 46 C— Completo e Total Cap. 2 6 falso. Porém, quando parénteses so adicionados 4 mesma expressao, como mostrado aqui, o resultado é verdadeiro: {0 && 0) £40 Lembre-se de que toda expressao relacional e légica produz como re- sultado 0 ou 1. Entao, o seguinte fragmento de programa nao apenas esta correto, como imprimiré o ntimero 1 na tela: int 3x; x = 10 printé Bd", x>10); Operadores Bit a Bit Ao contrério de muitas outras linguagens, C suporta um completo conjunto de operadores bit a bit. Uma vez que C foi projetada para substituir a linguagem assembly na maioria das tarefas de programagio, era importante que ela tivesse a habilidade de suportar muitas das operacdes que podem ser feitas em lingua- gem assembly. Operacao bit a bit refere-se a testar, atribuir ou deslocar os bits efetivos em um byte ou uma palavra, que correspondem aos tipos de dados char e int e variantes do padrao C. Operacées bit nao podem ser usadas em float, double, long double, void ou outros tipos mais complexos. A Tabela 2.6 lista 0s operadores que se aplicam as operacées bit a bit. Essas operacdes sao aplicadas aos bits individuais dos operandos. Tabela 2.6 Operadores bit a bit. Operador Acao & AND ! OR n OR exclusive (XOR) ~ Complemento de um > Deslocamento a esquerda << Deslocamento a direita As operacées bit a bit AND, OR e NOT (complemento de um) sio go- vernadas pela mesma tabela verdade de seus equivalentes ldgicos, exceto por trabalharem bit a bit. O OR exclusivo (4) tem a tabela verdade mostrada aqui: Cap. 2 Expressdes enn C "7 Pp q pra 0 0 0 1 0 1 1 1 0 0 1 1 Como a tabela indica, 0 resultado de um XOR é verdadeiro apenas se exatamente um dos operandos for verdadeiro; caso contrario, sera falso. Operagées bit a bit encontram aplicagdes mais freqiientemente em “dri- vers” de dispesitivos — como em programas de modems, rotinas de arquivos em disco e rotinas de impresoras — porque as operacées bit a bit mascaram certos bits, como o bit de paridade. (O bit de paridade confirma se o restante dos bits em um byte nao se modificaram. E geralmente o bit mais significativo em cada byte.) Imagine © operador AND como uma maneira de desligar bits. Isto é, qualquer bit que é zero, em qualquer operando, faz com que o bit correspondente no resultado seja desligado. Por exemplo, a seguinte funcao lé um caractere da porta do modem usando a funcao read_modem() e, entao, zera o bit de paridade. char get_char_from_modem(void) { char ch; ch = reaG_modem(}; /* 18 um caractere do modem */ urn (ch & 127); } A paridade é indicada pelo oitavo bit, que é colocado em 0, fazendo-se um AND com um byte em que os bits de 1 a 7 so 1 o bit 8 é 0. A expressao ch & 127 significa fazer um AND bit a bit de ch com os bits que compdem 0 ntimero 127. O resultado é que 9 oitavo bit de ch esta zerado. No seguinte exemplo, assuma que ch tenha recebido 0 caractere “A” e que o bit de paridade tenha sido ativado. Bit de paridade | 4 11000001 ch contém “A” com a paridade ligada 01111111 127 em bindrio & —————__ faz AND bit a bit 01000001 “A” sem paridade 46 C — Completo e Total — : Cap. 2 © operador OR, ao contrario de AND, pode ser usado para ligar um bit. Qualquer bit que é 1, em qualquer operando, faz com que o bit correspondente no resultado seja ligado. Por exemplo, o seguinte mostra a operagio 128 | 3: 000000 128 em binario 00017 3 em binario i OR bit a bit 10000011 resultado Um OR exclusivo, normalmente abreviado por XOR, ativa um bit se, e somente se, os bits comparados forem diferentes. Por exemplo, 1274120 & 01111111 127embinario 01111000 120 em binario A —_—_—_— XOR bit a bit 00000111 _ resultado Lembre-se de que os operadores légicos e relacionais sempre produzem um resultado que é 1 ou 0, enquanto as operagées similares bit a bit produzem quaisquer valores arbitrarios de acordo com a operacio especifica. Em outras palavras, operagées. bit a bit podem possuir valores diferentes de 0 e 1, mas os operadores légicos sempre conduzem a 0 ou 1. Os operadores de deslocamento, >> e <<, movem todos os bits de uma varidvel para a direita ou para a esquerda, como especificado. A forma geral do comando de deslocamento a direita é varidvel >> mimero de posigées de bits A forma geral do comando de deslocamento a esquerda é varidvel << nimero de posigdes de bits Conforme os bits s4o deslocados para uma extremidade, zeros sao co- locados na outra. Lembre-se de que um deslocamento ndo é uma rotagao. Ou seja, os bits que saem por uma extremidade nao voltam para a outra. Os bits deslocados sao perdidos e zeros sao colocados. Operagdes de deslocamento de bits podem ser uiteis quando se decodi- fica a entrada de um dispositivo externo, como um conversor D/A, e quando se léem informagées de estado. Os operadores de desiocamento em nivel de bits. também podem multiplicar e dividir inteiros rapidamente. Um deslocamento a direita efetivamente multiplica um namero por 2 e um deslocamento a esquerda divide-o por 2, como mostrado na Tabela 2.7. O programa seguinte ilustra os operadores de deslocamento. Cap. 2 “-Expresstes em © : 0 Tabela 2.7 Multiplicagao e divisdo com operadores de deslocamento. | unsigned char x; x a cada execugao da sentenga —-Vallor de x x=7; 00000111 7 00001110 14 01110000 112 11000000 192 01100000 96 00011000 24 Cada deslocamento a esquerda multiplica por 2. Note que se perdeu informagio apés 0 x<<2 porque um bit foi deslocado para fora Cada deslocamento a direita divide por 2. Note que divisdes subseqiientes nao trazem de volta bits anteriormente perdidos. /* Um exemplo de deslocamento de bits. */ #include void main(void) { unsigned int i; int 3; isd; /* deslocamentos 4 esquerda */ for(j=0; j<4; j+*) { i=i<<1; /* desioca i de 1 4 esquerda, que é 0 mesmo que multiplicar por 2 */ printf ("deslocamento & esquerda %d: d\n", 3, i); } /* deslocamentos a direita ‘/ for (j=0; j> 1; /* desloca i de 1 & direita, que é 0 mesmo que dividir por 2 */ print£(*deslocamento a direita td:%d\n", j, i); ) ) O operador de complemento a um, ~, inverte 0 estado de cada bit da variavel especificada. Ou seja, todos os 1s s0 colocados em 0 € todos os 0s sa0 colocados em I. 50 C— Completo'é Total Cap.2 Os operadores bit a bit sio usados freqiientemente em rotinas de crip- tografia. Se vocé deseja fazer um arquivo em disco parecer ilegivel, realize algu- mas manipulacées bit a bit nele. Um dos métodos mais simples é complementar cada byte usando o complemento de um para inverter cada bit no byte, como mostrado aqui: Byte original 00101100 Apos 0 12 complemento. 11010011 ean Apés 0 22complemento 00101100 Note que uma seqiiéncia de dois complementos produz 0 nimero ori- ginal. Logo, 0 primeiro complemento representa a versio codificada de cada byte. O segundo complemento decodifica-o ao seu valor original Vocé poderia usar a fungdo encode(), mostrada aqui, para codificar um caractere. /* Uma fungéo simples de criptografia. */ char encode(char ch) return(~ch); /* complementa */ O Operador ? C contém um operador muito poderoso e conveniente que substitui certas sen- tencas da forma if-then-else. O operador ternario ? tem a forma geral Expl ? Exp2 : Exp3; onde Expl, Exp2 e Exp3 sao expressdes. Note 0 uso e 0 posicionamento dos dois- pontos. O operador ? funciona desta forma: Exp! € avaliada. Se ela for verda- deira, entao Exp2 é avaliada e se toma o valor da expresso. Se Expl é falsa, entao Exp3 é avaliada e se torna o valor da expressao. Por exemplo, em x = 10; y = x29 7 100 : 200; ay é atribuido 0 valor 100. Se x fosse menor que 9, y teria recebido o valor 200 O mesmo cédigo, usando 0 comando if-else, é Cap. 2 Exprressoes eit C St x = 10; if (x29) y = 100 else y = 200; O operador ? sera discutido mais completamente no Capitulo 3 com relagao as outras sentengas condicionais de C. Os Operadores de Ponteiros & e * Um ponteiro é um endereco na memoria de uma variavel. Uma varidvel de ponteiro é uma variavel especialmente declarada para guardar um ponteiro para seu tipo especificado. Saber 0 endereco de uma variavel pode ser de grande ajuda em certos tipos de rotinas. Contudo, ponteiros tém trés fungdes principais em C. Eles podem fornecer uma maneira rapida de referenciar elementos de uma ma- triz. Os ponteiros também permitem que as fungdes em C modifiquem seus pa- rametros de chamada. Por ultimo, eles suportam listas encadeadas e outras es- truturas dinamicas de dados. O Capitulo 5 é dirigido exclusivamente a ponteiros. Porém, esse capitulo aborda de forma breve os dois operadores que sao usados para manipular ponteiros. O primeiro operador de ponteiro é &. Ele 6 um operador unario que devolve 0 endereco na memoria de seu operando. (Lembre-se de que um ope- rador unério requer apenas um operando.) Por exemplo, poe o enderego na meméria da variavel count em m. Esse endereco é a posicao interna da varidvel no computador. Ele nao tem nenhuma relacéo com 0 valor de count. Vocé pode imaginar & como significando “o endereco de”. Desta for- ma, a sentenca de atribuicao anterior significa “m recebe o endereco de count”. acount; Para entender melhor essa atribuicdo, assuma que a varidvel count usa a posigao de meméria 2000 para armazenar seu valor. Também assuma que count tem o valor 100. Entao, apés a atribuicdo anterior, m tem o valor 2000. O segundo operador é *, que é 0 complemento de &. O * 6 um operador unério que devolve o valor da varidvel localizada no endereco que 0 segue. Por exemplo, se m contém 0 endereco da variavel count, 52 C— Completo e Total Cap. 2 Boc-en coloca 0 valor de count em q. Agora q tem o valor 100 porque 100 est4 arma- zenado na posicao 2000, 0 endereco na memoria que esta armazenado em m Pense no * como significando “no enderego de”. Neste caso, a sentenga poderia ser lida como “q recebe o valor do endereco de m’. Infelizmente, 0 simbolo de multiplicacao e 0 simbolo de “no enderego de” sao iguais, e o simbolo para o AND bit a bit e 0 simbolo de “o enderego de” também sao iguais. Esses operadores nao tém nenhuma relacéo um com 0 outro. Ambos, & e *, tem uma precedéncia maior que todos os operadores arit- méticos, exceto 0 menos unério, que tem a mesma precedéncia. Variaveis que guardam ponteiros devem ser declaradas como tal. Va- ridveis que armazenam enderecos da meméria, ou ponteiros, como sao chamados em C, devem ser declarados colocando-se * em frente ao nome da varidvel para indicar ao compilador que ela guardara um ponteiro para aquele tipo de variavel Por exemplo, para declarar uma varidvel ponteiro ch para char, escreva char *ch; Aqui, ch nao é um caractere, mas um ponteiro para caractere — ha uma grande diferenca. O tipo de dado que o ponteiro aponta, neste caso char, é chamado o tipo base do ponteiro. De qualquer forma, a varidvel ponteiro é uma variavel que mantém o endereco de um objeto do tipo base. Logo, um ponteiro para caractere (ou qualquer ponteiro) é de tamanho suficiente pata guardar um enderego como definido pela arquitetura do computador em que esté rodando. Lembre-se de que um ponteiro deve ser usado apenas para apontar para dados que sao do tipo base do ponteiro. Vocé pode misturar diretivas de ponteiro ¢ de nao-ponteiros na mesma declaragao. Por exemplo @ int x, *y, count; declara x e count como sendo do tipo inteiro e y como um ponteiro para o tipo inteiro. Os seguintes operadores * e & poem o valor 10 na variével chamada target. Como esperado, esse programa mostra o valor 10 na tela. Cap.2 Expressiies em C 53 #include void main(void) { int target, source; int *m target); O Operador em Tempo de Compilagdao sizeof O operador sizeof é um operador em tempo de compilacao unério que retorna © tamanho, em bytes, da varidvel ou especificador de tipo, em parénteses, que ele precede. Por exemplo, assumindo que inteiros sio de 2 bytes e que floats sao de 8 bytes, float £; printf ("tf", sizeof f); printf ("%a", sizeof (int)); ir mostrar na tela 8 2. Lembre-se de que para calcular 0 tamanho de um tipo, o nome do tipo deve ser colocado entre parénteses. Isso nao é necessério para nomes de varidveis, embora nao haja qualquer mal em fazé-lo. O padrao ANSI (usando typedef) define um tipo especial chamado size_t, que corresponde de forma imprecisa a um inteiro sem sinal. Tecnicamente, 0 valor devolvido por sizeof é do tipo size_t. Para todos os fins praticos, porém, vocé pode imagind-lo (e us4-lo) como se fosse um valor sem sinal. sizeof ajuda basicamente a gerar cdigos portaveis que dependam do tamanho dos tipos de dados internos de C. Por exemplo, imagine um programa de banco de dados que precise armazenar seis valores inteiros por registro. Se vocé quer transportar 0 programa de banco de dados para varios computa- dores, nao deve assumir o tamanho de um inteiro, mas deve determinar 0 ta- manho real do inteiro usando sizeof. Sendo esse 0 caso, vocé poderia usar a seguinte rotina para escrever um registro em um arquivo em disco: a C— Campleto ¢ Total Cap. 2 /* Escreve 6 ij void put_rec (in { int len; iros em um arquivo em disco. */ rec[6], FILE *fp) len = rite(rec, sizeof rec, 1, fp); if(len != 1) printf("erro de escrita"); } Codificada como mostrado, put_rec() compila e roda corretamente em qualquer computador, nao importando quantos bytes tenha um inteiro. O Operador Virgula O operador virgula é usado para encadear diversas expressdes. O lado esquerdo de um operador virgula é sempre avaliado como void. Isso significa que a ex- pressio do lado direito torna-se valor de toda a expressao separada por vir gulas. Por exemplo, x = (y=3, y+); primeiro atribui 0 valor 3 a y e, em seguida, atribui o valor 4 a x. Os parénteses sA0 necessdrios porque o operador virgula tem uma precedéncia menor que o operador de atribuicao. Essencialmente, a virgula provoca uma seqiiéncia de operagdes. Quando ela é usada do lado direito de uma sentenca de atribuicao, o valor atribuido é © valor da tiltima expressao da lista separada por virgulas. O operador virgula tem, de certa forma, 0 mesmo significado da palavra e em portugués normal, como na frase “faca isso e isso e isso”. Os Operadores Ponto (.) e Seta (->) Os operadores . (ponto) e -> (seta) referenciam elementos individuais de estru- turas e unides. Estruturas e unides sao tipos de dados compostos que podem ser referenciados segundo um tinico nome (veja o Capitulo 7). O operador ponto é usado quando se esta referenciando a estrutura ou uniao real. O operador seta é usado quando um ponteiro para uma estrutura é usado. Por exemplo, dada a estrutura global struct employee t char name (80); Cap. 2 Expressbes em C 55 int age; float wage; 3} emp; struct employee *p = kemp; /* enderego de emp em p */ vocé escreveria o seguinte cédigo para atribuir o valor 123.23 ao elemento wage da estrutura emp: WB oemp.wage = 123.23; No entanto, a mesma atribuigao, usando um ponteiro para emp, seria @ p->wage = 123.23; Parénteses e Colchetes Como Operadores Em C, parénteses so operadores que aumentam a precedéncia das operagdes dentro deles. Colchetes realizam indexagao de matrizes (eles serao discutidos no Ca- pitulo 4). Dada uma matriz, a expresso dentro de colchetes prové um indice dentro dessa matriz. Por exemplo: #include char s(80); void main(void) { s(3) = °K; printf ("tc", s(3]}; y Esse cédigo primeiro atribui o valor ’X’ ao quarto elemento (lembre-se de que todas as matrizes em C comegam em 0) da matriz s e imprime esse elemento. Resumo das Precedéncias A Tabela 28 lista a precedéncia de todos os operadores de C. Note que todos os operadores, exceto os operadores undrios e ?, associam da esquerda para a direita. Os operadores unérios (*, & e -) e ? associam da direita para a esquerda 56 ‘C— Completa e Total Cap. 2 Tabela 2.8 A precedéncia dos operadores em C. Maior ous +f eae == = tipo) * & sizeof /= ete. Menor a Expressoes Operadores, constantes e variaveis sio os elementos que constituem as expres- ies. Uma expresso em C é qualquer combinacao valida desses elementos. Uma vez que a maioria das expressdes tende a seguir as regras gerais da algebra, elas sao freqiientemente tomadas como certas. Contudo, existem uns poucos aspectos de expressdes que se referem especificamente a C. Ordem de Avaliacao O padrao C ANSI nao estipula que as subexpressdes de uma expressio devam ser avaliadas em uma ordem especificada. Isso deixa 0 compilador livre para rearranjar uma expressao para produzir o melhor cédigo. No entanto, isso tam- bém significa que seu cédigo nunca deve contar com a ordem em que as subex- pressées so avaliadas. Por exemplo, a expressao B® x= f10 + 220; nao garante que f1() sera chamada antes de £20. Cap. 2 Expressies an C 57 Conversdo de Tipos em Expressées Quando constantes e varidveis de tipos diferentes so misturadas em uma expressio, elas sao convertidas a um mesmo tipo. O compilador C converte todos os operandos no tipo do maior operando, 0 que é denominado promogio de tipo. Isso é feito ope- Tago por operacao, como descrito nas regras de conversdo de tipos abaixo. SE um operando é long double ENTAO 0 segundo é convertido para long double SENAO, SE um operando é double ENTAO 0 segundo é convertido para double SENAO, SE um operando é float ENTAO 0 segundo é¢ convertido para float SENAO, SE um operando é unsigned long ENTAO o segundo é convertido para unsigned long SENAO, SE um operando é long ENTAO o segundo é convertido para long, SENAO, SE um operando é unsigned int ENTAO 0 segundo é convertido para unsigned int Ha ainda um caso adicional especial: se um operando é long e 0 outro é unsigned int, e se o valor do unsigned int nao pode ser representado por um long, os dois operandos sao convertidos para unsigned long. Uma vez que essas regras de conversao tenham sido aplicadas, cada par de operandos é do mesmo tipo e o resultado de cada operagao é do mesmo tipo de ambos 0s operandos. Por exemplo, considere as conversées de tipo que ocorrem na Figura 2.3. Primeiro, o caratere ch é convertido para um inteiro e float f é convertido para double. Em seguida, o resultado de ch/i 6 convertido para double porque f*d é double. O resultado final é double porque, nesse momento, os dois operandos so double. Casts Vocé pode forcar uma expressao a set de um determinado tipo usando um cast. A forma genérica de um cast é (tipo) expressao onde tipo é qualquer tipo de dados valido em C. Por exemplo, para garantir que a expresso x/2 resulte em um valor do tipo float, escreva BB (float) x/2; 5B © — Completo ¢ Total Cap. 2 char ch; int i; float f; double d; result = (ch/i) + (fd) - (fi); int | double float int double float double Figura 2.3. Um exemplo de conversio dé tipo. Casts so operadores tecnicamente. Como operador, um cast é undrio e possui a mesma precedéncia que qualquer outro operador unério. Embora os casts ndo sejam muito usados em programaao, eles podem ser muito titeis quando necessédrios. Por exemplo, suponha que vocé deseje usar um inteiro para controlar uma repeticao, no entanto as operagées sobre o valor exigem uma parte fraciondria, como no programa seguinte: #include void main(void) /* imprime i e i/2 com fracgdes */ ‘ int i; for (isl; i<=100; ++i) print£("@d / 2 é: ®f\n", i, (float) i /2); Sem o cast (float), teria sido efetuada somente uma divisao inteira. O cast garante que a parte fraciondria do resultado seja exibida. Cap. 2 Expressbés em C 59 Espacgamento e Parénteses Vocé pode acrescentar tabulacées e espacos a expresses em C para torné-las mais legiveis. Por exemplo, as duas proximas expressdes si0 a mesma x=l0/y ~ (127/x); x= 10 / y ~ (127/x); Parénteses redundantes ou adicionais ndo causam erros nem diminuem a velocidade de execucao da expressao. Voce deve usar parénteses para esclarecer a ordem de avaliacao, tanto para vocé mesmo como para os demais. Por exemplo, quais das duas expressoes a seguir 6 mais facil de ler? x=y/2-34*tempa127; x = (y/3) - ((34*temp) &127); C Reduzido Existe uma variante do comando de atribuigao, as vezes chamada de C reduzido, que simplifica a codificagao de um certo tipo de operagées de atribuicao. Por exemplo, Box = x10; pode ser escrito Bx += 10; © par operador += diz ao compilador para atribuir a x o valor de x mais 10. Essas abreviacdes existem para todos os operadores binérios em C (aque- les que requerem dois operands). A forma geral de uma abreviacéo C como var = rar operador expressiio é 0 mesmo que var operador = expresso Considere um outro exemplo: 60 C= Completo e Total Cap. 2 B x = x-100; 0 mesmo que Wx = 100; Vocé verd a notagao abreviada sendo utilizada largamente em programas escritos profissionalmente em C; vocé deve familiarizar-se com ela. ar Comandos de Controle do Programa Este capitulo discute os ricos e variados comandos de controle do programa em C. O padsao ANSI divide os comandos de C nestes grupos: m Selecao Iteragao Desvio Rétulo Expresso Bloco Esto inclufdos nos comando de selesao if e switch. (O termo “comando condicional” € freqiientemente usado em lugar de “comando de selecéo”. No entanto, 0 padrao ANSI usa “selecdo”, como também este livro.) Os comandos de iteracdo sdo while, for e do-while. Sao também normalmente chamados de comandos de laco. Os comandos de salto ou desvio so break, continue, goto e return. Os comandos de rétulo sao case e default (discutidos juntamente com 0 comando switch) e 0 comando label (discutido com goto). Sentencas de expres- so so aquelas compostas por uma expressdo C valida. Sentengas de bloco sao simplesmente blocos de cédigo. (Lembre-se de que um bloco comega com um [ e termina com um |.) Como muitos comandos em C contam com a saida de alguns testes con- dicionais, comecaremos revendo os conceitos de verdadeiro e falso em C. or 62 C— Completo e Total Cap. 3 Wi Verdadeiro e Falso em C Muitos comandos em C contam com um teste condicional que determina 0 curso da aio. Uma expressao condicional chega a um valor verdadeiro ou falso. Em C, ao contrario de muitas outras linguagens, um valor verdadeiro é qualquer valor diferente de zero, incluindo ntimeros negativos. Um valor falso é 0. Esse método para verdadeiro e falso permite que uma ampla gama de rotinas sejam codificadas de forma extremamente eficiente, como vocé vera em breve. HZ Comandos de Selegao C suporta dois tipos de comandos de selegao: if e switch. Além disso, o operador ? 6 uma alternativa ao if em certas circunstancias. if A forma geral da sentenca if é if (expressio) comando; else comando; onde comando pode ser um Unico comando, um bloco de comandos ou nada (no caso de comandos vazios). A clausula else ¢ opcional. Se a expressiio 6 verdadeira (algo diferente de 0), 0 comando ou bloco que forma 0 corpo do if é executado; caso contrario, 0 comando ou bloco que é © corpo do else (se existir) é executado. Lembre-se de que apenas 0 c6digo as- sociado ao if ou 0 c6digo associado ao else ser executado, nunca ambos. O comando condicional controlando o if deve produzir um resultado escalar. Um escalar é um inteiro, um caractere ou tipo de ponto flutuante. No entanto, é raro usar um ntimero em ponto flutuante para controlar um comando condicional, porque isso diminui consideravelmente a velocidade de execucao. (A CPU executa diversas instrugdes para efetuar uma operagao em ponto flu- tuante. Ela usa relativamente poucas instrucdes para efetuar uma operacao com caractere ou inteiro.) Por exemplo, considere o programa a seguir, que é uma versio muito simples do jogo de adivinhar o “ntimero magico”. Ele imprime a mensagem “** Certo **” quando 0 jogador acerta o mimero magico. Ele produz o numero mé- gico usando o gerador de ntimeros randémicos de C, que devolve um nimero arbitrario entre 0 e RAND-MAX (que define um valor inteiro que é 32.767 ou maior) exige o arquivo de cabecalho stdlib.h. Cap. 3 Comandos de controle do programa 63 /* Programa de ntimeros magicos #1. */ #include #include void main(void) £ int magic; /* mimero magico */ int guess; /* palpite do usudrio */ magic = rand(); /* gera o mimero magico */ printf (*adivinhe o nimero magico: "); scanf("$d", &guess) ; if (guess == magic) printf£("** Certo **"); } Levando o programa do niimero magico adiante, a préxima versao ilustfa 0 uso da sentenca else para imprimir outra mensagem em resposta a um numero er- rado. /* Programa de niimeros mdgicos #2. */ #include #include void main(void) i int magic; /* niimero mdgico */ int guess; /* palpite do usuério */ magic = rand(); /* gera o nimero magico */ printf("adivinhe o ntimero magico: "); scanf("$d", &guess); if(guess == magic) printf£("** Certo **"); else print£(*Errado"); ifs Aninhados Um if aninhado é um comando if que é 0 objeto de outro if ou else. ifs aninhados sdo muito comuns em programacao. Em C, um comando else sempre se refere 64 C— Completo e Total i Cap. 3 a0 comando if mais préximo, que esta dentro do mesmo bloco do else ¢ nao estd associado a outro if, Por exemplo if (i) ( if(j) comando 1; if(_\) comando 2; /* este if */ else comando 3; /* esté associado a este else */ } else comando 4; /* associado a if(i) */ Como observado, o ultimo else nao esta associado a iffj) porque nao pertence ao mesmo bloco. Em vez disso, 0 ultimo else esta associado ao if(i). O else interno esta associado ao iftk), que é 0 if mais préximo. © padrao C ANSI especifica que pelo menos 15 niveis de aninhamento devem ser suportados. Na pratica, a maioria dos compiladores permite substan- cialmente mais. Vocé pode usar um if aninhado para melhorar o programa do numero magico dando ao jogador uma realimentacao sobre um palpite errado. /* Programa de numeros magicos #3. */ #include #include void main (void) ( int magi int guess; /* mimero magico */ /* palpite do usudrio */ magic = rand(); /* gera o numero magico */ print£("Adivinhe o nimero mdgico: "); scanf("#d", &guess); if (guess magic) { print£("** Certo ***); print£(" %d 6 0 mimero mégico\n", magic); } else ( printf ("Errado, *); if(guess > magic) print£("muito alto\n"); else printf (*muito baixo\n"); } Cap. 3 ‘Comanitos de controle do programa. 3 65 A Escada if-else-if Uma construgdo comum em programacao é a forma if-else-if, algumas vezes cha- mada de escada if-else-if devido a sua aparéncia. A sua forma geral é if(expressao)comando; else if(expressiio)comando; else if(expressao)comando; else comando; As condigées sao avaliadas de cima para baixo. Assim que uma condicdo ver- dadeira é encontrada, 0 comando associado a ela é executado e desvia do resto da escada. Se nenhuma das condigées for verdadeira, entao 0 tiltime else € exe- cutado. Isto é, se todos os outros testes condicionais falham, o ultimo comando else é efetuado. Se o tiltimo else nao esta presente, nenhuma agio ocorre se todas as condigdes sao falsas Embora seja tecnicamente correta, 0 recuo da escada if-else-if anterior pode ser excessivamente profundo. Por essa razdo, a escada if-else-if é geralmente recuada desta forma: iflexpressio) comando; else if(expressdo) comando; else iffexpressao) comando; else comando; Usando uma escada if-else-if, 0 programa do ntimero magico torna-se: /* Programa de nimeros magicos #4. */ #include #include void main(void) 66 C— Conipleto e Tota Cap. 3 int magic; /* mimero m4gico */ int guess; /* palpite do usudrio */ magic = rand(); /* gera o mimero magico */ printf ("Adivinhe o mimero magico: "); "Sd", &guess); magic) ( ** Certo ***); printf ("8d é 0 numero magico", magic); se if (guess > magic) rintf("Errado, muito alto"); else printf("Errado, muito baixo"); O ? Alternativo Vocé pode usar 0 operador ? para substituir comandos if-else na forma geral: if(condigio) expresso; else expressiio; Contudo, os corpos de if e else devem ser uma expressao simples — nunca um outro comando de C. © 26 chamado de um operador terndrio porque ele requer trés operandos. Ele tem a seguinte forma geral Expl ? Exp2 : Exp3 onde Expl, Exp2 e Exp3 sao expressées. Note 0 uso e 0 posicionamento dos dois- pontos. © valor de uma expressdo ? é determinada como segue: Exp] 6 avaliada. Se for verdadeira, Exp? sera avaliada e se tornard o valor da expressao ? inteira Se Expl é falsa, entio Exp3 é avaliada e se torna o valor da expressao. Por exem- plo, considere x = 10; y = x>9 ? 100 : 200; Cap. 3 ‘Comandos de.controle do programa 67 Nesse exemplo, 0 valor 100 é atribuido a y. Se x fosse menor que 9, y teria recebido o valor 200. O mesmo cédigo escrito com 0 comando if-else seria x = 10; if (@9) y = 100; else y = 200; O seguinte programa usa 0 operador ? para elevar ao quadrado um valor inteiro digitado pelo usuario. Contudo, este programa preserva 0 sinal (10 ao quadrado € 100 e -10 ao quadrado é -100). #include void main(void) { int isqrd, print£("Digite um numero: scanf("td", &i); isqrd = in0 2 itd: (iti); print£("%d ao quadrado é $a", i, isard); O uso do operador ? para substituir comandos if-else nao é restrito a atribui apenas. Lembre-se de que todas as fung6es (exceto aquelas declaradas como void) podem retornar um valor. Logo, vocé pode usar uma ou mais chamadas a fun- GOes em uma expresso em C. Quando o nome da fungdo 6 encontrado, a funcao € executada e, entao, seu valor de retorno pode ser determinado. Portanto, vocé pode executar uma ou mais chamadas a fungdes usando o operador ?, colocando as chamadas nas expresses que formam os operandos, como em #include int f1(ini int £2(void); void main(void) { int t; printf("Digite um numero: "); 68 C— Completo ¢ Total Cop. 3 scanf("8d", &t); /* imprime a mensagem apropriada */ t ? £1(t) + £2() : print£("foi digitado zero"); f1(int n) ( printé("$d ", n); return 0; £2 (void) { print£(*foi digitado"); return 0; Inserir um 0 nesse exemplo faz com que a funcio printf() seja chamada, mos- trando a mensagem foi digitado zero. Se vocé inseriu qualquer outro numero, £10) e £20 serao executadas. Note que o valor da expressao ? 6 descartado nesse exemplo. Vocé nao precisa atribu-lo a nada. Um aviso: alguns compiladores C rearranjam a ordem de avaliacao de uma expresso numa tentativa de otimizar 0 cédigo-objeto. Isso pode fazer com que as fungdes que formam os operandos do operador ? sejam executadas em uma seqiiéncia diferente da pretendida. Usando o operador ?, vocé pode reescrever o programa do ntimero mé- gico mais uma vez. /* Nimero magico programa #5. */ #include #include void main(void) { int magic; int guess; magic = rand(); /* gera o niimero m4gico */ printf(*"Adivinhe o ntimero magico: "); scanf("%d", &guess); Cap. 3 Coniandos de controle do programa 69 if (Guess magic) { printf£("** Certo **"); pri 3d é 0 ntimero magico", magic) ; ) else guess > magic ? printf ("Alto") : printf ("Baixo"); ) Aqui, 0 operador ? mostra a mensagem apropriada baseado no resultado do teste guess > magic. A Expressao Condicional Algumas vezes, os iniciantes em C se confundem pelo fato de que vocé pode usar qualquer expressao valida em C para controlar o if ou 0 operador ?. Isto & vocé nao fica restrito a expressdes envolvendo os operadores relacionais e logicos (como é 0 caso nas linguagens como BASIC ou Pascal). O programa pre- cisa simplesmente chegar a um valor zero ou nao-zero. Por exemplo, o seguinte programa lé dois inteitos do teclado e mostra o quociente. Ele usa um comando if, controlado pelo segundo ntimero, para evitar um erro de divisao por zero. /* Divide o primeiro ntimero pelo segundo. */ #include void main(void) { int a, b; printf("Digite dois numeros: " scant ("#d%d", &a, &b); if(b) print£("%d\n", a/b); else printf ("ndo pode dividir por zero\n } Esse método funciona porque, se b é 0, a condic&o que controla if € falsa e 0 else é executado. De outra forma, a condicao é verdadeira (ndo-zero) e a divisdo 6 efetuada. Contuda, escrever 0 comando if desta forma: B itm € redundante, potencialmente ineficiente e considerado mau estilo. 0) printf ("td\n", a/b); 70 C— Completo ¢ Total Cap. 3 switch C tem um comando interno de selecao muiltipla, switch, que testa sucessivamente 0 valor de uma expressao contra uma lista de constantes inteiras ou de caractere. Quando 0 valor coincide, os comandos associados aquela constante so execu- tados. A forma geral do comando switch é switch (expresso) { case constantel: seqiiéncia de comandos break; case constante2: seqiiéncia de comandos break; case constante3: seqtiéncia de comandos break; default: seqiiéncia de comandos } O valor da expressdo é testado, na ordem, contra os valores das constantes espe- cificadas nos comandos case. Quando uma coincidéncia for encontrada, a se- qiiéncia de comandos associada aquele case sera executada até que 0 comando break ou o fim do comando switch seja alcancado. O comando default é exe- cutado se nenhuma coincidéncia for detectada. O default é opcional e, se nao estiver presente, nenhuma agao serd realizada se todos os testes falharem. O padrao ANSI C especifica que um switch pode ter pelo menos 257 comandos case. Na pratica, vocé desejara limitar o ntimero de comandos case a uma quantidade menor, para obter mais eficiéncia. Embora case seja um rétulo, ele nao pode existir sozinho, fora de um switch. Ocomando break é um dos comandos de desvio em C. Vocé pode usé-lo em lacos tal como no comando switch (veja a secio “Comandos de Iteracao"). Quando um break é encontrado em um switch, a execugao do programa “salta” para a linha de cédigo seguinte ao comando switch. Ha trés cois s importantes a saber sobre 0 comando switch ™@ ~Ocomando switch difere do comando if porque switch 86 pode testar igualdade, enquanto if pode avaliar uma expressao ldgica ou relacional. Cap. 3 Comandios de contraté ao programa 7” ™ Duas constantes case no mesmo switch néo podem ter valores idénticos. Obviamente, um comando switch incluido em outro switch mais externo pode ter as mesmas constantes case. ™ Se constantes de caractere sao usadas em um comando switch, elas sdo automaticamente convertidas para seus valores inteiros. O comando switch ¢ freqiientemente usado para processar uma entrada, via teclado, como em uma selegéo por menu. Como mostrado aqui, a fungao menu() mostra o menu de um programa de validacao da ortografia e chama os procedimentos apropriados: void menu (void) { char ch; print£("l. Checar Ortografia\n"); print£("2. Corrigir Erros de Ortografia\r printé("3. Mostrar Erros de Ortografia\n ntf£("Pressione Qualquer Outra Tecla para Abandonar\n"); printf (" Entre com sua escolha: "); ch=getchar(); /* Lé do teclado a selecao */ switch (ch) case ‘1's check_spelling(); break; case '2': correct_e break; case ‘3 display_errors (); break default printf ("Nenhuma op¢éo selegionad rors(}; } Tecnicamente, os comandos break, dentro do switch, sdo opcionais. Eles termi- nam a seqiiéncia de comandos associados com cada constante. Se o comando break é omitido, a execucao continua pelos préximos comandos case até que um break, ou o fim do switch, seja encontrado. Por exemplo, a seguinte fungao usa a natureza de “passar caindo” pelos cases para simplificar 0 codigo de um de- vice-driver que manipula a entrada: na CL. Compleio e Total Cap. 3 /* Processa um valor */ void inp_nandler(int i) ( int flag; flag = - switeh(i) ( case 1: /* Estes cases tém uma seqiéncia */ /* de comandos em comum */ 1: error (flag); break; defaul process (i); Essa rotina ilustra dois aspectos do comando switch. Primeito, vocé pode ter comandos case sem comandos associados. Quando isto ocorre, a execugao simplesmente cai no case seguinte. Nesse caso, todos os trés primeiros cases executam os mesmos comandos. flag break; Segundo, a execucdo continua no préximo case se nenhum comando break es- tiver presente. Se i for igual a 4, flag receberd 0 valor 1 e, como nao ha nenhum comando break no fim desse case, a execugao continuara e a fungao error(flag) ser chamada. Se i fosse igual a 5, ertor(flag) teria sido chamada com o valor -1 em flag. O fato de os cases poderem ser executados em conjunto quando nenhum break estiver presente evita a duplicacao indesejavel de cédigo, resultando em um cédigo muito eficiente Note que os comandos associados a cada case nao sao blocos de cédigo mas, sim, seqiléncias de comandos. (Obviamente, o comando switch inteiro define um bloco.) Essa distingao técnica é importante apenas em certas situacdes. Por exemplo, o seguinte fragmento de cédigo esta errado e nao sera compilado por- Cap. 3 Comandos de controle\do programa i 2 que vocé nao pode declarar uma variavel local em uma seqiiéncia de comandos (vocé s6 pode declarar varidveis locais no inicio de um bloco.) /* Isto est4 incorreto. */ switch(c) { case 1: ant ty Embora um pouco estranho, vocé poderia adicionar uma varidvel local, como mos- trado aqui. Neste caso, a declaragao ocorte no inicio do bloco do switch. /* Embora estranho, isto estd correto. */ switch(c) { int t; case 1 Obviamente, vocé pode criar um bloco de cédigo como um dos comandos da seqiiéncia e declarar uma varidvel local dentro dele, como exibido aqui: /* Isto é correto. */ switch (c) ( case 1: { /* Cria bloco */ int t; ” C~ Completo ¢ Total Cap. 3 Comandos switch Aninhados Vocé pode ter um switch como parte de uma seqiiéncia de comandos de um outro switch. Mesmo se as constantes dos cases dos switchs interno e externo possuirem valores comuns, nao ocorrerao conflitos. Por exemplo, o seguinte frag- mento de c6digo é perfeitamente aceitavel: switch(x) ( case 1: switch(y) ( case 0: print£ ("erro de divisdo por zero"); break; case 1: process (x,y); } break; case 2 BB comandos de Iteragao Em C, e em todas as outras linguagens modernas de programagao, comandos de iteragao (também chamados lagos) permitem que um conjunto de instrucées seja executado até que ocorra uma certa condicao. Essa condicéo pode ser pre- definida (como no laco for) ou com o final em aberto (como nos lacos while e do-while). O Laco for O formato geral do laco for de C é encontrado, de uma forma ou de outra, em todas as linguagens de programacao baseadas em procedimentos. Contudo, em C, ele fornece flexibilidade e capacidade surpreendentes. A forma geral do comando for é for(inicializagao; condicéo; incremento) comando; ‘0 laco for permite muitas variagées. Entretanto, a inicializacdo é, geralmente, um comando de atribuigio que é usado para colocar um valor na varidvel de controle do laco. A condigéo 6 uma expressao relacional que determina quando © lago acaba. O incremento define como a varidvel de controle do laco varia cada Cap. 3 Comandos dé controle do programa % vez que o laco é repetido. Vocé deve separar essas trés segdes principais por pontos-e-virgulas. Uma vez que a condigio se torne falsa, a execugao do progra- ma continua no comando seguinte ao for. Por exemplo, o seguinte programa imprime os nimeros de 1-a 100 na tela #include void mainivoid) { int x; for ( x <= 100; xt+) printf ("8d ",x); ) No programa, x é inicialmente ajustado paré’1. Uma vez qite x é menor que 100, printf() 6 executado e x é incrementado em 1 ¢ testado para ver se ainda é menor ou igual a 100. Esse processo se repete até que x fique maior que 100; nesse ponto, 0 iaco termina. Nesse exemplo, x 6 a variavel de controle do laco, que é alterada e testada toda vez que o laco se repete. O seguinte exemplo é um laco for que contém muiltiplos comandos: for(x=100; x Z = x*x: printf("0 quadrado de 8d, %£",x, 2); ) Tanto a multiplicacdo de x por si mesmo como a fungao printf() sao executadas até que x seja igual a 65. Note que o laco é executado de forma inversa: x 6 inicia- lizado com 100 e sera subtraido de 5 cada vez que 0 laco se repetir. Nos lagos for, o teste condicional sempre é executado no topo do lao. Isso significa que 0 cédigo dentro do laco pode nao ser executado se todas as condicées forem falsas logo no inicio. Por exemplo, em x = 10; for(y=10; y!=x; ++y) printf("$a", y); printf("%d", y); /* este é 0 tnico comando printf() que executard */ © lago nunca executard, pois x ¢ y jé so iguais desde a sua entrada. Jé que a expressao condicional é avaliada como falsa, nem 0 corpo do laco nem a porcao de incremento do laco sao executados. Logo, y ainda tem o valor 10 e a saida é o mimero 10 escrito apenas uma vez na tela. 76 C= Completa e Total Cap. 3 Variagées do Lago for A discussao anterior descreveu a forma mais comum do laco for. Porém, C ofe- rece diversas variacdes que aumentam a flexibilidade e a aplicacao do lago for. Uma das variacdes mais comuns usa o operador virgula para permitir que duas ou mais varidveis controlem 0 lago. (Lembre-se de que vocé usa o operador virgula para encadear expresses numa configuragao “faca isto e isto”. Veja o Capitulo 2.) Por exemplo, as varidveis x e y controlam o seguinte laco e ambas sao inicializadas dentro do comando for: for(x=0, y=: x+y<10; ++x) { y = getchar(); y = y-'0'; /* subtrai o cédigo ASCII do caractere 0 Virgulas separam os dois comandos de inicializagav. Cada vez que x é incremen- tado, o laco repete e 0 valor de y é ajustado pela entrada do teclado. Tanto x como y devem ter valor correto para que o lago termine. Embora os valores de y sejam definidos pela entrada do teclado, y deve ser inicializado com 0 de forma que seu valor seja definido antes da avaliagio da expressao condicional. (Se y nao fosse definido, ele poderia conter um 10, tornando o teste condicional falso e impedindo a execucao do laco.) A funcao converge), mostrada a seguir, ilustra multiplas varidveis de controle de lago. A funcdo converge() expde uma string escrevendo os caracteres vindos de ambas as extremidades, convergindo no meio da linha especificada Isso requer um posicionamento do cursor em varios e desconexes pontos da tela Uma vez que C roda sob uma ampla variedade de ambientes, ele ndo define uma fungao de posicionamento do cursor. Porém, virtualmente, todo compilador C fornece uma, embora seu nome possa variar, O programa a seguir usa a fungao gotoxy(), do Borland, para posicionar o cursor. (Ela exige o cabecalho conio.h.) /* Versdo Borland. */ #include /* arquivo de cabecalho ndo-padrao */ void converge(int line, char *me! ge); Cap. 5 2 _ Comandos de controle di programa void main (void) £ converge(10, "Isto é um teste de converge()."); } /* Essa fungdéo mostra uma string iniciando do lado esquerdo da linha especificada. Ela escreve caracteres de ambas as extremidades, convergindo para o centro. */ void converge(int line, char *message) { int i, 3; for(isl, j=strlen(message); i #include /* arquivo de cabegalho nao-padrao */ #include void converge(int line, char *message}; void main(void) { converge(10, "Isto é um teste de converge()."); } /* Essa fungdo mostra uma string iniciando do lado esquerdo da linha especificada. Ela escreve caracteres de ambas as extremidades, convergindo para o centro. */ void converge(int line, char *message) ( int i, jr for(i=1, j-strlen(message); i Cap. 3 Comandos de controle do programa int sqrnum(int nun}; int readnum(void) ; int prompt (void) ; void main(void) { ant ty for (prompt (); t=readnum(); prompt ()) sqraum(t); ) prompt (void) t print€("Digite um mimero: "); return 0; } readnum (void) { int t; scanf("@d", &t); return t; sarnum(int num) { print£("%a\n", num*num) ; return num*num; } Olhe atentamente o laco for em main(). Note que cada parte do laco for 6 com- posta de chamadas a fungées que interagem com 0 usuario e léem um niimero inserido pelo teclado. Se o ntimero inserido for 0, 0 lago terminard, porque a expressao condicional ser falsa. Caso contrario, 0 numero é elevado ao quadra- do. Assim, esse laco for usa as porgdes de inicializagao e incremento de uma maneira nao tradicional, mas completamente valida. Uma outra caracteristica interessante do lago for é que partes da defini- a0 do laco nao precisam existix. De fato, nao ha necessidade de que uma ex- pressdo esteja presente em nenhuma das segées — as expressGes so opcionais. Por exemplo, esse lago sera executado até que o usuario insira 0 ndmero 123: BP ofor(x=0; x!=123; ) scanf("%a", &x); 80 C—Comipleto e Total 2 Cap. 3 Note que a porgao de incremento da definigao do for ests vazia. Isso significa que cada vez que 0 laco se repetir, x sera testado para verificar se é igual a 12 mas nenhuma atitude adicional sera tomada. Se vocé digitar 123 no teclado, po- rém, a condigao do laco se tornaré falsa e o laco terminara. Freqiientemente a inicializagao é feita fora do comando for. Isso geral- mente acontece quando a condicao inicial da variével de controle do laco for calculada por algum método complexo, como neste exemplo: gets(s); /* 16 uma string para s */ if(*s) x = strlen(s); /* obtém o comprimento da string */ else x = 10; for( ;x<10; } printf ("8a",x); $x: ) A secao de inicializagao foi deixada em branco e x é inicializado antes que o lago comece a ser executado. O Lago Infinito Embora vocé possa usar qualquer comando de laco para criar um laco infinito, 0 for 6 tradicionalmente usado para esse fim. J que nenhuma das trés expressdes que formam o laco for é obrigatéria, voce pode fazer um laco sem fim deixando a expressao condicional vazia, como aqui: Vocé pode ter uma inicializagéo e uma expressao de incremento, mas programa- dores C usam mais usualmente a construgao for(;) para significar um laco infinito. ;) print£("Esse lago serdé executado para sempre. \n De fato, a construgao for(;) nao garante um laco infinito porque o comando break de C, encontrado em qualquer lugar dentro do corpo de um lago, provoca um término imediato (break sera discutido mais adiante neste capitulo). O controle do programa, entao, continua no cédigo seguinte ao lago, como mostrado aqui: ch=/\0'; for(; 7) ch = getchar(}; /* obtém um caractere */ Cap. 3 Comandos de controle do programa) a if(e! ) A‘) break; /* sai do lago */ print£("vocé digitou um A‘); Esse lago serd executado até que 0 usuario digite um A. Lacos for sem Corpos Como definido pela sintaxe de C, um comando pode ser vazio. Isso significa que © corpo do laco for (ou qualquer outro laco) também pode ser vazio. Vocé pode usar esse fato para aumentar a eficiéncia de certos algoritmos e para criar laos para atraso de tempo. Remover espacos de uma “stream” de entrada é uma tarefa comum de programagao. Por exemplo, um programa de banco de dados pode permitir uma requisigdo do tipo “mostre todos os saldos menores que 400”. O banco de dados precisa ser alimentado com cada palavra separadamente, sem espagos. Isto é, 0 processador de entrada do banco de dados reconhece “show” mas nao “show”. O seguinte lago remove os primeiros espacos da stream apontada por str. for( ; *str == ' '; str++) ; a Como vocé pode ver, esse lago nao tem corpo — e nem precisa de um. Os lagos para atraso de tempo sao muito usados em programas. O seguinte cédigo mostra como criar um usando for: Wofor(t=0; t ude void pad(char *s, int length); void main (void) { char str[80]; strepy(str, "isto é um teste"); pad(str, 40); printf(*td", strlen(str)); /* Acrescenta espagos ao final da string. */ void pad(char *s, int length) £ int 1; 1 = strlen(s); /* encontra o comprimento */ while(1 100); Talvez 0 uso mais comum do lago do-while seja em uma rotina de selecdo por menu. Quando 0 usuario entra com uma resposta valida, ela é retornada como © valor da fungdo. Respostas invalidas provocam uma repetigao do lago. O se- guinte cédigo mostra uma versio melhorada do menu do verificador de orto- grafia que foi desenvolvido anteriormente neste capitulo: void menu (void) { char ch; print£("l. verificar ortografia\n"); printf("2. Corrigir Erros de Ortografia\n"); printf£("3. Mostrar Erros de Ortografia\n"); printé(" Digite sua escolha: do ( ch = getchar(); /* 1@ do teclado a selecdo */ switch(ch) { case ‘1’: check_spelling(); break; Cap:5 8 Cattandos de- controle do:programa | \i@ |” é 85 case ‘2': correct_errors(); break; case ‘3': display_errors (); break; ) } while(ch!= y && ch 2! && chi='3'); Aqui, 0 laco do-while é uma boa escolha, porque vocé sempre deseja que uma fungdo do menu execute ao menos uma vez. Depois que as opcdes forem mos- tradas, 0 programa sera executado até que uma opcao valida seja selecionada. I comandos de Desvio C tem quatro comandos que realizam um desvio incondicional: return, goto, break e continue. Destes, vocé pode usar return e goto em qualquer lugar em seu programa. Vocé pode usar os comandos break e continue em conjunto com qualquer dos comandos de lago. Como discutido anteriormente neste capitulo, vocé também pode usar 0 break com switch. O Comando return O comando return é usado para retornar de uma funcao. Ele é um comando de desvio porque faz com que a execugao retorne (salte de volta) ao ponto em que a chamada a fungao foi feita. Se return tem um valor associado a ele, esse valor €0 valor de retorno da fungao. Se nenhum valor de retorno é especificado, as- sume-se que apenas lixo é retornado. (Alguns compiladores C irao automatica- mente retornar 0 se nenhum valor for especificado, mas nao conte com isso.) A forma geral do comando return é return expres Lembre-se de que a expresséo é opcional. Entretanto, se estiver presente, ela se tornard © valor da fungao. Vocé pode usar quantos comandos return quiser dentro de uma fungio. Contudo, a fungio parara de executar to logo ela encontre o primeiro return. A} que finaliza uma fungao também faz com que a fungio retorne. E o mesmo que um return sem nenhum valor especificado. 86 C —Completo e Total cap. 3 Uma funcao declarada como void nao pode ter um comando return que especifique um valor. (Como uma fungao void nao retorna valor, nao tem sentido que 0 comando return especifique um valor nas fungdes void). Veja 0 Capitulo 6 para mais informacoes sobre return. © Comando goto Uma vez que C tem um rico conjunto de estruturas de controle e permite um controle adicional usando break e continue, ha pouca necessidade do goto. A grande preocupacio da maioria dos programadores sobre o goto é a sua ten- dencia de tornar os programas ilegiveis. Entretanto, embora 0 goto tenha sido desencorajado alguns anos atras, ele tem recentemente polido um pouco a sua imagem manchada. Nao ha nenhuma situagdo na programacao que necessite do goto. Apesar disso, contudo, goto é uma conveniéncia que, se usada prudente- mente, pode ser uma vantagem em certas situagdes na programagao. Sendo as- sim, goto nao é usado fora desta segao. © comando goto requer um rétulo para sua operacao. (Um rétulo é um identificador vlido em C seguido por dois-pontos.) O padrao ANSI refere-se a esse tipo de construgdo como uma sentenca de rotulo. Além disso, o rétulo tem de estar na mesma fungio do goto que o usa — vocé nao pode efetuar desvios entre fungdes. A forma geral do comando goto é goto rétulo; rotulo: onde rétulo 6 qualquer rétulo valido existente antes ou depois do goto. Por exem- plo, vocé poderia criar um lago de 1 até 100 usando goto e um rotulo, como mostrado aqui: x= 1; loop1: if(x<100) gote loop; O Comando break © comando break tem dois usos. Vocé pode usé-lo para terminar um case em um comando switch (abordado anteriormente na secdo sobre 0 switch, neste Cap. 3 ‘Comandos de controle do programa a7 capitulo). Voce também pode usé-lo para forcar uma terminagao imediata de um laco, evitando o teste condicional normal do lago. Quando 0 comando break é encontrado dentro de um laco, o laco é imediatamente terminado e o controle do programa retorna no comando seguinte ao lago. Por exemplo, #include void main(void) ( int ty for(t=0; t<100; t++) ( printf ("td ", t); if(t==10) break; } d escreve os nuimeros de 1 até 10 na tela. Entao, 0 lago termina porque o break provoca uma saida imediata do laco, desrespeitando o teste condicional t<100. Os programadores geralmente usam 0 comando break em lagos em que uma condigao especial pode provocar uma terminagao imediata. Por exemplo, aqui, o pressionamento de uma tecla pode parar a execucao da funcao look_up() look_up(char *name) t do ( /* procura nomes ... */ if (kbhit()) break; } while(! found) ; /* processa a concordancia */ } A funcao kbhit() retorna 0 se vocé nao pressionar uma tecla. Caso contrario, ela retorna um valor nao-zero. Devido as grandes diferengas entre ambientes com- putacionais, o padrao ANSI nao define kbhit0, mas € quase certo que vocé a tem (ou alguma com um nome um pouco diferente) fornecida com seu compilador. Um comando break provoca uma saida apenas do laco mais interno. Por exemplo, 88 C— Completa e Totat Cap.'3 for(t=0; t 90 C— Complete e Total Cap. 3 void main(void) { char s[80], *st int space; print£(*Digite uma string: "); gets(s); str = for(space=0; *str; str++) { if(*str != spacet+; ') continue; ) printf£("%d espacos\n", space); } Cada caractere é testado para ver se é um espaco. Se ndo 6, 0 comando continue forga o for a iterar novamente. Se o caractere é um espaco, space é incrementada. O seguinte exemplo mostra que vocé pode usar continue para apressar a saida de um laco, forcando 0 teste condicional a ser realizado mais cedo. void code (void) ( char done, ch; done = 0; while(!done) ( ch = getchar(); if(ch=='$") ( done continu } putchar(ch+1); /* desloca o alfabeto uma posicao */ } Esta funcdo codifica uma mensagem deslocando todos os caracteres uma letra acima. Por exemplo, um A se tornaria um B. A fungao terminaré quando um $ for digitado. Depois que um $ for inserido, nenhuma saida adicional ocorrerd porque 0 teste condicional, trazido a efeito pelo continue, encontraré done como sendo verdadeiro e provocard a saida do laco. Cap. 3 Comandos de controle do programa 1 Ei comandos de Expressées O Capitulo 2 abrange completamente as expressdes de C. Porém, alguns pontos especiais sio mencionados aqui. Lembre-se de que um comando de expresso & simplesmente uma expressao valida em C seguida por um ponto-e-virgula, como em /* uma chamada a uma fungao */ /* um comando de atribuigao */ b+f(); /* um comando valido, que nado faz nada */ fl /* um comando vazio */ O primeiro comando de expresso executa uma chamada a uma fungao. O se- gundo é uma atribuicao. O terceiro elemento é estranho, mas é avaliado pelo compilador C, porque a fungéo £0 pode realizar alguma tarefa necessaria. O Ultimo exemplo mostra que C permite que um comando seja vazio (algumas vezes chamado de comando nulo). @ Blocos de Comandos Blocos de tomandos sao simplesmente grupos de comandos relacionados que sdo tratados como uma unidade. Os comandos que constituem um bloco estao logicamente conectados. Um bloco comeca com um { e termina com um } cor- respondente. Programadores normalmente usam blocos de comandos para criar um objeto multicomandos para algum outro comando, como o if. No entanto, vocé pode pér um bloco de comandos em qualquer lugar onde seja possivel a colocagio de um outro comando qualquer. Por exemplo, este é um cédigo em C perfeitamente valido (embora nao usual): #include void main(void) int i; { /* um bloco de comandos */ i = 120; printf£("$d", i); Tixeon Matrizes e Strings — SS Uma matriz é uma colegao de varidveis do mesmo tipo que é referenciada por um nome comum. Um elemento especifico em uma matriz acessado por meio de um indice. Em C, todas as matrizes consistem em posigées contiguas na me- méria. O endereco mais baixo corresponde ao primeiro elemento e 0 mais alto, ao ultimo elemento. Matrizes podem ter de uma a varias dimensdes. A matriz mais comum em C é a de string, que é simplesmente uma matriz de caracteres terminada por um nulo. Essa abordagem a strings dé a C maior poder e eficiéncia que as outras linguagens Em C, matrizes e ponteiros estéo intimamente relacionados; uma dis- cussdo sobre um deles normalmente refere-se a0 outro. Este capitulo focaliza matrizes, enquanto 0 Capitulo 5 examina mais profundamente os ponteiros. Vocé deve ler ambos para entender completamente essas construcdes importantes de C. BB Matrizes Unidimensionais A forma geral para declarar uma matriz unidimensional é tipo nome_var{tamanhol; Como outras varidveis, as matrizes devem ser explicitamente declaradas para que 0 compilador possa alocar espaco para elas na memoria. Aqui, tipo declara © tipo de base da matriz, que é 0 tipo de cada elemento da matriz; tamanho define quantos elementos a matriz iré guardar. Por exemplo, para declarar um matriz de 100 elementos, chamada balance, e do tipo double, use este comando: 9 Cap. 4 Matrizes ¢ strings 93 BH double balance[100]; Em C, toda matriz tem 0 como o indice do seu primeiro elemento. Portanto, quando vocé escreve ® char pli); vocé esti declarando uma matriz de caracteres que tem dez elementos, p[0] até pI9]. Por exemplo, o seguinte programa carrega uma matriz inteira com os nu- meros de 0 a 99: void main(void) { int x[100]; /* isto reserva 100 elementos inteiros */ int tr tet) xft] = t ‘A quantidade de armazenamento necessério para guardar uma matriz esta dire- tamente relacionada com seu tamanho e seu tipo. Para uma matriz unidimen- sional, 0 tamanho total em bytes calculado como mostrado aqui: total em bytes = sizeof(tipo) * tamanho da matri: C nao tem verificagdo de limites em matrizes, Voce poderia ultrapassar © fim de uma matriz e escrever nos dados de alguma outra varidvel ou mesmo no cddigo do programa. Como programador, é seu trabalho prover verificagao dos limites onde for necessario. Por exemplo, este cédigo compilara sem erros, mas é incorreto, porque o lago for faré com que a matriz count ultrapasse seus limites. int count [10], /* isto faz com que count seja u: for(i=0; icl00; i++) countlil= i; la */ Matrizes unidimensionais sao, essencialmente, listas de informagdes do mesmo tipo, que sao armazenadas em posicées contiguas da meméria em uma ordem de indice. Por exemplo, a Figura 4.1 mostra como a matriz a apareceria na me- méria se ela comegasse na posicao de meméria 1000 e fosse declarada como mostrado aqui: 4 C ~Completo e Total Cap. 4 @ char a7); Elemento af] all], a2]. a3]. ald] als] alo] Endereco 1000 1001 1002 1003 1004 1005 1006 Figura 4.1. Uma matriz de sete elementos comegando na posicao 1000. @ Gerando um Ponteiro para uma Matriz Vocé pode gerar um ponteiro para 0 primeiro elemento de uma matriz simples- mente especificando o nome da matriz, sem nenhum indice. Por exemplo, dado @ int sample(10]; vocé pode gerar umi ponteiro para o primeiro elemento simplesmente usando 0 nome sample. Por exemplo, 0 seguinte fragmento atribui a p o endereco do primeiro elemento de sample: Peo sample [10]; p = sample; Vocé também pode especificar 0 endereco do primeiro elemento de uma matriz. usando 0 operador &. Por exemplo, sample e &sample[0] produzem os mesmos resultados. Porém, em cédigos C escritos profissionalmente, vocé quase nunca veré algo como &sample[0] @ Passando Matrizes Unidimensionais para Funcdes Em C, vocé nao pode passar uma matriz inteira como um argumento para uma fungao. Voce pode, porém, passar um ponteiro para uma matriz para uma fun- cdo, especificando 0 nome da matriz sem um indice. Por exemplo, o seguinte fragmento de programa passa o endereco de i para funcl(: void main(void) ( int if{10); Cap. 4 Matrizes e strings 5 funci (i); } Se uma fungao recebe uma matriz unidimensional, vocé pode declarar o para- metro formal em uma entre trés formas: como um ponteiro, como uma matriz dimensionada ou como uma matriz ndo-dimensionada. Por exemplo, para rece- ber i, uma fungao chamada funcl1() pode ser declarada como void funci(int *x) /* ponteiro */ ou void funci(int x[10]) /* matriz dimensionada */ ) ou finalmente como void funcl(int x[]) /* matriz n&o-dimensionada */ 96 C — Completa e Toll Capi Todos os trés métodos de declaracao produzem resultados idénticos, porque cada um diz ao compilador que um ponteiro inteiro vai ser recebido. A primeira declaracao usa, de fato, um ponteiro. A segunda emprega a declara- sao de matriz padrao. Na ultima versao, uma verso modificada de uma decla- racao de matriz simplesmente especifica que uma matriz do tipo int, de algum tamanho, serd recebida. Como vocé pode ver, 0 comprimento da matriz ndo importa a fungdo, porque C nao realiza verificagao de limites. De fato, até onde diz respeito ao compilador, void funci(int x[32]) ) também funciona, porque 0 compilador C gera um cédigo que instrui funcl() a receber um ponteiro — ele nao cria realmente uma matriz de 32 elementos @ sir gs © uso mais comum de matrizes unidimensionais ¢ como string de caracteres. Lembre-se de que, em C, uma string é definida como uma matriz de caracteres que é terminada por um nulo. Um nulo é especificado como ‘\0' e geralmente 6 zero. Por essa razao, voce precisa declarar matrizes de caracteres como sendo um caractere mais longo que a maior string que elas devem guardar. Por exem- plo, para declarar uma matriz str que guarda uma string de 10 caracteres, vocé escreveria @ char ser(11); Isso reserva espaco para o nulo no final da string. Embora C nao tenha o tipo de dado string, ela permite constantes string. Uma constante string € uma lista de caracteres entre aspas. Por exemplo, “alo aqui” Vocé nao precisa adicionar 0 nulo no final das constantes string manualmente — 0 compilador C faz isso por vocé automaticamente. C suporta uma ampla gama de funcdes de manipulacdo de strings. As mais comuns sao: Cap. 4 Matrizes ¢ strings : a Nome Funcao strepy(s1, s2) Copia s2 em sl streai(s1, s2) Concatena s2 ao final de s1. strlen(s1) Retorna o tamanho de st stremp(s1, s2)__Retorna 0 se s1 e s2 sao iguais; menor que 0 se s1s2. strchr(s1, ch) Retorna um ponteiro para a primeira ocorréncia de ch em sl strstr(s1, s2) Retorna um ponteiro para a primeira ocorréncia de s2 em sl. Essas funcdes usam o cabecalho padrio STRING.H. (Essas e outras fun- es de string sao discutidas em detalhes na Parte 2.) O seguinte programa ilustra © uso dessas funcdes de string: #include include void main(void) ‘ char s1[80], s2[(80]; gets (sl); gets (s2); print£("comprimentos: %d %d\n", strlen(sl), strli s2)); if(!stremp(si, s2)) printf ("As strings sao iguais\n"); streat(sl, s2); printf(*"%s\n", sl); strcpy(sl, "Isso é um teste.\n"); printf (s1); if(strchr("alo", ’o')) printf£("o esté em alo\n"); if(strstr("ola aqui", "ola*)) printf£("ola encontrado"); ) Se vocé rodar esse programa e digitar as strings "alo" e "alo", a safda seré comprimentos: 33 As string sao iguais aloalo Isso 6 um teste. 0 esté em alo ola encontrado 98 C= Complete ¢ Toial eB Cop. 4 Lembre-se de que stremp() retorna falso se as strings sdo iguais. Assegure-se de usar 0 operador ! para reverter a condicao, como mostrado, se vocé estiver tes- tando igualdade. Wl Matrizes Bidimensionais C suporta matrizes multidimensionais. A forma mais simples de matriz multi- dimensional é a matriz bidimensional — uma matriz de matrizes unidimensio- nais. Para declarar uma matriz bidimensional de inteiros d de tamanho 10,20, vocé escreveria Hint a(i0j (20); Preste bastante atencao a declaracao. Muitas linguagens de computador usam virgulas para separar as dimensGes da matriz; C, em contraste, coloca cada di- menso no seu préprio conjunto de colchetes. Similarmente, para acessar o ponto 1,2 da matriz d, vocé usaria @ aie; O seguinte exemplo carrega uma matriz bidimensional com os mimeros de 1 a 12 e escreve-os linha por linha #include void main(v « int t, i, num(3](4]; ) for(t=0; t<3; ++t) for(i=0; i<4; ++i) num(t] {i} = (t*4)+i+1; /* agora escreva-os */ O; ted; +4) { for(i=0; i #include #include /* Um banco de dados simples para notas de alunos. */ #define CLASSES 3 Cape Si Matrizes ¢ strings #define GRADES 30 int grade [CLASSES] [GRADES] ; void enter_grades (void); int get_grade(int num); void disp_grades(int g[] [GRADES]); void main(void) { char ch, str [80]; for(s;) 4 do { print£(*"(D)igitar notas\n"); print£(" (Mostrar notas\n"); print£("(S)air\n"); gets(str); ch = toupper(*str); } while(ch!='D’ && ch!='M’ && ch!='S'); switch(ch) { case 'D': enter_grades(); break; case 'M’: disp_grades (grade) ; break; case 'S': exit (0); } /* Digita a nota dos alunos. */ void enter_grades (void) int t, i; } t #define MAX 100 #define LEN 80 char text MAX] (LEN] ; /* Um editor de texto muito simples. */ void main(void) register int t, i, j; print£(*Digite uma linha vazia para sair.\n"); for(t=0; t #include void mail ( int x; int *pl, *p2: (void) pl = &x; p2 = pl; rintf("%p", p2); /* escreve o endereco de x, ndéo seu valor! */ } Agora, tanto pl quanto p2 apontam para x. O endereso de x é mostrado, usando 0 modificador de formato de printf() op, que faz com que printf apresente um endereco no formato usado pelo computador host. Aritmética de Ponteiros Existem apenas duas operagies aritméticas que podem ser usadas com ponteiros: adigao e subtracgao. Para entender o que ocorre na aritmética de ponteiros, con- sideremos p1 um ponteiro para um inteito com o valor atual 2000. Assuma, também, que os inteiros s4o de 2 bytes. Apés a expresso Cap. 5 Ponteiros 17 Bovis; pl contém 2002, nao 2001. Cada vez que pl é incrementado, ele aponta para 0 proximo inteiro. O mesmo é verdade nos decrementos. Por exemplo, assumindo que p1 tem o valor 2000, a expressao @ pi--: faz com que p1 receba o valor 1998. Generalizando a partir do exemplo anterior, as regras a seguir governam a aritmética de ponteiros. Cada vez que um ponteiro é incrementado, ele aponta para a posicdo de meméria do proximo elemento do seu tipo base. Cada vez que é decrementado, ele aponta para a posicao do elemento anterior. Com pon- teiros para caracteres, isso freqiientemente se parece com a aritmética “normal”. Contudo, todos os outros ponteiros incrementam ou decrementam pelo tamanho do tipo de dado que eles apontam. Por exemplo, assumindo caracteres de 1 byte € inteiros de 2 bytes, quando um ponteiro para caracteres é incrementado, seu valor aumenta em um. Porém, quando um ponteiro inteiro é incrementado, seu valor aumenta em dois. Isso ocorre porque os ponteiros sio incrementados e decrementados relativamente ao tamanho do tipo base de forma que ele sempre aponta para o préximo elemento. De maneira mais geral, toda a aritmética de pon- teiros é feita relativamente ao tipo base do ponteiro, para que ele sempre aponte para o elemento do tipo base apropriado. A Figura 5.2 ilustra esse conceito. Vocé nao esta limitado a apenas incremento e decremento. Vocé pode somar ou subtrair inteiros de ponteiros. A expressao B opi = pl + 12; faz p1 apontar para 0 décimo segundo elemento do tipo p1 adiante do elemento que ele esta atualmente apontando. Além de adicao e subtracao entre um ponteiro e um inteiro, nenhuma outra operacado aritmética pode ser efetuada com ponteiros. Especificamente, vocé nao pode multiplicar ou dividir ponteiros; nao pode aplicar os operadores de deslocamento e de mascaramento bit a bit com ponteiros; e néo pode adicionar ou subtrair 0 tipo float ou 0 tipo double a ponteiros. 118 C-— Completo e Total Cap. 5 char *ch=3000; int *i=3000; ch 3000 i ch+1 3001 ch+2 3002 i+] ch+3 | 3003 | ched 4 ee rrr ches 3005 eee Memoria Figura 5.2. Toda aritmética de ponteiros ¢ relativa a seu tipo base. Comparacao de Ponteiros E possivel comparar dois ponteiros em uma expressdo relacional. Por exemplo, da- dos dois ponteiros p e q, 0 fragmento de cédigo seguinte é perfeitamente valido. @ ittp #include # define SIZ 50 Cap. 5 Ponteiros 119 void push(int int pop(void): int *tos, *pl, stack({SIZE]; void main(void) { int value; tos = stack; /* faz tos conter o topo da pilha */ pl = stack; /* inicializa pl */ do ( printf(*Digite o valor: "); scanf("%d", &value); if(value!=0) push(value); else printf ("valor do topo é #d\n", pop()); } while(value!=-1); ) void push(int i) c pitt; if (pl==(tos+SIZE)) ( print£("Estouro da pilha"); exit (1); *pl = ij } pop (void) ( if(pl==tos) ( £("Estouro da pilha"); pl--; return *(pi+l); } Vocé pode ver que a meméria para a pilha é fornecida pela matriz stack. O ponteiro p1 ¢ ajustado para apontar para o primeiro byte em stack. A pilha é realmente acessada pela varidvel p1. A varidvel tos contém o endereco do topo da pilha. O valor de tos evita que se retirem elementos da pilha vazia. Uma vez 120 C— Completore Total Cap. 5 que a pilha tenha sido inicializada, push0) e pop) podem ser usadas como uma pilha de inteiros. Tanto push) como pop() realizam um teste relacional com o ponteiro p1 para detectar erros de limite. Em push0), p1 é testado junto ao final da pilha adicionando-se SIZE (0 tamanho da pilha) a tos. Em pop0, p1 é veri- ficado junto a tos para assegurar que nao se retirem elementos da pilha vazia. Em pop0, os parénteses sio necessdrios no comando return. Sem eles, © comando seria BW return «pi + que retornaria o valor da posicao pl mais um, nao o valor da posicéo p1+1. Vocé deve usar parénteses para garantir a ordem correta de avaliagao quando usar ponteiros. I Ponteiros e Matrizes Ha uma estreita relagao entre ponteiros e matrizes. Considere este fragmento de programa: char str(80], *pl; pl = str; Aqui, pil foi inicializado com 0 endereco do primeiro elemento da mattiz str. Para acessar 0 quinto elemento em str, teria de ser escrito @ seria ou B *ipi+4) Os dois comandos devolvem o quinto elemento. Lembre-se de que matrizes co- mecam em 0, assim, deve-se usar 4 para indexar str. Também deve-se adicionar 4 ao ponteiro p1 para acessar 0 quinto elemento porque p1 aponta atualmente para o primeiro elemento de str. (Recorde-se que um nome de uma matriz sem um indice retorna o endere¢o inicial da matriz, que é o primeiro elemento.) C fornece dois métodos para acessar elementos de matrizes: aritmética de ponteiros e indexagao de matrizes. Aritmética de ponteiros pode ser mais répida que indexacao de matrizes. J4 que velocidade 6 geralmente uma conside- ragdo em programagao, programadores em C normalmente usam ponteiros para acessar elementos de matrizes. Cap. 5 Ponteiros 121 Essas duas versdes de putstr() — uma com indexagao de matrizes e uma com ponteiros — ilustram como vocé pode usar ponteiros em lugar de indexacao de matrizes. A funcao putstr() escreve uma string no dispositivo de saida padrao. /* Indexa s como uma matriz. */ void puts(char *s) t register int t; for(t=0; s[t}; +#t) putchar(s[tl); /* Acessa s como um ponteiro. */ void putstr(char *s) { while(*s) putchar(*s++); } A maioria dos programadores profissionais em C acharia a segunda versao mais facil de ler e entender. Na realidade, a versao com ponteiros €a forma pela qual rotinas desse tipo sio normalmente escritas em C. Matrizes de Ponteiros Ponteiros podem ser organizados em matrizes como qualquer outro tipo de dado. A declaracao de uma matriz de ponteiros int, de tamanho 10, 6 BH oince *x(10); Para atribuir o endereco de uma varidvel inteira, chamada var, ao terceiro ele- mento da matriz de ponteiros, deve-se escrever @ xi2) = evar; Para encontrar o valor de var, escreve-se B osx2) Se for necessario passar uma matriz de ponteiros para uma funcdo, pode ser usado 0 mesmo método que é utilizado para passar outras matrizes — simples- mente chame a fungao com o nome da matriz sem qualquer indice. Por exemplo, uma funcao que recebe a matriz x se parece com isto: 12 C— Complete e Total Cop. 5 void display_array(int *q[]) t int ty for (t=0; t<10; tt+) printf("td ", *altl); Lembre-se de que q nao é um ponteiro para inteiros; q é um ponteito para uma matriz de ponteiros para inteiros. Portanto, 6 necessario declarar 0 parametro q como uma matriz de ponteiros para inteiros, como mostrado no cédigo anterior. Ela nao pode ser simplesmente declarada como um ponteiro para inteiros, porque nao é isso 0 que ela é. Matrizes de ponteiros sdo usadas normalmente como ponteiros para strings. Voeé pode criar uma funcao que exiba uma mensagem de erro, quando é dado seu ntimero de cédigo, como mostrado aqui: void syntax_error(int num) static char ‘eri “Arquivo nao pode ser aberto\n", “Erro de leitura\n", “Erro de escrita\n", Falha da midia\n" Mf printé(*"%s", ery (num]); ) A matriz err contém ponteiros para cada string. Como vocé pode ver, printf) dentro de syntax_error() é chamada com um ponteiro de caracteres que aponta para uma das varias mensagens de erro indexadas pelo nimero de erro passado para a funcio. Por exemplo, se for passado o valor 2, a mensagem Erro de escrita € apresentada. Observe que o argumento da linha de comandos argv é uma matriz de ponteiros a caracteres. (Veja o Capitulo 6) a Indirega@o Multipla Vocé pode ter um ponteiro apontando para outro ponteiro que aponta para 0 valor final. Essa situagdo é chamada indirec@o muiltipla, ou ponteiros para ponteiros. Cap. 5 Ponteiros 123 Ponteiros para ponteiros podem causar confusdo. A Figura 5.3 ajuda a esclarecer © conceito de indirecao miltipla. Como vocé pode ver, o valor de um ponteiro normal é 0 enderego de uma varidvel que contém o valor desejado. No caso de um ponteiro para um ponteiro, o primeiro ponteiro contém o endereco do se- gundo, que aponta para a varidvel que contém o valor desejado. Ponteiro Variavel enderego, |---| valor | Indirecao Simples Ponteiro Ponteiro: Variavel Indirecdo Maltipla Figura 5.3 Indirecio simples e miltipla. A indiregéo miiltipla pode ser levada a qualquer dimensao desejada, mas raramente é necessario mais de um ponteiro para um ponteiro. De fato, indiregao excessiva é dificil de seguir e propensa a erros conceituais. (Nao con- funda indireg4o miltipla com listas encadeadas.) 4 NOTA: Nao confunda a miltipla indirecio com estruturas de dados de alto nivel, void main(void) int x, *p, ta: x = 10; D = &X; @ = 6p; print£("%d", **q); /* imprime o valor de x */ Aqui, p é declarado como um ponteiro para um inteiro e q, como um ponteiro para um ponteiro para um inteiro. A chamada a printf() imprime 10 na tela. Hl Inicializagao de Ponteiros Apés um ponteiro ser declarado, mas antes que lhe seja atribuido um valor, ele contém um valor desconhecido. Se vocé tentar usar um ponteiro antes de Ihe dar um valor, provavelmente quebraré nao apenas seu programa como também o sistema operacional de seu computador — um tipo de erro muito desagradavel! Ha uma importante convengao que a maioria dos programadores de C segue quando trabalha com ponteiros: um ponteiro que atualmente nao aponta para um local de meméria valido recebe o valor nulo (que é zero). Por convencao, qualquer ponteiro que é nulo implica que ele ndo aponta para nada e nao deve ser usado. Porém, apenas 0 fato de um ponteiro ter um valor nulo nao o torna “seguro”. Se vocé usar um ponteiro nulo no lado esquerdo de um comando de atribuicao, ainda correré 0 risco de quebrar seu programa ou o sistema operacional. Como um ponteiro nulo é assumido como sendo nao usado, voce pode utilizar o ponteiro nulo para tornar faceis de codificar e mais eficientes muitas rotinas. Por exemplo, vocé poderia usar um ponteiro nulo para marcar o final de uma matriz de ponteiros. Uma rotina que acessa essa matriz sabe que chegara ao final ao encontrar o valor nulo. A funcao search(), mostrada aqui, ilustra esse tipo de abordagem. Cap.'5 Ponteiros 25 /* procura um nome */ search(char *p{], char ‘name) register int t; for ( p(t}; ++t) if(istremp(p[t], name)) return t; return /* n&o encontrado */ } O laco for dentro de search() é executado até que seja encontrada uma coinci- déncia ou um ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condigao de controle do laco falha quando ele é atingido. E uma pratica comum entre programadores em C inicializar strings. Voce viu um exemplo disso na funcao syntax_error(), na secdo “Matrizes de Ponteiros”. Uma outra variagao no tema de inicializagao é 0 seguinte tipo de declaragao de string: Como vocé pode observar, 0 ponteiro p ndo é uma matriz. A razdo pela qual esse tipo de inicializagao funciona deve-se 4 maneira como o compilador opera. Todo compilador C cria o que é chamada de tabela de string, que é usada inter- namente pelo compilador para armazenar as constantes string usadas pelo pro- grama. Assim, 0 comando de declaracao anterior coloca o endereco de “alo mundo”, armazenado na tabela de strings no ponteiro p. p pode ser usado por todo o programa como qualquer outra string. Por exemplo, 0 programa que se- gue é perfeitamente valido: ar *p = “alo mundo #include #include char *p = "alo mundo"; void main(void) { register int t; /* imprime o contetido da string de tras para frente */ printf (p); for(t=strlen(p)-1; t>-1; t--) printf£("%e", plt]); 126 C—Completo e Total Cap. 5 @ Ponteiros para Funcédes Um recurso confuso, mas poderoso de C, é 0 ponteiro para funcao. Muito embora uma fungao nao seja uma variavel, ela tem uma posicao fisica na meméria que pode ser atribuida a um ponteiro. O enderego de uma fungao é 0 ponto de entrada da fungao. Portanto, um ponteiro de funcao pode ser usado para chamar uma fungio. Para entender como funcionam os ponteiros de fungdes, vocé deve co- nhecer um pouco como uma funcao é compilada e chamada em C. Primeiro, quando cada fungio é compilada, 0 cédigo-fonte é transformado em cédigo-objeto ¢ um ponto de entrada é estabelecido. Quando ¢ feita uma chamada a funcéo, enquanto seu programa est sendo executado, é efetuada uma chamada em linguagem de maquina para esse ponto de entrada. Portanto, se um ponteiro contém o enderego do ponto de entrada de uma funcao, ele pode ser usado para chamar essa fungio. © enderego de uma fungio € obtido usando 0 nome da fungio sem parénteses ou argumentos. (Isso é semelhante 4 maneira como o endereco de uma matriz é obtido quando apenas o nome da matriz, sem indices, 6 usado.) Para ver como isso é feito, estude o programa seguinte, prestando bastante aten- cdo as declaragées: #include #include void check(char *a, char *b, int (*cmp) (const char *, const char *)); void main(void) ( char s1(80], s2[80]; int (*p)(); p = stremp; gets(s1); gets(s2); check(si, s2, p); void check(char *a, char *b, int (*cmp) (const char *, const char *)} { 128 C— Completo ¢ Totad Cap. 5 #include #include #include #include "string.h" void check(char *a, char *b, int (*cemp) (const char *, const char*)); int numemp(const char *a, const char *b); void main(vo £ char s1(80], s2(801; gets (sl); gets (s2); if (isalpha(*s1)) check(sl, s2, strcmp); else check(s1, s2, numemp’ ) void check(char *a, char *b, int (*cmp) (const char *, const char*)) printé£("testando igualdade\n"); if(!(*emp) (a, b)) print£ ("igual"); else printf ("diferente"); } numemp(const char *a, const char *b) ‘ if (atoi(a)==atoi(b)) return 0; else return 1; Bas Funcées de Alocacgao Dinamica em C Ponteiros fornecem 0 suporte necessario para 0 poderoso sistema de alocacéo dinamica de C. Alocagio dinémica é 0 meio pelo qual um programa pode obter meméria enquanto est em execucao. Como vocé sabe, varidveis globais tem o armazenamento alocado em tempo de compilagao. Varidveis locais usam a pilha. Cap.5 Ponteiros 129 No entanto, nem variaveis globais nem locais podem ser acrescentadas durante © tempo de execugao. Porém, haveré momentos em que um programa precisard usar quantidades de armazenamento varidveis. Por exemplo, um processador de texto ou um banco de dados aproveita toda a RAM de um sistema. Porém, como a quantidade de RAM varia entre computadores esses programas nao poderao usar varidveis normais. Em vez disso, esses e outros programas alocam meméria, conforme necessario, usando as funcées do sistema de alocacao dinamica de C A meméria alocada pelas funcdes de alocagio dinamica de C é obtida do heap — a regido de memGria livre que esta entre seu programa e a area de arma- zenamento permanente e a pilha. Embora o tamanho do heap seja desconhecido, ele geralmente contém uma quantidade razoavelmente grande de meméria livre. © coracéo do sistema de alocagao dinamica de C consiste nas fungdes malloc() e free(. (Na verdade, C tem diversas outras fungées de alocacae dina- mica, mas essas duas so as mais importantes.) Essas fungdes operam em con- junto, usando a regiao de meméria livre para estabelecer e manter uma lista de armazenamento disponivel. A funco malloc() aloca memoria e free() a libera. Isto 6, cada vez que é feita uma solicitacdo de meméria por malloc(), uma porgio da meméria livre restante é alocada. Cada vez, que é efetuada uma chamada a free() para liberacdo de memoria, a meméria é devolvida ao sistema. Qualquer programa que use essas fungdes deve incluir o cabecalho STDLIB.H. A fungao malloc() tem este protstipo: void *malloc(size_t niimero_de_bytes); Aqui, rimero_de_bytes 6 0 numero de bytes de meméria que vocé quer alocar. (O tipo size_t é definido em STDLIB.H como — mais ou menos — um inteiro sem sinal.) A fungao malloc() devolve um ponteiro do tipo void, o que significa que vocé pode atribui-lo a qualquer tipo de ponteiro. Apés uma chamada bem-sucedida, malloc() devolve um ponteiro para o primeiro byte da regiao de meméria alocada do heap. Se nao ha meméria disponivel para satisfazer a requi- sigao de maliocQ, ocorre uma falha de alocacao e malloc() devolve um nulo. O fragmento de cédigo mostrado aqui aloca 1000 bytes de meméria. char *p; Pp = malloc(1000); /* obtém 1000 bytes */ Apés a atribuicdo, p aponta para o primeiro dos 1000 bytes de memoria livre. O préximo exemplo aloca espago para 50 inteiros. Observe o uso de sizeof para assegurar portabilidade. int *p; p = malloc (50*sizeof(int)); 130 C—Completo e Total Cop. 5 Como o heap nao é infinito, sempre que alocar meméria, vocé deve testar o valor devolvido por malloc(), antes de usar o ponteiro, para estar certo de que nao & nulo. Usar um ponteiro nulo quase certamente travara o computador. A maneira adequada de alocar meméria é ilustrada neste fragmento de cédigo: if(!(p=malloc(100)) { printf ("sem meméria.\ exit (1); } Obviamente, vocé pode substituir algum outro tipo de manipulador de erro em lugar do exit(). Apenas tenha certeza de nao usar o ponteiro p se ele for nulo. A funco free() 6 0 oposto de malloc(), visto que ela devolve meméria previamente alocada ao sistema. Uma vez, que a memGéria tenha sido liberada, ela pode ser reutilizada por uma chamada subseqiiente a malloc(). A fungdo free() tem este prototipo: void free(void *p); Aqui, p é um ponteiro para memoria alocada anteriormente por malloc(). E muito importante que vocé nunca use free() com um argumento invalido; isso destruiria a lista de meméria livre. O subsistema de alocagao dinamica de C é usado em conjungio com ponteiros para suportar uma variedade de construgées de programacao impor- tantes, como listas encadeadas ¢ arvores bindrias. Vocé vera diversos exemplos disso na Parte 3. Um outro uso importante de alocagéo dinaémica é a matriz dinamica, discutida a seguir. Matrizes Dinamicamente Alocadas Igumas vezes vocé teré de alocar meméria, usando malloc(), mas operar na meméria como se ela fosse uma matriz, usando indexacao de matrizes. Em es- séncia, vocé pode querer criar uma matriz dinamicamente alocada. Como qualquer ponteiro pode ser indexado como se fosse uma matriz unidimensional, isso no representa nenhum problema. Por exemplo, 0 programa seguinte mostra como vocé pode usar uma matriz alocada dinamicamente: /* Rloca espacgo para uma string dinamicamente, solicita a entrada do usudrio e, em seguida, imprime a string de trds para frente. */ #include Cap. 5 Ponteiros 131 #include #include void main(void) { char * register int t; = malloc(80); if(1s) ( printf("Falha na solicitagao de meméria.\n"); exit(h); gets(s); for(t=strlen(s)-1; t>=0; free(s putchar (s[t}); Como 0 programa mostra, antes de seu primeiro uso, s é testado para assegurar que a solicitagao de alocagao foi bem-sucedida e um ponteiro valido foi devolvido por malloc(). Isso é absolutamente necessario para evitar 0 uso acidental de um ponteiro nulo, que, como exposto anteriormente, quase certamente provocaria um problema. Observe como 0 ponteiro s é usado na chamada a gets() e, em seguida, indexado como uma matriz para imprimir a string de trds para frente. Acessar meméria alocada como se fosse uma matriz unidimensional é simples. No entanto, matrizes dinamicas multidimensionais levantam alguns pro- blemas. Como as dimens6es da matriz nao foram definidas no programa, vocé nao pode indexar diretamente um ponteiro como se ele fosse uma matriz mul- tidimensional. Para conseguir uma matriz alocada dinamicamente, vocé deve usar este truque: passar 0 ponteiro como um parametro a uma fungao. Dessa forma, a fungao pode definir as dimens6es do parametro que recebe o ponteiro, permitindo, assim, a indexagao normal de matriz. Para ver como isso funciona, estude o exemplo seguinte, que constréi uma tabela dos ntimeros de 1 a 10 ele- vados a primeira, A segunda, & terceira e A quarta poténcias: /* presenta as poténcias dos mimeros de 1 a 10. Nota: muito embora esse programa esteja correto, alguns compiladores apresentarao uma mensagem de adverténcia com relagdo aos argumentos para as fungdes table() e show(). Se is: so acontecer, ignore. */ 132 C= Completo « Total Cap. 5 clude #include void main(void) /* 136 + C—Completo ¢ Total Cap. 5 Essa nao é uma boa forma de inicializar as matrizes first e second com os nti- meros de 0 a 19. Embora possa funcionar em alguns compiladores, sob certas circunstancias, esta-se assumindo que as duas matrizes sero colocadas uma apés a outra com first primeiro. Jsso pode nao ser sempre 0 caso. © proximo programa ilustra um tipo de erro muito perigoso. Veja se vocé é capaz de encontra-lo. /* Esse programa tem um erro. */ #include #include void main(void) € char ‘pl; ar s(80]; do { gets(s); /* 18 uma string */ /* imprime o equivalente decimal de cada caractere */ while(*pl) printf(" %d", *pi++); } while (stremp(s, "done")); ) Esse programa usa p1 para imprimir os valores ASCII associados a cada caractere contido em s. O problema é que o endereco de s é atribuido a p1 apenas uma vez. Na primeira iteracdo do lao, p1 aponta para o primeiro caractere em s. Porém, na segunda iteragao, ele continua de onde foi deixado, porque ele nao é reinicializado para o comeco de s. O préximo caractere pode ser parte de uma outra string, uma outra varidvel ou um pedaco do programa. A maneira apro- priada de escrever esse programa é /* Esse programa estd correto. */ #include #include void main (void) { nar *pl; ar s(80]; Cap. 5 Ponteiros 137 do { pl gets(s); /* 1€ uma string */ /* imprime o equivalente decimal de cada caractere */ while(*pl) printf£(* 8a", *pl++); } while (strcmp(s, "done")); Aqui, cada vez que 0 laco repete, p1 ¢ ajustado para 0 inicio da string. Em geral, vocé deve lembrar-se de reinicializar um ponteiro se ele for reutilizado. O fato de manipular ponteiros incorretamente e poder provocar erros traigoeiros nao é razao para nao usa-los. Apenas seja cuidadoso e assegure-se de que vocé sabe para onde cada ponteiro esta apontando antes de usé-lo. TiaKKON ‘ete Funcgoes lS Fungées sido os blocos de construcdo de C e o local onde toda a atividade do programa ocorre. Elas sao uma das caracteristicas mais importantes de C. E A Forma Geral de uma Funcao A forma geral de uma fungao é especificador_de_tipo nome_da_fungdo(lista de pardmetros) { corpo da funcio ' O especificador_de_tipo especifica o tipo de valor que o comando return da fungao devolve, podendo ser qualquer tipo valido. Se nenhum tipo é espe- cificado, o compilador assume que a fungéo devolve um resultado inteiro. A lista de parimetros € uma lista de nomes de varidveis separados por virgulas e scus tipos associados que recebem os valores dos argumentos quando a fungao 6 chamada. Uma funggo pode nao ter parametros, neste caso a lista de parametros é vazia. No entanto, mesmo que nao existam parametros, os paténteses ainda so necessarios Nas declaragdes de variaiveis, vocé pode declarar muitas variaveis como sendo de um tipo comum, usando uma lista de nomes de varidveis separados por virgulas. Em contraposicao, todos os parametros de fungao devem incluir o tipo e o nome da variavel. Isto é, a lista de declaracéo de parametros para uma fungao tem esta forma geral: f(tipo nomevarl, tipo nomevar2, ..., tipo nomevarN) 138 Cap. 6 Fungées 139 Regras de Escopo de Funcées As regras de escopo de uma linguagem sao as regras que governam se uma porgao de cédigo conhece ou tem acesso a outra porgao de cédigo ou dados. Em C, cada fungao é um bloco discreto de cédigo. Um eédigo de uma fungao € privativo aquela fungao e nao pode ser acessado por nenhum comando em uma outra fungao, exceto por meio de uma chamada a fungdo. (Por exemplo, vocé nao pode usar goto para saltar para o meio de outra funcao.) O cédigo que constitui o corpo de uma fungio é escondido do resto do programa e, a menos que use varidveis ou dados globais, ndo pode afetar ou ser afetado por outras partes do programa. Colocado de outra maneira, 0 cdigo e os dados que sio definidos internamente a wna fungao nao podem interagir com 0 cédigo ou da- dos definidos em outra fungao porque as duas funcdes tém escopos diferentes. Varidveis que so definidas internamente a uma funcao sao chamadas varidveis locais. Uma variavel local vem a existir quando ocorre a entrada da fungao e ela 6 destruida ao sair. Ou seja, varidveis locais nao podem manter seus valores entre chamadas a fungées. A tinica excecio ocorre quando a variavel é declarada com o especificador de tipo de armazenamento static. Isso faz com que 0 compilador trate a varidvel como se ela fosse uma varidvel global para fins de armazenamento, mas ainda limita seu escopo para dentro da fungao. (O Capitulo 2 aborda varidveis globais e locais em profundidade.) Em C, todas as fungées estéo no mesmo nivel de escopo. Isto é, nao é possivel definir uma funcao internamente a uma fungao. Esta é a razio de C nao ser tecnicamente uma linguagem estruturada em blocs. Hi Argumentos de Fungdes Se uma fungao usa argumentos, ela deve declarar varidveis que aceitem os va- lores dos argumentos. Essas varidveis sio chamadas de pardmetros formais da funcao. Elas se comportam como quaisquer outras varidveis locais dentro da fungao e séo criadas na entrada e destrufdas na saida. Como mostra a fungdo seguinte, a declaracao de parametros ocorre apés 0 nome da funcao: /* Devolve 1 se c é parte da is_in(char *s, char c) { while(*s) if(*s ring s; 0 caso contrério. */ ) retur: 140 C—Gompleto e Total Cap. 6 else s+ return 0; } A fungao is_in@ tem dois parametros: s e c. Essa funcdo devolve 1 se 0 caractere ¢ faz parte da string s; caso contrario, ela devolve 0. Vocé deve assegurar-se de que os argumentos usados para chamar a funcdo sejam compativeis com o tipo de seus parametros. Se os tipos sao incom- pativeis, o compilador nao gera uma mensagem de erro, mas ocorrem resultados inesperados. Ao contrario de muitas outras linguagens, C é robusta e geralmente faz alguma coisa com qualquer programa sintaticamente correto, mesmo que 0 programa contenha incompatibilidades de tipos questionaveis. Por exemplo, se uma fungao espera um ponteiro mas é chamada com um valor, podem ocorrer resultados inesperados. O uso de prototipos de funges (discutidos em breve) pode ajudar a achar esses tipos de erro. Como no caso com varidveis locais, vocé pode fazer atribuigdes a para- metros formais ou usé-los em qualquer expressio C permitida. Embora essas varidveis realizem a tarefa especial de receber o valor dos argumentos passados para a fungio, vocé pode usé-las como qualquer outra varidvel local. Chamada por Valor, Chamada por Referén: Em geral, podem ser passados argumentos para sub-rotinas de duas maneiras. A primeira 6 chamada por valor. Esse método copia 0 valor de um argumento no parametro formal da sub-rotina. Assim, alterages feitas nos parametros da sub- rotina nao tém nenhum efeito nas varidveis usadas para chaméa-la. Chamada por referéncia & a segunda maneira de passar argumentos para uma sub-rotina. Nesse método, o enderego de um argumento é copiado no pa- rametro. Dentro da sub-rotina, 0 enderego é usado para acessar 0 argumento real utilizado na chamada. Isso significa que alterac6es feitas no parametro afetam a varidvel usada para chamar a rotina. Com poucas excegdes, C usa chamada por valor para passar argumentos. Em geral, isso significa que vocé nao pode alterar as varidveis usadas para cha- mar a funcao. (Vocé aprendera, mais tarde, neste capitulo, como forcar uma cha- mada por referéncia, utilizando um ponteiro para permitir alteracdes na varidvel usada na chamada.) Considere o programa seguinte: #include int sar (int x); Cap. 6 Fungoes 1 void main(void) t int t=10; print£("td %a", sar(t), t); sqr(int x) Force return (x); Neste exemplo, 0 valor do argumento para sq, 10, é copiado no parametro x. Quando a atribuigao x = x*x ocorre, apenas a vatidvel local x é modificada. A varidvel t, usada para chamar sqr(), ainda tem o valor 10. Assim, a saida é 100 10, Lembre-se de que é uma c6pia do valor do argumento que é passada para a funcao. O que ocorre dentro da fungdo nao tem efeito algum sobre a varidvel usada na chamada Criando uma Chamada por Referéncia Muito embora a convengio de C de passagem de parametros seja por valor, vocé pode criar uma chamada por referéncia passando um ponteiro para 0 argumento. Como isso faz com que 0 endereco do argumento seja passado para a funcao, vocé pode, entao, alterar 0 valor do argumento fora da funcdo. Ponteiros sao passados para as fungdes como qualquer outra varidvel Obviamente, é necessario declarar os pardmetros como do tipo ponteiro. Por exemplo, a funcao swap(), que troca os valores dos seus dois argumentos inteiros, mostra como. void swap(int *x, int ty) { int temp; temp = *x; /* salva o valor no endereco x */ +x = ty; /* pde y em x */ vy = temp; /* poe x em y */ 12 C— Completo ¢ Total Cap. 6 swap() é capaz de trocar os valores das duas varidveis apontadas por x e y porque sao passados seus enderecos (e ndo seus valores). Dai que, dentro da fungao, o contetido das varidveis pode ser acessado usando as operacées padrao de ponteiro. Portanto, 0 contetido das variaveis usadas para chamar a funcao € trocado. Lembre-se de que swap() (ou qualquer outra fungao que usa parametros de ponteiros) deve ser chamada com 0 endereso dos argumentos. O programa seguinte mostra a maneira correta de chamar swap(): void swap(int *x, int ty); void main(void) { int i, j; i= 10; 20; swap(&i, &j); /* passa os enderegos de ie j */ } Neste exemplo, ¢ atribuido 10 a varidvel i e 20 a varidvel j. Em seguida, swap0) & chamada com os enderegos de i e j. (O operador undrio & é usado para pro- duzir 0 endereco das varidveis.) Assim, os enderegos de i e j, ndo seus valores, sao passados para a funcgao swap(). Chamando Fungées com Matrizes Matrizes so examinadas em detalhes no Capitulo 4. No entanto, esta seco dis cute a operacao de passagem de matrizes, como argumentos, para funcées, por- que € uma excegao & convengao de passagem de parametros com chamada por valor. Quando uma matriz é usada como um argumento para uma funcdo, apenas o endereco da matriz é passado, nao uma cépia da matriz inteira. Quando vocé chama uma fungao com um nome de matriz, um ponteiro para o primeiro elemento na matriz é passado para a funcao. (Nao se esqueca: em C, um nome de matriz sem qualquer indice é um ponteiro para o primeiro elemento na ma- triz.) Isso significa que a declaragao de parametros deve ser de um tipo de pon- teiro compativel. Existem trés maneiras de declarar um parametro que recebera um ponteiro para matriz, Primeiro, ele pode ser declarado como uma matriz, conforme mostrado aqui: Cap. 6 Fungdes 143 /* Imprime alguns niimeros. */ #include void display(int num[10]); void main(void) int t{10], for(i=0; i void display(int num); void main(void) { int t{10], i: for ( for ( ++i) tlil=ds i++) display(tlil); } void display(int num) { printf ("® } num) ; Como vocé pode ver, 0 parametro para display( é do tipo int. Nao é relevante que display() seja chamada usando um elemento de matriz, pois apenas um valor da matriz é usado. E importante entender que, quando uma matriz 6 usada como um ar- gumento para uma fungao, seu endereco é passado para a funcéo. Isso é uma excegao a convengio de C no que diz respeito a passar parametros. Nesse caso, Cap. 6 Fungdes 5 © cédigo dentro da fungdo ests operando com, e potencialmente alterando, 0 contettdo real da matriz usada para chamar a funcao. Por exemplo, considere a fungao print_upper(), que imprime seu argumento string em maiisculas: #include #include void print_upper(char *string); void main(void) { char s[80); gets(s); print_upper(s); /* Imprime uma string em maitsculas. */ void print_upper(char *string) £ register int t; for(t=0; string[t]; ++t) { string(t] = toupper(string[t]); putchar (string(t]); } Apés a chamada a print_upper0, 0 contetido da matriz s em main() estaré al- terado para maitisculas. Se ndo é isso o que vocé quer, 0 programa poderia ser escrito dessa forma: #include #include void print_upper(char *string); void main(void) ( char s(80]; gets(s); print_upper|s); 146 C— Completo e Total void print_upper(char *string) { register int t for(t string[t]; ++t) putchar (toupper (stringft])}; } Nesta versao, 0 contetido da matriz s permanece inalterado, porque seus valores nao sao modificados. A funcao gets() da biblioteca padrao é um exemplo classico de passagem de matrizes para funcées. A funcao gets() da biblioteca padrao € mais sofisticada e complexa. Porém, a funcao mais simples xgets(), dada a seguir, da uma idéia de como ela funciona. /* Uma versdo muito simples da fungdo gets() da biblioteca padrao */ char *xgets(char *s) char ch, *p; int t; P /* gets () devolve um ponteiro para = */ for(t=0; t0) t--7 break; default: s(t} = chy } ) s(80] = '"\0"; return p; Cap. 6 Fungées 17 A fungao xgets() deve ser chamada com um ponteiro para caractere, que pode ser uma variavel declarada como um ponteiro para caractere ou o nome de uma matriz de caracteres, que, por definigao, ¢ um ponteiro de caractere. Na entrada, xgets() estabelece um laco for de 0 a 80. Isso evita que strings maiores sejam inseridas pelo teclado. Se mais de 80 caracteres forem inseridos, a fungao retorna. (A fungao gets0 real nao tem essa restrigdo.) Como C nao tem verificagao interna de limites, vocé deve assegurar-se de que qualquer varidvel utilizada para cha- mar xgets() pode aceitar pelo menos 80 caracteres. Ao digitar caracteres no te- clado, eles sao colocados na string. Se vocé pressionar a tecla de retrocesso, 0 contador t 6 reduzido em 1. Quando vocé pressiona ENTER, um caractere nulo é colocado no final da string, sinalizando sua terminagao. Como a matriz usada para chamar xgets() 6 modificada, ao retornar ela contém os caracteres digitados. | argc e argv — Argumentos para main() Algumas vezes é titil passar informagdes para um programa quando 0 executa- mos. Geralmente, vocé passa informagées para a fungao main() via argumentos da linha de comando. Um argumento da linha de comando & a informacao que segue 0 nome do programa na linha de comando do sistema operacional. Por exemplo, quando compila programas em C, vocé digita algo apés aviso de co- mando na tela semelhante a: cc nome_programa onde nome_programa & 0 programa que voce deseja compilar. O nome do pro- grama é passado para o compilador C como um argumento. Ha dois argumentos internos especiais, argc e argv, que sio usados para receber os argumentos da linha de comando. O parametro argc contém 0 ntimero de argumentos da linha de comando e é um inteiro. Ele é sempre pelo menos 1 porque © nome do programa é qualificado come primeiro argumento. O para- metro argv é um ponteiro para uma matriz de ponteiros para caractere. Cada elemento nessa matriz aponta para um argumento da linha de comando. Todos os argumentos da linha de comando sao strings — quaisquer nimeros terdo de ser convertidos pelo programa no formato interno apropriado. Pos exemplo, esse programa simples imprime Ola e seu nome na tela se vocé o digitar imediata- mente apés 0 nome do programa #include #include void main(int argc, char *targv[]) 148 C—Completo Total Cap. 6 if(arge! printé( exit(1); ¢ Vocé esqueceu de digitar seu nome.\n"); print£(*Ola $s", argvfll); } Se esse programa chamasse nome e seu nome fosse Tom, entdo, para rodar 0 programa, vocé deveria digitar nome Tom. A resposta do programa seria Ola Tom. Em muitos ambientes, cada argumento da linha de comando deve ser separado por um espago ou um caractere de tabulacio. Virgulas, pontos~ gulas etc. nao sao considerados separadores. Por exemplo, vir- @ run spot, run 6 constituido de trés strings, enquanto @ Herb, Rick, Fred 6 uma dnica string, uma vez que virgulas nao sao separadores legais. Alguns ambientes permitem que se coloque entre aspas uma string con- tendo espacos. Isso faz com que a string inteira seja tratada como um tnico argumento. Verifique o manual do seu sistema operacional para mais detalhes sobre a definigéo dos parametros na linha de comando do seu sistema. E importante declarar argv adequadamente. O método de declaragéo mais comum é Bochar *argyi): Os colchetes vazios indicam que a matriz é de tamanho indetermimado. Vocé pode, agora, acessar os argumentos individuais indexando argv. Por exemplo, argv[0] aponta para a primeira string, que é sempre o nome do programa; argy{1) aponta para o primeiro argumento e assim por diante. Um outro exemplo usando argumentos da linha de comando é 0 pro- grama chamado countdown, mostrado aqui. Ele conta regressivamente a partir do valor especificado na linha de comando e avisa quando chega a 0. Observe que o primeiro argumento contendo o ntimero é convertido em um inteiro pela funcdo padrao atoi(). Se a string “display” é o segundo argumento da linha de ccomando, a contagem regressiva também sera mostrada na tela. Cop.6 Fungoes /* Programa de contagem regressiva. #include #include #include include void main(int argc, char targv[]} int disp, count; if(argc<2) ( printf (*Vocé deve digi printf£("na linka de comando. exit (1); } if(arge==3 && !stromp(argv[2], aro vi 149 s x a contar\n"); Tente novamente.\n"); “display")) disp = 1 else = 0; for (count=atoi(argv[1]); count; --count) if(disp) printf ("%d\n", count); putchar(‘\a"); /* print£("Terminou"); ) isso ira tocar a campainha na maioria dos computadores y Observe que, se nenhum argumento for especificado, é mostrada uma mensagem de erro. Um programa com argumentos na linha de comando geralmente apre- senta instrugdes se 0 usuario executou o programa sem inserir a informagao apro- priada. Para acessar um caractere individual em uma da: strings de comando, acrescente um segundo indice a argv. Por exemplo, o proximo programa mostra todos os argumentos com os quais foi chamado, um caractere por vez: #include void main(int argc, char *argv(]) txarge; ++t) { 150 C — Completo e Total Cap. 6 while(argv(t][il) { putchar (argv(t] (il); } Lembre-se: © primeiro indice acessa a string e 0 segundo acessa os caracteres individuais da string. Normalmente, vocé usa arge e argy para obter os comandos iniciais do seu programa. Teoricamente, vocé pode ter até 32.767 argumentos, mas a maioria dos sistemas operacionais nao permite mais que alguns poucos. Geralmente, voce usa esses argumentos para indicar um nome de arquivo ou uma opgao. O uso dos argumentos da linha de comando da aos seus programas uma aparéncia profissional e facilita 0 uso do programa em arquivos de lote (batch files). E uma pratica comum declarar main() como nao tendo parametro al- gum, usando a palavra-chave (ou palavra reservada) void quando os parametros da linha de comando néo estao sendo usados. (Esta abordagem é usada pelos programas neste livro). Porém, vocé pode simplesmente deixar os parénteses vazios. Os nomes arge e argy so tradicionais, porém arbitrarios. Vocé pode dar quaisquer nomes a esses dois parametros de main(). Além disso, alguns com, ladores podem suportar arguments adicionais para main(); assegure-se, entao, de verificar seu manual do usuario EO Comando return O comando return tem dois importantes usos. Primeiro, ele provoca uma saida imediata da fungao que o contém. Isto é, faz com que a execugio do programa retorne ao cddigo chamador. Segundo, ele pode ser usado para devolver um valor. Retornando de uma Funcao Existem duas maneiras pelas quais uma funcdo termina a execucdo e retorna ao cédigo que a chamou. A primeira ocorre quando o tiltimo comando da funcéo for executado e, conceitualmente, a chave final do programa (J) é encontrada. (Obviamente, a chave nao esta realmente presente no cédigo-objeto, mas vocé pode imaginé-la como se estivesse.) Por exemplo, a funcao pr_reverse() nesse pro- grama, simplesmente escreve a string “Eu gosto de C” de tras para frente na tela. Cap. 6 Fungdes 151 #include #include void pr_reverse(char *s); void main(void) { pr_reverse("Eu gosto de C"); void pr_reverse(char *s) { register int for (t trlen(s)-1; t>=0; t--) putchar(s[t]); Uma vez que a string tenha sido mostrada, nao fica nada por fazer na funcdo pr_reverse(), de forma que ela retorna para o lugar de onde foi chamada. Na realidade, poucas funcdes usam esse método default de terminar a execugao. A maioria das fungdes adota o comando return para encerrar a exe- cugao, seja porque um valor deve ser devolvido seja para tornar 0 cédigo da funcao mais simples e eficiente. Lembre-se de que uma fungdo pode ter diversos comandos return. Por exemplo, a fungio find_substr(, no programa a seguir, devolve a posicio inicial de uma substring dentro de uma string ou, se nao for encontrada, a funcao devolve -1. #include int find_substr(char “sl, char *s2); void main(void) if(£ind_substr("C é legal", "é")!=-1) print£("a substring nao foi encontrada) ; /* Devolve o indice de sl em s2. */ find_substr(char *s1, char *s2) 152 C — Completo ¢ Total Cap. 6 for(t=0; sl[t}; t++) { &sl[t}; 2 e(*p2 && “p2==*p) ( pa++ (1*p2) return t; /* 1* retorno */ return - /* 2% retorno */ Retornando Valores Todas as funcées, exceto as do tipo void, devolvem um valor. Esse valor € es- pecificado explicitamente pelo comando return. Se nenhum comando return es- tiver presente, entao o valor de retorno da fungao sera tecnicamente indefinido. (Geralmente, os compiladores C devolvem 0 quando nenhum valor de retorno for especificado explicitamente, mas vocé nado deve contar com isso se ha inte- resse em portabilidade.) Em outras palavras, a partir do momento que uma fun- ga0 nao é declarada como void, ela pode ser usada como operando em qualquer expressio valida de C. Assim, cada uma das seguintes expressées é valida em C: x = powerly}: if(max(x,y) > 100) printé ("maior"); for(ch=getchar(); isdigit(ch); ) Porém, uma fungao nao pode ser o destino de uma atribuigio. Um co- mando tal como esta errado. O compilador C indicaré isso como um erro e nao compilara pro- gramas que contenham um comando como esse. Quando vocé escreve programas, suas funges geralmente serao de trés tipos. O primeiro tipo é simplesmente computacional. Essas fungGes sao proje- tadas especificamente para executar operacdes em seus argumentos e devolver um valor. Uma funcéo computacional é uma funcéo “pura”. Exemplos so as fungées da biblioteca padrao sqrt( e sin, que calculam a raiz quadrada e 0 seno de seus argumentos. ap(x,y) = 100; /* comando incorreto */ Cap. 6 Fungdes 153 O segundo tipo de fungao manipula informagdes e devolve um valor que simplesmente indica 0 sucesso ou a falha dessa manipulagao. Um exemplo é a funcao da biblioteca padrao felose), que é usada para fechar um arquivo. Se a operacdo de fechamento for bem-sucedida, a funcao devolvera 0; se a ope- ragéo nao for bem-sucedida, ela devolverd um cédigo de erro. O iltimo tipo nao tem nenhum valor de retorno explicito. Em esséncia, a fungao é estritamente de procedimento e nao produz nenhum valor. Um exem- plo € exit, que termina um programa. Todas as fungdes que nao devolvem valores devem ser declaradas como retornando o tipo void. Ao declarar uma funcao como void, vocé a protege de ser usada em uma expressio, evitando uma utilizacdo errada acidental. Algumas vezes, fungées que, na realidade, nao produzem um resultado relevante de qualquer forma devolvem um valor. Por exemplo, printf() devolve co ntimero de caracteres escritos. Entretanto, é muito incomum encontrar um pro- grama que realmente verifique isso. Em outras palavras, embora todas as fungoes, exceto aquelas do tipo void, devolvam valores, vocé nao tem necessariamente de usar o valor de retorno. Uma questao envolvendo valores de retorno de fun- Goes 6: “Eu ndo tenho de atribuir esse valor a alguma varidvel j& que um valor est sendo devolvido?”. A resposta é nao. Se nao hé nenhuma atribuicao espe- cificada, 0 valor de retorno é simplesmente descartado. Considere 0 programa seguinte, que utiliza a fungdo mul0): #include int mul(int a, int b); void main(void) nt x, yy x = 10; y = 20; z= mul(x, y); wae print£("$d", mul(x,y)); /* 2 */ mul (x, y); emia } mul(int a, int b) £ return a*b; 154 C — Completo e Total Cap. 6 Na linha 1, 0 valor de retorno de mul( é atribuido a z. Na linha 2, 0 valor de retorno nao é realmente atribuido, mas é usado pela funcao print£(). Finalmente, na linha 3, o valor de retorno é perdido porque nao é atribuido a outra variavel nem usado como parte de uma expressao. is Funcgées Que Devolvem Valores Ndo-Inteiros Quando o tipo da fungao nao é explicitamente declarado, 0 compilador C atribui automaticamente a ela o tipo padrao, que é int. Para muitas funges em C, esse tipo padrao é aplicavel. No entanto, quando 6 necessério um tipo de dado dife- rente, 0 processo envolve dois passos. Primeiro, deve ser dada a funcéo um es- pecificador de tipo explicito. Segundo, o tipo da funcao deve ser identificado antes da primeira chamada feita a ela. Apenas assim C pode gerar um cédigo correto para fungdes que nao devolvem valores inteiros. As fungdes podem ser declaradas como retornando qualquer tipo de dado valido em C. O método da declaracdo é semelhante a declaragio de varid- veis: o especificador de tipo precede o nome da funcao. O especificador de tipo informa ao compilador que tipo de dado a fungao devolvera. Essa informacao é critica para o programa rodar corretamente, porque tipos de dados diferentes tém tamanhos e representacées internas diferentes. Antes que uma fungdo que nao retorne um valor inteiro possa ser usada, seu tipo deve ser declarado ao programa. Isso porque, a menos que informado em contrario, 0 compilador C assume que uma fungao devolve um valor inteiro. Se seu programa usa uma fungio que devolve um tipo diferente antes da sua declaragao, 0 compilador gera erroneamente o cédigo para a chamada. Para evi- tar isso, vocé deve usar uma forma especial de declaracao, perto do inicio do seu programa, informando ao compilador o tipo de dado que sua fungao real- mente devalverd. Existem duas maneiras de declarar uma funcao antes de ela ser usada: a forma tradicional e 0 moderno método de protétipos. A abordagem tradicional era 0 tinico método disponivel quando C foi inventada, mas agora esta obsoleto. Os prot6tipos foram acrescentados pelo padrao C ANSI. A abordagem tradicional 6 permitida pelo padrao ANSI para assegurar compatibilidade com cédigos mais antigos, mas novos usos séo desencorajados. Muito embora seja antiquado, mui- tos milhares de programas ainda o usam, de forma que vocé deve familiarizar-se com ele. Além disso, 0 método com protétipos é basicamente uma extensio do conceito tradicional. Cap. 6 Fungées 155 Nesta segdo, examinaremos a abordagem tradicional. Embora desatuali- zada, muitos dos programas existentes ainda a utilizam. Além disso, um método do prototipo é basicamente uma extensao do conceito tradicional. (Os prototipos. de fungio sao discutidos na préxima segio.) Com a abordagem tradicional, vocé especifica o tipo e o nome da fungao proximos ao inicio do programa para informar ao compilador que uma funcao devolverd algum tipo de valor diferente de um inteiro, como ilustrado aqui: #include Eloat sum(); /* identifica a funcdo */ float first, second; void main(void) { st = 123.23; second = 99.09; printf ("*£", sum()); 3 float sum(} ( return first A primeira declaracao da fungao informa ao compilador que sum() devolve um tipo de dado em ponto flutuante. Isso permite que o compilador gere correta- mente 0 cédigo para a chamada a sum(). Sem a declaragao, 0 compilador indi caria um erro de incompatibilidade de tipos. O comando tradicional de declaragao de tipos tem a forma geral especificador_de_tipo nome_da_funcio(); Mesmo que a fungao tenha argumentos, eles néo constam na declaracao de tipo, Sem o comando de declaracao de tipo, ocorreria um erro de incompa- tibilidade entre o tipo de dado que a fungao devolve e o tipo de dado que a rotina chamadora espera. Os resultados serao bizarros e imprevisiveis. Se ambas as fungGes estéo no mesmo arquivo, o compilador descobre a incompatibilidade de tipos e n’o compila o programa. Contudo, se as fungSes estao em arquivos diferentes, 0 compilador nao detecta 0 erro. Nenhuma verificacao de tipos € feita durante o tempo de linkedigéo ou tempo de execucéo, apenas em tempo de compilacao. Por essa razdo, vocé deve assegurar-se de que ambos os tipos sio compativeis. 156 C — Completo e Total Cap. 6 NOTA: Quando um caractere é devolvido por umv fungio declarada como sendo do tipo int, 0 valor caractere & convertido em um inteiro. Visto que C faz a conversito de caractere para inteiro, e vice-versa, uma fungio que devolve um caractere geral- mente no é declarada come devolvendo um valor caractere. O programador confia na conversito padrio de caractere em inteiro ¢ vice-versa. Esse tipo de coisa & fre giientemente encontrado em cédigos em C mais antigos ¢ tecnicamente nao & consi- derado um erro. & Protétipos de Funcées O padrao C ANSI expandiu a declaracao tradicional de fungao, permitindo que a quantidade e os tipos des argumentos das fungées sejam declarados. A defi- nigao expandida é chamada protétipo de fungdo. Protétipos de funcdes nao faziam parte da linguagem C original. Bles so, porém, um dos acréscimos mais impor- tantes do ANSI a C. Neste livro, todos os exemplos incluem prototipos completos das funcées. Protétipos permitem que C forneca uma verificagao mais forte de tipos, algo como aquela fornecida por linguagens como Pascal. Quando vocé usa prototipos, C pode encontrar e apresentar quaisquer conversées de tipos ilegais entre o argumento usado para chamar uma fungao e a definigao de seus para- metros. C também encontra diferencas entre o ntimero de argumentos usados para chamar a funcao e o ntimero de parametros da fungio. iu A forma geral de uma definicao de protétipo de funcao é tipo nome_func(tipo nome_paramt, tipo nome_param?,..., tipo nome_paramN); O uso dos nomes dos parametros ¢ opcional. Porém, eles habilitam 0 compilador a identificar qualquer incompatibilidade de tipos por meio do nome quando ocor- re um erro, de forma que é uma boa idéia inclui-los. Por exemplo, o programa seguinte produz uma mensagem de erro por- que ele tenta chamar sqr_it0 com um argumento inteiro em vez do exigido pon- teiro para inteiro. (Voce nao pode transformar um inteiro em um ponteiro.) /* Esse programa usa um protétipo de fun¢aéo para forcar uma verificacaéo forte de tipos. */ void sqr_it(int *i}; /* protétipo */ void main(voia) { int x Cap. 6 Fungées 157 x = 10; sar_it(: /* incompatibilidade de tipos */ ) void sqr_it(int *i) Devido & necessidade de compatibilidade com a versio original de C, algumas regras especiais sao aplicadas aos protétipos de fungées. Primeiro, quando o tipo de retorno de uma fungao é declarado sem nenhuma informacao de protétipo, 0 compilador simplesmemte assume que nenhuma informacao sobre os parame- tros é dada. No que se refere ao compilador, a funcao pode ter diversos ou nenhum parametro. Assim, como pode ser dado um prot6tipo a uma fungao que nao tem nenhum pardmetro? A resposta é: quando uma funcdo nao tem para- metros, seu protétipo usa void dentro dos parénteses. Por exemplo, se uma fun- ao chamada £0 devolve um float e nao tem parametros, seu protétipo sera BP float £(voia); Isso informa ao compilador que a fungaéo nao tem parametros e qualquer cha- mada a fungao com parametros é um erro. © uso de prototipos afeta a promogao automitica de tipos de C. Quando uma funcao sem protétipo € chamada, todos os caracteres séo convertidos em inteiros e todos os floats em doubles. Essas promocdes um tanto estranhas estao relacionadas com as caracteristicas do ambiente original em que C foi desenvol- vida. No entanto, se a funcao tem prototipo, os tipos especificados no protétipo 10 mantidos e ndo ocorrem promocoes de tipo. Protétipos de fungées ajudam a detectar erros antes que eles ocorram. Além disso, cles auxiliam a verificar se seu programa esta funcionando corretamen- te, nao permitindo que fungdes sejam chamadas com argumentos inconsistentes. Tenha um fato em mente: embora 0 uso de prototipos de fungao seja bastante recomendado, tecnicamente nao é errado que uma fungio nao tenha protétipos. Isso 6 necessério para suportar cédigos C sem protstipos. Todavia, seu codigo deve, em geral, incluir total informagao de prototipos. KA, NOTA: Embora os protétipos sejam opcionais em C, eles sto exigidos pela sucessora Sa de C: Co. 158 C — Completa e Total Cap. 6 &@ Retornando Ponteiros Embora funcdes que devolvem ponteiros sejam manipuladas da mesma forma que qualquer outro tipo de fungao, alguns conceitos importantes precisam ser discutidos Ponteiros para varidveis nao sao varidveis e tampouco inteiros sem sinal. Eles so 0 endereco na memoria de um certo tipo de dado. A razao para a distingao deve-se ao fato de a aritmética de ponteiros ser feita relativa ao tipo de base. Por exemplo, se um ponteiro inteiro é incrementado, ele conteré um valor que é maior que o seu anterior em 2 (assumindo inteiros de 2 bytes). Em geral, cada vez que um ponteiro é incrementado, ele aponta para o préximo item de dado do tipo correspondente. Desde que cada tipo de dado pode ter um comprimento diferente, o compilador deve saber para que tipo de dados 0 pon- teiro esta apontando. Por esta razao, uma funcao que retorna um ponteiro deve declarar explicitamente qual tipo de ponteiro ela esta retornando. Para retornar um ponteiro, deve-se declarar uma fungéo como tendo tipo de retorno ponteiro. Por exemplo, esta funcéo devolve um ponteiro para a primeira ocorréncia do caractere ¢ na string s: /* Devolve um ponteiro para a primeira ocorréncia de c ems. */ char *match(char ¢, char *s) ( while(c!=*s && *s) s++ return(s); } Se nenhuma coincidéncia for encontrada, ¢ devolvido um ponteiro para o ter- minador nulo. Aqui esta unt programa pequeno que usa match0: #include char *match(char ¢, char *s); /* protétipo */ void main(void) { char s[80], *p, ch; gets(s}; ch = getchar(); p = matchich, s); if(*p) /* encontrou */ Cap: 6 Fungoes 159 printf("ts ", p); else printf ("Nao encontrei."); } Esse programa Ié uma string e, em seguida, um caractere. Se 0 caractere esté na string, 0 programa imprime a string do ponto em que ha a coincidéncia. Caso contrario, ele imprime Nao encontr gE Funcées do Tipo void Um dos usos de void é declarar explicitamente fungdes que nao devolvem va- lores. Isso evita seu uso em express6es ¢ ajuda a afastar um mau uso acidental. Por exemplo, a funcdo print_vertical() imprime seu argumento string vertical- mente para baixo, do lado da tela. Visto que nao devolve nenhum valor, ela é declarada como void void print_vertical (char *str) € while(*str) printf(%c\n", *str++); } Antes de poder usar qualquer fungao void, vocé deve declarar seu protétipo. Se isso nao for feito, C assumira que ela devolve um inteiro e, quando o compilador encontrar de fato a funcao, ele declararé um erro de incompatibilidade. O pro- grama seguinte mostra um exemplo apropriado que imprime verticalmente na tela um unico argumento da linha de comande: #include void print_vertical(char *str); /* protétipo */ void main(int argc, char *argv[]) if(arge) print_ver al (argv(1}); ) void print_vertical (char *str) while(*str) printf("8c\n", *str++); 160 C= Completo e Total Cap.6 Antes que 0 padrao C ANSI definisse void, fungdes que nao devolviam valores simplesmente eram assumidas como do tipo int por padrao. Portanto, nao fique surpreso ao ver muitos exemplos disto em cédigos mais antigos. @ O Que main() Devolve? De acordo com 0 padrao ANSI, a fungdo main() devolve um inteiro para 0 pro- cesso chamador, que é, geralmente, o sistema operacional. Devolver um valor em main() é equivalente a chamar exit() com o mesmo valor. Se main() nao devolve explicitamente um valor, o valor passado para 0 proceso chamador ¢ tecnicamente indefinido. Na pratica, a maioria dos compiladores C devolve 0, mas nao conte com isso se ha interesse em portabilidade. Vocé também pode declarar main() como void se ela nao devolve um valor. Alguns compiladores geram uma mensagem de adverténcia, se a funcao nao é declarada como void e também nao devolve um valor. @ Recursao Em C, fungdes podem chamar a si mesmas. A fungao é recursiva se um comando no corpo da fungao a chama. Recursao ¢ 0 processo de definir algo em termos de si mesmo e 6, algumas vezes, chamado de definigao circular. Um exemplo simples de fungao recursiva 6 facts), que calcula o fatorial de um inteiro. O fatorial de um némero n é 0 produto de todos os ntimeros inteiros entre 1 en. Por exemple, fatorial de 3 é 1 x 2 x 3, ou seja, 6. Tanto factr) como sua equivalente iterativa sio mostradas aqui: factr n) /* recursiva */ { int answer; if (n==1) returm (1); answer = factr(n-1)*n; /* chamada recursiva */ return(answer) ; 3 fact(int n) /* n&o-recursiva */ { int t, answer; Cap. 6 Fungies 161 nswer = for(t=1; t<=n; t++) answer=answer*(t); re en (answer) ; ) A versao ndo-recursiva de factt) deve ser clara. Ela usa um lago que é executado de 1a ne multiplica progressivamente cada ntimero pelo produto mével. A operacao de factr() recursiva é um pouco mais complexa. Quando factr) é chamada com um argumento de 1, a funcao devolve 1. Caso contrario, ela devolve o produto de factr(n-1)*n. Para avaliar essa expresso, factr() é cha- mada com nel. Isso acontece até que n se iguale a 1 e as chamadas a funcdo comecem a retornar. Calculando o fatorial de 2, a primeira chamada a faetr() provoca uma segunda chamada com o argumento 1. Essa chamada retorna 1, que é, entdo, multiplicado por 2 (0 valor original de n). A resposta, entao, é 2. (Vocé pode achar interessante inserir comandos printf() em factr() para ver o nivel de cada chamada e quais sao as respostas intermediarias.) Quando uma fungdo chama a si mesma, novos parametros e varidveis locais so alocados na pilha e 0 cédigo da fungao é executado com essas novas varidveis. Uma chamada recursiva nao faz uma nova cépia da funcao; apenas os argumentos sao novos. Quando cada fungao recursiva retorna, as varidveis locais e os parametros sao removidos da pilha e a execucao recomeca do ponto da chamada a funcao dentro da funcao. A maioria das fungdes recursivas néo minimiza significativamente 0 tamanho do cédigo nem melhora a utilizagio da meméria. Além disso, as versoes recursivas da maioria das rotinas podem ser executadas um pouco mais lenta- mente que suas equivalentes iterativas devido as repetidas chamadas & funcio. De fato, muitas chamadas recursivas a uma fungéo podem provocar um estouro da pilha. Como o armazenamento para os parametros da funcao e variaveis locais estd na pilha e cada nova chamada cria uma nova cépia dessas variaveis, a pilha pode provavelmente escrever sobre outra meméria de dados ou de program. Contudo, vocé possivelmente nunca terd de se preocupar com isso, a menos que uma fungao recursiva seja executada de forma desenfreada. A principal vantagem das fungées recursivas é que vocé pode usé-las para criar versdes mais claras e simples de varios algoritmos. Por exemplo, 0 QuickSort, na Parte 3, é muito dificil de implementar numa forma iterativa. Além disso, alguns problemas, especialmente aqueles relacionados com inteligéncia ar- 162 C — Completo e Total Cap. 6 tificial, resultaram em solugoes recursivas. Finalmente, algumas pessoas parecem pensar recursivamente com mais facilidade que iterativamente. Ao escrever fungées recursivas, vocé deve ter um comando if em algum lugar para forcar a fungao a retornar sem que a chamada recursiva seja executada. Se vocé nao o fizer, a fungao nunca retornat quando chamada. Omitir o if & um erro comum quando se escrevem fungées recursivas. Use printf) e getchar() deliberadamente durante 0 desenvolvimento do programa de forma que vocé possa ver 0 que esta acontecendo e encerrar a execugao se localizar um erro. I Declarando uma Lista de Pardmetros de Extensao Varidvel Em C, vocé pode especificar uma func4o que possui a quantidade e os tipos de parametros varidveis. O exemplo mais comum € printf(). Para informar ao com- pilador que um namero desconhecido de parametros seré passado para uma fungao, vocé deve terminar a declaragao dos seus parametros usando trés pontos. Por exemplo, esta declaracdo especifica que func() terd pelo menos dois parame- tros inteiros e um numero desconhecido (incluindo 0) de parametros apés eles. @ otunc(int a, int b, ...); Essa forma de declaragao também é usada por um protétipo de fungao. Qualquer fungao que use um ntimero varidvel de argumentos deve ter pelo menos um argumento verdadeiro. Por exemplo, isto esta incorreto: ® func(...); Para mais informagées sobre ntimero e tipos varidveis, veja a Parte 2, sobre a fungao va_arg() da biblioteca C padrao. | Declaracao de Pardmetros de Funcgées Moderna Versus Classica C originalmente usava um método de declaragao de parametros diferente, algu- mas vezes chamado de forma cldssica. Este livro usa a abordagem de declaragao chamada de forma moderna. O padrao ANSI para C suporta as duas formas, mas Cap. 6 Fungoes 163 recomenda fortemente a forma moderna. Porém, vocé deve saber a forma classica porque, literalmente, milhdes de linhas de cédigo ja existentes a usam! (Além disso, muitos programas usam essa forma porque ela funciona com todos os compiladores — mesmo os antigos.) A declaracao classica de pardmetros de fungées consiste em duas partes: uma lista de parametros, que ficam dentro dos parénteses que seguem 0 nome da funcao, e as declaracées reais dos parametros, que ficam entre o fecha-parén- teses e o abre-chaves da fungao. A forma geral da declaragao classica é tipo nome_func\param!, param2,...paramN) tipo param; tipo paramn2; tipo paramN; { cédigo da fungao } Por exemplo, esta declaragao moderna: float f(int a, int b, char ch) era ) ird se parecer com isto na sua forma classica: float f(a, b, ch) int a, b; char ch; { aera ) Observe que a forma classica pode suportar mais de um parametro em uma lista apés © nome do tipo Lembre-se de que a forma classica de declaragao de parametro esté ob- soleta. Contudo, seu compilador ainda pode compilar programas mais antigos que usam a forma cléssica sem qualquer problema. Isso permite a manutengao de cédigos mais antigos, 164 © ~ Completo e Total Cap. 6 I Questées sobre a Implementacgao Ha uns poucos pontos a lembrar, quando se criam fungdes em C, que afetam sua eficiéncia e usabilidade. Essas questdes sio 0 t6pico desta secao. Pardmetros e Fungoes de Propésito Geral Uma fungao de propésito geral é aquela que sera usada, em uma variedade de situagées, talvez por muitos outros programadores. Tipicamente, vocé nao deve basear fungées de propésito geral em dados globais. Todas as informagées de que uma funcao precisa devem ser passadas para ela por meio de seus parame- tros. Quando isso nao é possivel, voce deve usar varidveis estaticas. Além de tornar suas fungdes de proposito geral, os parametros deixam seu cédigo legivel e menos suscetivel a erros resultantes de efeitos colaterais. Eficiéncia Fungées sao 0s blocos de construgao de C e sao cruciais para todos os programas, exceto os mais simples. Entretanto, em certas aplicagdes especializadas, voce tal- vez precise eliminar uma fungao ¢ substitui-la por cédigo em linha (in-line). Co- digo em linha é 0 equivalente aos comandos da fungao usados sem uma chamada a funcao. Deve-se usar cdigo em linha em lugar de chamadas a funcdes apenas quando 0 tempo de execucio € critico. Cédigo em linha é mais rapido que a chamada a uma fungao por duas razoes. Primeiro, uma instrugao CALL leva tempo para ser executada. Segundo, se ha argumentos para passar, eles devem ser colocados na pilha, o que também toma tempo. Para a maioria das aplicagdes, esse aumento muito pequeno no tempo de execucao nao é significative. Mas, se for, lembre-se de que cada cha- mada a funcao usa um tempo que poderia ser economizado se 0 codigo da funcéo fosse colocado em linha. Por exemplo, seguem duas versdes de um programa que imprime 0 quadrado dos numeros de 1 a 10. A versao em linha é executada mais rapidamente que a outra porque a chamada 4 fungao toma tempo. em linha chamada 4 funcae #include #include int sqr(int a); void main(void) void main(void) ( { int x; Cap. 6 Fungdes 165 for(x=1; x void main(void) Cap.7 Estruturas, unides, enumeragdes e tipos definidos pelo usudrio ai y =x; /* atribui uma estrutura a outra */ print£("%d*, y.a); Apés a atribuigao, y.a contera o valor 10. @ Matrizes de Estruturas Talvez 0 uso mais comum de estruturas seja em matriz de estruturas. Para de- clarar uma matriz de estruturas, vocé deve primeiro definir uma estrutura e, entao, declarar uma varidvel matriz desse tipo. Por exemplo, para declarar uma matriz de estruturas com 100 elementos do tipo addr, que foi definido anterior- mente, deve-se escrever Isso cria 100 conjuntos de variaveis que estio organizados como definido na estrutura addr. Para acessar uma estrutura especifica, deve-se indexar 0 nome da estru- tura. Por exemplo, para imprimir 0 cédigo do CEP da estrutura 3, escreva truct addr addr_info[100]; BP oprint£(-sd", addr_infol2].2ip); Como todas as outras matrizes, matrizes de estruturas comegam a indexagao em 0. Um Exemplo de Lista Postal Para ilustrar como estruturas e matrizes de estruturas sdo usadas, esta segdo desenvolve um programa simples de lista postal que usa uma estrutura para guardar as informagGes de endereco. Nesse exemplo, a informagao armazenada inclui nome, rua, cidade, estado e CEP. Para definir a estrutura basica de dados, addr, que contém essa infor- macao, escreva 172 C— Completo e Total Cap. 7 struct addr { char name [30]; char street [40]; char city[20}; char state(3]; unsigned long int zip; } addr_info [MAX] ; Observe que © campo de CEP é um inteiro longo sem sinal. Isso ocorre porque os CEPs maiores que 64000 — como 94564 — nao podem ser represen- tados em um inteiro de 2 bytes. Nesse exemplo, um inteiro possui 0 cédigo do CEP para ilustrar um elemento de estrutura numérico. Porém, a pratica mais. comum é usar uma string de caracteres para acomodar cédigos postais com letras além de numeros (como usado no Canada e em outros paises), O valor de MAX pode ser definido para satisfazer necessidades especificas. A primeira fungao necessaria para 0 programa é main(). /* Um exemplo simples de 1 matriz de estruturas. */ #include 1 usando uma post, #define MAX 100 struct addr { char name(30); char street [40]; char city([20]; char state[3]; unsigned long int zip; } addr_info[MAx]; void init_list (void), enter(void); void delete(void), list (void) ; int menu_select (void), find_free(void); void main(void) { char choice; init_list(); /* inicializa a matriz de estruturas */ for(i7) ¢ Cap. 7 Estruturas, unides, enumeragées e tipos definidios pelo usuario 173 choice=menu_select (); switch(choice) { case 1: enter(); break; e@ 2: delete(); break; case 3: list(); break; case 4: exit (0); } Primeiro, a funcdo init_list0 prepara a matriz de estruturas para ser usada, co- locando um caractere nulo no primeiro byte do campo nome. O programa as- sume que uma variavel estrutura nao esta sendo usada se nome estiver vazio. A funcao init_list() é mostrada aqui /* Inicializa a lista. */ void init_list (void) { register int for(t=0; t ) MAX; ++t) addr_info[t].name[0] = ‘\0‘; A funcdo menu_select() apresenta as mensagens de opcio e devolve a selegdo do usuario /* Obtém a selecao. */ menu_select (void) { char s[80]; int ¢; print€("1, Inserir um nome\n"); print£("2. Excluir um nome\n"); print£("3. Listar o arquivo\n"); printf£("4. Sair\n"); do { printf gets(s); c = atoils); \nDigite sua escolha 174 ‘C— Completo e Total Cap. 7 )} while(c4); return c; } A funcao enter() espera pela entrada do usudrio e coloca a informagao recebida na proxima estrutura livre. Se a matriz, estiver cheia, entao a mensagem lista cheia seré escrita na tela. A funcdo find_free() procura um elemento nao usado na matriz de estruturas. /* Insere os enderegos na lista. */ void enter (void) ( int slot; char s(80]; slot = find_free(); if(slot==-1) { printé(*\nLista cheia"); return; print£("Digite o nome: "}; gets (addr_info[slot] .name) ; print£("Digite a rua: "); gets (addr_info{slot] .street); printf£("Digite a cidade: "); gets (addr_info{slot] .city); print£("Digite o estado: "); gets (addr_info[slot].state); printf ("Digite o cep: gets(s); addr_info[slot].zip = strtoul(s, ‘\0’, 10); /* Encontra uma estrutura ndo usada. */ find_free(void) { register int t; for ( addr_info[t].name[0] && t #include define MAX 100 struct addr ( char name [30]; char street [40]; char city[20]; char state[3]; unsigned long int zip; } addy_info[MAX) ; void init_list (void), enter (void); void delete(void), list (void); nt menu_select (void), find_free(void); void main (void) char choice; /* inicializa a matriz de estruturas { e=menu_select (); switch(choice) { case 1: enter(}; break, case 2: delete(); break, case 3 break case 4: exit (0 list); a Cap. 7 Estruturas, wnides, enumeracies e tipés definidos pelo usudrio 17 /* Inicializa a lista. */ void init_list (void) register int t; for(t=0; t4); return c; /* Insere os enderecos na lista. */ void enter (void) { int slot; char s[80]; slot = find_free(); if(slot==-1) ( printf ("\nlista cheia return; } printé("Digite o nome: "); gets (addr_info[slot] .name) ; print£(*Digite a rua: gets (addr_info[slot].street); 78 C— Completo ¢ Total printf ("Digite a cidade: "); gets (addr_info[slot] .city); printf ("Digite o estado: "}; gets (addr_info[slot].state) ; printf ("Digite o cep: "}i gets(s); addr_info[{slot].zip = strtoul(s, '\0’, 10); i /* Encontra uma estrutura ndo usada. */ find_free(void) { register int t; for (t=0; addr_infoft].name(0] && t=0 && slot < MAX) addr_info[slot].name[0] = '\0'; } /* Mostra a lista na tela. */ void list (void) { register int t; for(t=0; t /* Define um tipo de estrutura. */ struct struct_type { int a, b; char ch; De void fl(struct struct_type parm); void main(void) { struct struct_type arg; arg.a = 1000; fllarg); Cap. 7 Estrulsiras, unides, enumeracées ¢ tipos definidos pelo usuario 181 ) void fl(struct struct_type parm) { printf£(*%d", parm.a); } Como este programa ilustra, se vocé declarar pardmetros que sao estruturas, deveré tornar a declaracao do tipo de estrutura global, para que todas as partes do seu programa possam usé-la. Por exemplo, se struct_type tivesse sido decl rada dentro de main) (por exemplo), entao nao seria visivel a f10. Como acabamos de enunciar, ao passar estruturas, o tipo do argumento deve coincidir com 0 tipo do parametro. Nao é suficiente que eles sejam fisica- mente semelhantes; os nomes dos seus tipos devem coincidir. Por exemplo, a versdo seguinte do programa anterior é incorreta e nao compilara porque o nome do tipo do argumento usado para chamar f10 difere do nome do tipo de seu parametro. /* Este programa esta errado e néo poderd ser compilado. */ #include /* Define um tipo de estrutura. */ struct struct_type ( int a, b; char ch; 08 /* Define uma estrutura similar a struct_| mas com outro nome. */ struct struct_type2 ( int a, b; char DG void fl(struct struct_type2 parm); void main(void) { struct struct_type arg; arg.a = 1000; fllarg); /* erro de tipos */ 182 C— Complete ¢ Total Cap. 7 ) void filstruct struct_type2 parm) ‘ print£("%d", parm.a); } EZ Ponteiros para Estruturas C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de varidveis. No entanto, ha alguns aspectos especiais de ponteiros de estruturas que vocé deve conhecer. Declarando um Ponteiro para Estrutura Como outros ponteiros, vocé declara ponteiros para estrutura colocando * na frente do nome da estrutura. Por exemplo, assumindo a estrutura previamente definida addr, 0 cédigo seguinte declara addr_pointer como um ponteiro para dados daquele tipo. WB struct addr *addr_pointer: Usando Ponteiros para Estruturas Ha dois usos primarios para ponteiros de estrutura: gerar uma chamada por referéncia para uma fungao e criar listas encadeadas e outras estruturas de dados dinamicas usando o sistema de alocagao de C. Este capitulo cobre 0 primeiro uso. O segundo uso é coberto detalhadamente na Parte 3. Ha um prejuizo maior em passar todas as estruturas, exceto as mais simples, para fungdes: 0 tempo extra necessdrio para colocar (e tirar) todos os elementos da estrutura na pilha. Em estruturas simples, com poucos elementos, esse tempo extra nao é tio grande. Se varios elementos séo usados, porém, ou se alguns dos elementos sao matrizes, a performance pode ser reduzida a niveis inaceitaveis. A solugao para esse problema 6 passar apenas um ponteiro para uma funcdo. Quando um ponteiro para uma estrutura é passado para uma fungao, apenas 0 endereco da estrutura € colocado (e tirado) da pilha. Isso contribui para chamadas muito rapidas a fungdes. Uma segunda vantagem, em alguns casos, 6 Cap. 7 Estruturas, unides, enumeragdes ¢ tipos definidos pelo ustatrio 183 quando a funcao precisa referenciar o argumento real em lugar de uma copia Passando um ponteiro, é possivel alterar 0 contetido dos elementos reais da es- trutura usada na chamada. Para encontrar o enderego da variavel estrutura, deve-se colocat 0 ope- rador & antes do nome da estrutura. Por exemplo, dado o seguinte fragmento: struct bal ( float balance; char name[80] ; } person; uct bal *p; /* declara um ponteiro para estrutura */ entdo poe 0 endereco da estrutura person no ponteiro p. &person; Para acessar os elementos de uma estrutura usando um ponteiro para a es- trutura, voce deve usar 0 operador ->. Por exemplo, isso referencia o campo balance: B p->palance O -> é normalmente chamado de operador seta, e cansiste no sinal de subtragio seguido pelo sinal de maior. A seta é usada no lugar do operador ponto quando se est acessando um elemento de estrutura por meio de um ponteiro para a estrutura. Para ver como um ponteiro para estrutura pode ser usado, examine este programa simples, que escreve as horas, minutos e segundos na tela usando um reldgio (timer) por software. /* Mostra um relégio por software. */ clude #define DELAY 128000 struct my_time { int hours; int minutes; int seconds; 184 C— Completo e Total void display(struct my_time *t); void update(struct my_time *t); void delay (void); void main(void) { struct my_time systime; systime.hours = 0 systime.minutes = 0; systime.seconds = 0; for (ii) { update (&systime) ; display (asystime) ; } void update(struct my_time *t) { t->seconds++; if (t->seconds==60) ( t->seconds = 0; t->minutes++ if (t->minutes==60) { >minutes = 0; t->hours++; 4) t->hours = (t->hour: delay(); void display(struct my_time *t) { printé("%02d:", t->hours); printf ("02d:", t->minutes); print£("%024\n", t->seconds) ; } void delay (void) Cap. 7 Estruturas, unides, enunieracdes e tipds definides| pelo wsubrio 185 necessério */ thours-=24) t->hours = 0; Essa linha de cédigo informa ao compilador para tomar o enderego de t (que aponta para systime em mainO) e atribuir zero a seu elemento hours. Lembre-se de usar 0 operador ponto para acessar elementos de estrutu- ras quando estiver operando na propria estrutura. Quando vocé tem um ponteiro para a estrutura, use o operador seta. I Matrizes e Estruturas Dentro de Estruturas Um elemento de estrutura pode ser simples ou complexo. Um elemento simples € qualquer dos tipos de dados intrinsecos, como um caractere ou inteiro. Voce ja viu um elemento complexo: a matriz de caracteres usada em addr. Outros tipos de dados complexos sao matrizes unidimensionais e muitidimensionais e outros tipos de dados e estruturas. Um elemento de estrutura que é uma matriz é tratada como vocé poderia esperar a partir dos exemplos anteriores. Por exemplo, considere esta estrutura: B struct x { 186 C—Completo e Total Cap.7 int a[10](10]; /* matriz de 10 x 10 itens */ float b; hy: Para referenciar 0 inteiro 3,7 em a da estrutura y, escreva @ y.at3117) Quando um elemento de uma estrutura é um elemento de outra estrutura, ela 6 chamada estrutura aninhada. Por exemplo, a estrutura address 6 aninhada em emp neste exemplo: struct emp ( struct addr address; /* estrutura aninhada */ float wage; ) worker; Aqui, a estrutura emp foi definida como tendo dois elementos. O primeiro ele- mento é a estrutura do tipo addr, que contém o endereco de um empregado. O outro 6 wage, que contém o saldrio do empregado. O seguinte fragmento de cédigo atribui 93456 ao elemento zip de address. @ worker.address.zip = 93456; Como voeé pode ver, os elementos de cada estrutura sao referenciados do mais externo ao mais interno. O padrao ANSI C especifica que as estruturas podem ser aninhadas até 15 niveis. A maioria dos compiladores permite mais. @ Campos de Bits Ao contrario da maioria das linguagens de computador, C tem um método in- trinseco para acessar um tinico bit dentro de um byte. Isso pode ser util por um certo ntimero de razies: ™ Se o armazenamento é limitado, vocé pode armazenar diversas varidveis booleanas (verdadeiro/falso) em um byte. Certos dispositivos transmitem informacées codificadas nos bits. @ Certas rotinas de criptografia precisam acessar os bits dentro de um byte. Cap. 7 Estruturas, unibes, enumeragdes ¢ tipos definidos pelo isiirio 187 Embora essas tarefas possam ser realizadas usando os operadores bit a bit, um campo de bit pode acrescentar mais estrutura (e possivelmente eficiéncia) ao seu cédigo. Para acessar os bits, C usa um método baseado na estrutura. Um campo de bits é, na verdade, apenas um tipo de elemento de estrutura que define 0 com- primento, em bits, do campo. A forma geral de uma definigao de campo de bit ¢ struct identificador| tipo nomel : comprimento; tipo nome2 : comprimento; tipo nomeN : comprimento; } lista_de_varidveis; Um campo de bits deve ser declarado como int, unsigned ou signed. Campos de bits de comprimento 1 devem ser declarados como unsigned, porque um tinico bit nao pode ter um sinal. (Alguns compiladores s6 permitem campos de bit unsigned.) O ntimero de bits no campo de bits é especificado por comprimento. Campos de bits sao freqiientemente usados quando se analisa a entrada de um dispositivo de hardware. Por exemplo, o estado da porta do adaptador de comunicagées seriais poderia retornar um byte de estado organizado desta forma: Bit Significade quando ligado 0 Allteragao na linha clear-to-send Alteracao em data-set-ready Borda de subida da portadora detectada Alteracao na linha de recepcao 1 2 3 4 Clear-to-send 5 Data-set-ready 6 — Chamada do telefone 7 Sinal recebido Vocé pode representar a informagao em um byte de estado usando o seguinte campo de bits: struct status_type { unsigned delta_cts: unsigned delta_dsr unsigned tr_edge: 1; 188 Cap. 7 unsigned delta_rec: unsigned cts: unsigned dsr: unsigned ring: unsigned rec_lin } status; Vocé pode usar uma rotina semelhante a esta, mostrada aqui, para permitir que um programa determine quando pode enviar ou receber dados. status = get_port_status(}; (status.cts) printf("livre para envia: status.dsr) printf("dados prontos"}; Para atribuir um valor a um campo de bits, simplesmente use a forma que vocé usaria para qualquer outro tipo de elemento de estrutura. Por exemplo, este frag- mento de cédigo limpa o campo ring: @ status.ring = 0 Como vocé pode ver, a partir destes exemplos, cada campo de bits 6 acessado com o operador ponto. Porém, se a estrutura é referenciada por meio de um ponteiro, vocé deve usar o operador ->. Nao é necessério dar um nome a todo campo de bits. Isso torna facil alcangar o bit que vocé quer, contornando os nao usados. Por exemplo, se apenas cts e dsr importam, vocé poderia declarar a estrutura status_type desta forma: struct status_type { unsigned : 4a; unsigned cts: ae unsigned ds. a: ) status; Além disso, note que os bits apds dsr nao precisam ser especificados se nao sio usados. E vélido misturar elementos normais de estrutura com elementos de campos de bit. Por exemplo, struct emp { struct addr address; float pay; unsigned lay_off: /* ocioso ou ativo */ Cap. 7 Estruturas, uniées, enumeragées e tipos definidos pelo usurio 189 unsigned hourly: 1; /* pagamento por horas ou salario */ unsigned deduction:3; /* dedugées de imposto */ a define um registro de empregado que utiliza apenas 1 byte para segurar trés pedacos de informacao: 0 estado do empregado se contratado ou assalariado, e © ntimero de dedugdes. Sem o campo de bit, essa informagio usaria 3 bytes. Varidveis de campo de bits tém certas restrigdes. Vocé nao pode obter o endereco de uma varidvel de campo de bits. Varidveis de campo de bits nao podem ser organizadas em matrizes. Vocé nao pode ultrapassar os limites de um inteiro. Nao pode saber, de maquina para maquina, se os campos estarao dispostos da esquerda para direita ou da direita para a esquerda. Em outras palavras, qualquer cédigo que use campos de bits pode ter algumas dependén- cias da maquina EE Unies Em C, uma union é uma posigao de meméria que é compartilhada por duas ou mais varidveis diferentes, geralmente de tipos diferentes, em momentos diferen- tes. A definicéo de uma union é semelhante a definicao de estrutura. Sua forma geral é union identificador { tipo nome_da_variivel; tipo nome_da_varifvel; tipo nome_da_varidvel; | varidveis_uniao; Por exemplo, union u_type ( int i; char ch; oe Essa definicao nao declara quaisquer variaveis. Vocé pode declarar uma variavel colocando seu nome no final da definigdo ou usando um comando de declaragao. separado. Para declarar um varidvel union cnvt do tipo u_type, usando a defi- nigéo dada ha pouco, escreva

You might also like